UNPKG

fireship-ai-blog

Version:

A beautiful, SEO-friendly blog package for Next.js 13+ with Fireship.ai API integration

1,154 lines (1,145 loc) 564 kB
'use strict'; var React3 = require('react'); var jsxRuntime = require('react/jsx-runtime'); var lucideReact = require('lucide-react'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React3__default = /*#__PURE__*/_interopDefault(React3); var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all3) => { for (var name in all3) __defProp(target, name, { get: all3[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key2 of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key2) && key2 !== except) __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. __defProp(target, "default", { value: mod, enumerable: true }) , mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // utils/api.ts var api_exports = {}; __export(api_exports, { FireshipApi: () => FireshipApi, createApiHelpers: () => createApiHelpers, createFireshipApi: () => createFireshipApi, default: () => exports.FireshipApi, getAnalytics: () => getAnalytics, getCampaigns: () => getCampaigns, getFireshipApi: () => getFireshipApi, getPostBySlug: () => getPostBySlug, getPosts: () => getPosts, getRelatedPosts: () => getRelatedPosts, submitPost: () => submitPost, trackView: () => trackView, updateThemeSettings: () => updateThemeSettings }); function createFireshipApi(config) { return new FireshipApi(config); } function createApiHelpers(api) { return { getPosts: (params) => api.getPosts(params), getPostBySlug: (slug) => api.getPostBySlug(slug), getRelatedPosts: (postSlug, limit) => api.getRelatedPosts(postSlug, limit), getAnalytics: (params) => api.getAnalytics(params), updateThemeSettings: (settings) => api.updateThemeSettings(settings), submitPost: (postData) => api.submitPost(postData), trackView: (postId, metadata) => api.trackView(postId, metadata), getCampaigns: () => api.getCampaigns() }; } function getFireshipApi() { const baseUrl = typeof window === "undefined" ? process.env.NEXT_PUBLIC_BASE_URL || "" : ""; return createFireshipApi({ // Use custom fetcher so absolute or relative URLs both work fetcher: async (endpoint, options) => { const url = `${baseUrl}/api/${endpoint.startsWith("/") ? endpoint.slice(1) : endpoint}`; return fetch(url, options); } }); } var FireshipApi; exports.FireshipApi = void 0; var __defaultApi, __helpers, getPosts, getPostBySlug, getRelatedPosts, getAnalytics, updateThemeSettings, submitPost, trackView, getCampaigns; var init_api = __esm({ "utils/api.ts"() { FireshipApi = class { constructor(config) { this.config = config; if (!config.dataProvider && !config.endpoints && !config.fetcher) { throw new Error("FireshipApi requires either dataProvider, endpoints, or fetcher configuration"); } } async request(endpoint, options = {}) { if (this.config.fetcher) { const response = await this.config.fetcher(endpoint, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); } if (this.config.endpoints) { const url = `${this.config.endpoints.baseUrl}${endpoint}`; const response = await fetch(url, { ...options, headers: { "Content-Type": "application/json", ...this.config.endpoints.headers, ...options.headers } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); } throw new Error("No valid request method configured"); } // Blog Posts async getPosts(params = {}) { if (this.config.dataProvider?.getPosts) { return this.config.dataProvider.getPosts(params); } if (this.config.endpoints?.posts) { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key2, value]) => { if (value !== void 0) { searchParams.append(key2, value.toString()); } }); const endpoint = `${this.config.endpoints.posts}?${searchParams.toString()}`; return this.request(endpoint); } throw new Error("No posts endpoint or data provider configured"); } async getPostBySlug(slug) { const response = await this.request(`blog/posts/${slug}`); return response.post; } async getRelatedPosts(postSlug, limit = 5) { const response = await this.request( `blog/posts/${postSlug}/related?limit=${limit}` ); return response.relatedPosts; } async submitPost(postData) { const method = postData.id ? "PUT" : "POST"; const endpoint = postData.id ? `blog/posts/${postData.slug}` : "blog/posts"; const response = await this.request(endpoint, { method, body: JSON.stringify(postData) }); return response.post; } async deletePost(postId) { await this.request(`blog/posts/${postId}`, { method: "DELETE" }); } // Analytics async getAnalytics(params = {}) { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key2, value]) => { if (value !== void 0) { searchParams.append(key2, value); } }); const response = await this.request( `blog/analytics?${searchParams.toString()}` ); return response; } async trackView(postId, metadata = {}) { await this.request("blog/analytics/view", { method: "POST", body: JSON.stringify({ postId, ...metadata }) }); } // Theme async getThemeSettings() { const response = await this.request("blog/theme"); return response; } async updateThemeSettings(settings) { const response = await this.request("blog/theme", { method: "PUT", body: JSON.stringify(settings) }); return response; } // Campaigns async getCampaigns() { const response = await this.request("campaigns"); return response.campaigns; } }; exports.FireshipApi = FireshipApi; __defaultApi = getFireshipApi(); __helpers = createApiHelpers(__defaultApi); getPosts = __helpers.getPosts; getPostBySlug = __helpers.getPostBySlug; getRelatedPosts = __helpers.getRelatedPosts; getAnalytics = __helpers.getAnalytics; updateThemeSettings = __helpers.updateThemeSettings; submitPost = __helpers.submitPost; trackView = __helpers.trackView; getCampaigns = __helpers.getCampaigns; } }); // utils/theme.ts var theme_exports = {}; __export(theme_exports, { applyTheme: () => exports.applyTheme, defaultTheme: () => exports.defaultTheme, generateColorVariations: () => exports.generateColorVariations, getSystemDarkMode: () => exports.getSystemDarkMode, getThemeClasses: () => exports.getThemeClasses, hexToRgb: () => exports.hexToRgb, initializeDarkMode: () => exports.initializeDarkMode, initializeTheme: () => exports.initializeTheme, loadThemeFromStorage: () => exports.loadThemeFromStorage, saveThemeToStorage: () => exports.saveThemeToStorage, toggleDarkMode: () => exports.toggleDarkMode }); exports.defaultTheme = void 0; exports.applyTheme = void 0; exports.getThemeClasses = void 0; exports.hexToRgb = void 0; exports.generateColorVariations = void 0; exports.loadThemeFromStorage = void 0; exports.saveThemeToStorage = void 0; exports.initializeTheme = void 0; exports.getSystemDarkMode = void 0; exports.toggleDarkMode = void 0; exports.initializeDarkMode = void 0; var init_theme = __esm({ "utils/theme.ts"() { exports.defaultTheme = { primaryColor: "#3b82f6", // blue-500 secondaryColor: "#64748b", // slate-500 fontFamily: "inter", fontSize: "base", showSidebar: true, glassEffect: true, borderRadius: "xl" }; exports.applyTheme = (settings) => { const root5 = document.documentElement; root5.style.setProperty("--fireship-primary", settings.primaryColor); root5.style.setProperty("--fireship-secondary", settings.secondaryColor); const fontFamilyMap = { "inter": 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif', "sf-pro": '-apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", system-ui, sans-serif', "system": 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif' }; root5.style.setProperty("--fireship-font-family", fontFamilyMap[settings.fontFamily]); const fontSizeMap = { "sm": "14px", "base": "16px", "lg": "18px" }; root5.style.setProperty("--fireship-font-size", fontSizeMap[settings.fontSize]); const borderRadiusMap = { "none": "0", "sm": "0.125rem", "md": "0.375rem", "lg": "0.5rem", "xl": "0.75rem" }; root5.style.setProperty("--fireship-border-radius", borderRadiusMap[settings.borderRadius]); root5.style.setProperty("--fireship-glass-effect", settings.glassEffect ? "1" : "0"); root5.style.setProperty("--fireship-sidebar-display", settings.showSidebar ? "block" : "none"); }; exports.getThemeClasses = (settings) => { const baseClasses = { container: "fireship-blog-container", card: "fireship-blog-card", text: "fireship-blog-text", heading: "fireship-blog-heading", button: "fireship-blog-button", sidebar: "fireship-blog-sidebar" }; const glassClasses = settings.glassEffect ? "backdrop-blur-xl bg-white/30 dark:bg-zinc-900/30 border border-white/20 dark:border-zinc-700/20" : "bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800"; const radiusClass = { "none": "rounded-none", "sm": "rounded-sm", "md": "rounded-md", "lg": "rounded-lg", "xl": "rounded-xl" }[settings.borderRadius]; const fontSizeClass = { "sm": "text-sm", "base": "text-base", "lg": "text-lg" }[settings.fontSize]; const fontFamilyClass = { "inter": "font-inter", "sf-pro": "font-sf-pro", "system": "font-system" }[settings.fontFamily]; return { ...baseClasses, glass: glassClasses, radius: radiusClass, fontSize: fontSizeClass, fontFamily: fontFamilyClass, primaryButton: `bg-[var(--fireship-primary)] hover:bg-[var(--fireship-primary)]/90 text-white ${radiusClass}`, secondaryButton: `bg-[var(--fireship-secondary)] hover:bg-[var(--fireship-secondary)]/90 text-white ${radiusClass}` }; }; exports.hexToRgb = (hex) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; }; exports.generateColorVariations = (baseColor) => { const rgb = exports.hexToRgb(baseColor); if (!rgb) return { base: baseColor }; const { r, g, b } = rgb; const variations = { 50: `rgb(${Math.min(255, r + 40)}, ${Math.min(255, g + 40)}, ${Math.min(255, b + 40)})`, 100: `rgb(${Math.min(255, r + 30)}, ${Math.min(255, g + 30)}, ${Math.min(255, b + 30)})`, 200: `rgb(${Math.min(255, r + 20)}, ${Math.min(255, g + 20)}, ${Math.min(255, b + 20)})`, 300: `rgb(${Math.min(255, r + 10)}, ${Math.min(255, g + 10)}, ${Math.min(255, b + 10)})`, 400: `rgb(${r}, ${g}, ${b})`, 500: baseColor, 600: `rgb(${Math.max(0, r - 10)}, ${Math.max(0, g - 10)}, ${Math.max(0, b - 10)})`, 700: `rgb(${Math.max(0, r - 20)}, ${Math.max(0, g - 20)}, ${Math.max(0, b - 20)})`, 800: `rgb(${Math.max(0, r - 30)}, ${Math.max(0, g - 30)}, ${Math.max(0, b - 30)})`, 900: `rgb(${Math.max(0, r - 40)}, ${Math.max(0, g - 40)}, ${Math.max(0, b - 40)})` }; return variations; }; exports.loadThemeFromStorage = () => { if (typeof window === "undefined") return exports.defaultTheme; try { const stored = localStorage.getItem("fireship-blog-theme"); if (stored) { const parsed = JSON.parse(stored); return { ...exports.defaultTheme, ...parsed }; } } catch (error) { console.warn("Failed to load theme from localStorage:", error); } return exports.defaultTheme; }; exports.saveThemeToStorage = (settings) => { if (typeof window === "undefined") return; try { localStorage.setItem("fireship-blog-theme", JSON.stringify(settings)); } catch (error) { console.warn("Failed to save theme to localStorage:", error); } }; exports.initializeTheme = (settings) => { const theme = settings ? { ...exports.defaultTheme, ...settings } : exports.loadThemeFromStorage(); exports.applyTheme(theme); return theme; }; exports.getSystemDarkMode = () => { if (typeof window === "undefined") return false; return window.matchMedia("(prefers-color-scheme: dark)").matches; }; exports.toggleDarkMode = (isDark) => { if (typeof window === "undefined") return false; const shouldBeDark = isDark !== void 0 ? isDark : !document.documentElement.classList.contains("dark"); if (shouldBeDark) { document.documentElement.classList.add("dark"); } else { document.documentElement.classList.remove("dark"); } try { localStorage.setItem("fireship-blog-dark-mode", shouldBeDark.toString()); } catch (error) { console.warn("Failed to save dark mode preference:", error); } return shouldBeDark; }; exports.initializeDarkMode = () => { if (typeof window === "undefined") return false; try { const stored = localStorage.getItem("fireship-blog-dark-mode"); const shouldBeDark = stored ? stored === "true" : exports.getSystemDarkMode(); return exports.toggleDarkMode(shouldBeDark); } catch (error) { console.warn("Failed to initialize dark mode:", error); return exports.getSystemDarkMode(); } }; } }); // ../../node_modules/extend/index.js var require_extend = __commonJS({ "../../node_modules/extend/index.js"(exports, module) { var hasOwn = Object.prototype.hasOwnProperty; var toStr = Object.prototype.toString; var defineProperty = Object.defineProperty; var gOPD = Object.getOwnPropertyDescriptor; var isArray = function isArray2(arr) { if (typeof Array.isArray === "function") { return Array.isArray(arr); } return toStr.call(arr) === "[object Array]"; }; var isPlainObject2 = function isPlainObject3(obj) { if (!obj || toStr.call(obj) !== "[object Object]") { return false; } var hasOwnConstructor = hasOwn.call(obj, "constructor"); var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, "isPrototypeOf"); if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { return false; } var key2; for (key2 in obj) { } return typeof key2 === "undefined" || hasOwn.call(obj, key2); }; var setProperty = function setProperty2(target, options) { if (defineProperty && options.name === "__proto__") { defineProperty(target, options.name, { enumerable: true, configurable: true, value: options.newValue, writable: true }); } else { target[options.name] = options.newValue; } }; var getProperty = function getProperty2(obj, name) { if (name === "__proto__") { if (!hasOwn.call(obj, name)) { return void 0; } else if (gOPD) { return gOPD(obj, name).value; } } return obj[name]; }; module.exports = function extend2() { var options, name, src, copy, copyIsArray, clone; var target = arguments[0]; var i = 1; var length = arguments.length; var deep = false; if (typeof target === "boolean") { deep = target; target = arguments[1] || {}; i = 2; } if (target == null || typeof target !== "object" && typeof target !== "function") { target = {}; } for (; i < length; ++i) { options = arguments[i]; if (options != null) { for (name in options) { src = getProperty(target, name); copy = getProperty(options, name); if (target !== copy) { if (deep && copy && (isPlainObject2(copy) || (copyIsArray = isArray(copy)))) { if (copyIsArray) { copyIsArray = false; clone = src && isArray(src) ? src : []; } else { clone = src && isPlainObject2(src) ? src : {}; } setProperty(target, { name, newValue: extend2(deep, clone, copy) }); } else if (typeof copy !== "undefined") { setProperty(target, { name, newValue: copy }); } } } } } return target; }; } }); var getApiKey = (providedKey) => { if (providedKey) return providedKey; const envKey = process.env.NEXT_PUBLIC_FIRESHIP_BLOG_PUBLISHABLE_KEY; if (!envKey) { throw new Error("NEXT_PUBLIC_FIRESHIP_BLOG_PUBLISHABLE_KEY is required"); } return envKey; }; var fetchBlogPosts = async (apiKey, apiBase = "/api/blog") => { const response = await fetch(`${apiBase}/posts`, { headers: { "Content-Type": "application/json", "x-api-key": apiKey }, cache: "no-store" }); if (!response.ok) { throw new Error("Failed to fetch blog posts"); } const data = await response.json(); return Array.isArray(data) ? data : Array.isArray(data?.posts) ? data.posts : []; }; var fetchBlogPost = async (apiKey, slug, apiBase = "/api/blog") => { const response = await fetch(`${apiBase}/posts/${encodeURIComponent(slug)}`, { headers: { "Content-Type": "application/json", "x-api-key": apiKey }, cache: "no-store" }); if (!response.ok) { throw new Error("Failed to fetch blog post"); } const data = await response.json(); return data.post || data; }; var useSearchParams = () => { const [searchParams, setSearchParams] = React3.useState(new URLSearchParams()); const [isClient, setIsClient] = React3.useState(false); React3.useEffect(() => { setIsClient(true); setSearchParams(new URLSearchParams(window.location.search)); }, []); React3.useEffect(() => { if (!isClient) return; const updateSearchParams = () => { setSearchParams(new URLSearchParams(window.location.search)); }; window.addEventListener("popstate", updateSearchParams); window.addEventListener("urlchange", updateSearchParams); return () => { window.removeEventListener("popstate", updateSearchParams); window.removeEventListener("urlchange", updateSearchParams); }; }, [isClient]); return searchParams; }; function FireshipBlog({ className = "", apiKey, apiBase = "/api/blog" }) { const searchParams = useSearchParams(); const slug = searchParams.get("slug"); const [posts, setPosts] = React3.useState([]); const [currentPost, setCurrentPost] = React3.useState(null); const [loading, setLoading] = React3.useState(true); const [error, setError] = React3.useState(null); const [isClient, setIsClient] = React3.useState(false); const blogApiKey = getApiKey(apiKey); React3.useEffect(() => { setIsClient(true); }, []); React3.useEffect(() => { if (!isClient) return; const loadPosts = async () => { try { setLoading(true); const posts2 = await fetchBlogPosts(blogApiKey, apiBase); setPosts(posts2); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load posts"); } finally { setLoading(false); } }; loadPosts(); }, [blogApiKey, apiBase, isClient]); React3.useEffect(() => { if (!isClient) return; if (!slug) { setCurrentPost(null); return; } const loadPost = async () => { try { setLoading(true); const post = await fetchBlogPost(blogApiKey, slug, apiBase); setCurrentPost(post); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load post"); } finally { setLoading(false); } }; loadPost(); }, [slug, blogApiKey, apiBase, isClient]); const handlePostClick = (post) => { const currentUrl = new URL(window.location.href); currentUrl.searchParams.set("slug", post.slug); const newUrl = currentUrl.pathname + "?" + currentUrl.searchParams.toString(); window.history.pushState({}, "", newUrl); window.dispatchEvent(new CustomEvent("urlchange")); }; const handleBackToBlog = () => { const currentUrl = new URL(window.location.href); currentUrl.searchParams.delete("slug"); const newUrl = currentUrl.pathname + (currentUrl.searchParams.toString() ? "?" + currentUrl.searchParams.toString() : ""); window.history.pushState({}, "", newUrl); window.dispatchEvent(new CustomEvent("urlchange")); }; if (!isClient || loading) { return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `fireship-blog ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-zinc-900 dark:border-zinc-100" }) }) }); } if (error) { return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `fireship-blog ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-red-500 mb-4", children: [ "Error: ", error ] }), /* @__PURE__ */ jsxRuntime.jsx( "button", { onClick: () => window.location.reload(), className: "px-4 py-2 bg-zinc-900 text-white rounded-md hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200", children: "Retry" } ) ] }) }); } if (currentPost) { return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `fireship-blog ${className}`, children: [ /* @__PURE__ */ jsxRuntime.jsx( "button", { onClick: handleBackToBlog, className: "mb-6 px-4 py-2 text-sm bg-white/70 dark:bg-zinc-900/70 backdrop-blur-xl border border-zinc-200/50 dark:border-zinc-700/50 rounded-xl hover:bg-white/80 dark:hover:bg-zinc-900/80 transition-all duration-300", children: "\u2190 Back to Blog" } ), /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "bg-white/70 dark:bg-zinc-900/70 backdrop-blur-xl border border-zinc-200/50 dark:border-zinc-700/50 rounded-2xl p-8 shadow-lg", children: [ currentPost.featuredImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-8 overflow-hidden rounded-xl", children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: currentPost.featuredImage, alt: currentPost.title, className: "w-full h-64 object-cover" } ) }), /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "mb-8", children: [ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-4xl font-thin text-zinc-900 dark:text-zinc-100 mb-4 tracking-tight", children: currentPost.title }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-4 text-sm text-zinc-600 dark:text-zinc-400", children: [ currentPost.author?.avatar && /* @__PURE__ */ jsxRuntime.jsx( "img", { src: currentPost.author.avatar, alt: currentPost.author.name, className: "w-8 h-8 rounded-full" } ), /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: currentPost.author?.name || "Anonymous" }), currentPost.publishedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u2022" }), currentPost.publishedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(currentPost.publishedAt).toLocaleDateString() }) ] }) ] }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "prose prose-zinc dark:prose-invert max-w-none", children: currentPost.content ? /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: currentPost.content } }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: currentPost.excerpt || "No content available." }) }) ] }) ] }); } return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `fireship-blog ${className}`, children: [ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-12 text-center", children: [ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-5xl font-thin text-zinc-900 dark:text-zinc-100 mb-4 tracking-tight", children: "Blog" }), /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xl text-zinc-600 dark:text-zinc-400 font-light", children: "Latest insights and updates" }) ] }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-8 md:grid-cols-2 lg:grid-cols-3", children: posts.map((post) => /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "group cursor-pointer", onClick: () => handlePostClick(post), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white/70 dark:bg-zinc-900/70 backdrop-blur-xl border border-zinc-200/50 dark:border-zinc-700/50 rounded-2xl p-6 shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-[1.02] hover:bg-white/80 dark:hover:bg-zinc-900/80", children: [ post.featuredImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 overflow-hidden rounded-xl", children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: post.featuredImage, alt: post.title, className: "w-full h-48 object-cover transition-transform duration-300 group-hover:scale-105" } ) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100 line-clamp-2 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors", children: post.title }), post.excerpt && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-zinc-600 dark:text-zinc-400 text-sm line-clamp-3 leading-relaxed", children: post.excerpt }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between pt-2", children: [ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [ post.author?.avatar && /* @__PURE__ */ jsxRuntime.jsx( "img", { src: post.author.avatar, alt: post.author.name, className: "w-6 h-6 rounded-full" } ), /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-zinc-500 dark:text-zinc-500 font-medium", children: post.author?.name || "Anonymous" }) ] }), post.publishedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-zinc-500 dark:text-zinc-500", children: new Date(post.publishedAt).toLocaleDateString() }) ] }) ] }) ] }) }, post.id )) }), posts.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-16", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white/70 dark:bg-zinc-900/70 backdrop-blur-xl border border-zinc-200/50 dark:border-zinc-700/50 rounded-2xl p-8 shadow-lg inline-block", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 text-lg font-light", children: "No blog posts found." }) }) }) ] }); } // hooks/useFireshipPosts.ts init_api(); var useFireshipPosts = (options = {}) => { const [posts, setPosts] = React3.useState([]); const [loading, setLoading] = React3.useState(true); const [error, setError] = React3.useState(null); const [hasMore, setHasMore] = React3.useState(true); const [totalCount, setTotalCount] = React3.useState(0); const [currentPage, setCurrentPage] = React3.useState(1); const { category, tag, limit = 10, page = 1, campaignId } = options; const fetchPosts = React3.useCallback(async (pageNum, append = false) => { try { setLoading(true); setError(null); const response = await getPosts({ offset: (pageNum - 1) * limit, limit, category, tag, campaignId }); if (append) { setPosts((prev) => [...prev, ...response.data]); } else { setPosts(response.data); } setTotalCount(response.pagination.total); setHasMore(pageNum < response.pagination.totalPages); setCurrentPage(pageNum); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Failed to fetch posts"; setError(errorMessage); console.error("Error fetching posts:", err); } finally { setLoading(false); } }, [category, tag, limit, campaignId]); const loadMore = React3.useCallback(() => { if (!loading && hasMore) { fetchPosts(currentPage + 1, true); } }, [loading, hasMore, currentPage, fetchPosts]); const refresh = React3.useCallback(() => { setCurrentPage(1); fetchPosts(1, false); }, [fetchPosts]); React3.useEffect(() => { fetchPosts(page, false); }, [fetchPosts, page]); return { posts, loading, error, hasMore, loadMore, refresh, totalCount }; }; var useFireshipPost = (slug) => { const [post, setPost] = React3.useState(null); const [loading, setLoading] = React3.useState(true); const [error, setError] = React3.useState(null); React3.useEffect(() => { if (!slug) return; const fetchPost = async () => { try { setLoading(true); setError(null); const { getPostBySlug: getPostBySlug2 } = await Promise.resolve().then(() => (init_api(), api_exports)); const fetchedPost = await getPostBySlug2(slug); setPost(fetchedPost); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Failed to fetch post"; setError(errorMessage); console.error("Error fetching post:", err); } finally { setLoading(false); } }; fetchPost(); }, [slug]); return { post, loading, error }; }; var useRelatedPosts = (postSlug, limit = 5) => { const [relatedPosts, setRelatedPosts] = React3.useState([]); const [loading, setLoading] = React3.useState(true); const [error, setError] = React3.useState(null); React3.useEffect(() => { if (!postSlug) return; const fetchRelatedPosts = async () => { try { setLoading(true); setError(null); const { getRelatedPosts: getRelatedPosts2 } = await Promise.resolve().then(() => (init_api(), api_exports)); const posts = await getRelatedPosts2(postSlug, limit); setRelatedPosts(posts); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Failed to fetch related posts"; setError(errorMessage); console.error("Error fetching related posts:", err); } finally { setLoading(false); } }; fetchRelatedPosts(); }, [postSlug, limit]); return { relatedPosts, loading, error }; }; var useFireshipTheme = () => { const [theme, setTheme] = React3.useState(() => { if (typeof window !== "undefined") { const { loadThemeFromStorage: loadThemeFromStorage2 } = (init_theme(), __toCommonJS(theme_exports)); return loadThemeFromStorage2(); } return null; }); const updateTheme = React3.useCallback(async (newTheme) => { try { const { applyTheme: applyTheme2, saveThemeToStorage: saveThemeToStorage2 } = await Promise.resolve().then(() => (init_theme(), theme_exports)); const { updateThemeSettings: updateThemeSettings2 } = await Promise.resolve().then(() => (init_api(), api_exports)); applyTheme2(newTheme); saveThemeToStorage2(newTheme); setTheme(newTheme); await updateThemeSettings2(newTheme); } catch (error) { console.error("Error updating theme:", error); } }, []); React3.useEffect(() => { if (theme && typeof window !== "undefined") { const { applyTheme: applyTheme2 } = (init_theme(), __toCommonJS(theme_exports)); applyTheme2(theme); } }, [theme]); return { theme, updateTheme }; }; var useFireshipAnalytics = (postId) => { const [analytics, setAnalytics] = React3.useState(null); const [loading, setLoading] = React3.useState(true); const [error, setError] = React3.useState(null); const fetchAnalytics = React3.useCallback(async (dateRange) => { try { setLoading(true); setError(null); const { getAnalytics: getAnalytics2 } = await Promise.resolve().then(() => (init_api(), api_exports)); const data = await getAnalytics2({ postId, dateFrom: dateRange?.from, dateTo: dateRange?.to }); setAnalytics(data); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Failed to fetch analytics"; setError(errorMessage); console.error("Error fetching analytics:", err); } finally { setLoading(false); } }, [postId]); React3.useEffect(() => { fetchAnalytics(); }, [fetchAnalytics]); return { analytics, loading, error, refetch: fetchAnalytics }; }; // components/RelatedPostsSidebar.tsx init_theme(); var RelatedPostsSidebar = ({ postSlug, limit = 5, className = "", title = "Related Posts" }) => { const { relatedPosts, loading, error } = useRelatedPosts(postSlug, limit); const { theme } = useFireshipTheme(); const themeClasses = exports.getThemeClasses(theme || { primaryColor: "#3b82f6", secondaryColor: "#64748b", fontFamily: "inter", fontSize: "base", showSidebar: true, glassEffect: true, borderRadius: "xl" }); if (loading) { return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: [ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-6 flex items-center gap-2", children: [ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingUp, { className: "w-5 h-5" }), title ] }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: Array.from({ length: 3 }).map((_, index2) => /* @__PURE__ */ jsxRuntime.jsx(RelatedPostSkeleton, {}, index2)) }) ] }); } if (error || !relatedPosts || !relatedPosts.length) { return null; } return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: [ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-6 flex items-center gap-2", children: [ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingUp, { className: "w-5 h-5 text-[var(--fireship-primary)]" }), title ] }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: relatedPosts.map((post) => /* @__PURE__ */ jsxRuntime.jsx(RelatedPostItem, { post }, post.id)) }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6 pt-4 border-t border-zinc-200 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "w-full text-center text-[var(--fireship-primary)] hover:text-[var(--fireship-primary)]/80 font-medium text-sm transition-colors", children: "View All Posts \u2192" }) }) ] }); }; var RelatedPostItem = ({ post, onClick }) => { const { theme } = useFireshipTheme(); const themeClasses = exports.getThemeClasses(theme || { primaryColor: "#3b82f6", secondaryColor: "#64748b", fontFamily: "inter", fontSize: "base", showSidebar: true, glassEffect: true, borderRadius: "xl" }); const handleClick = () => { if (onClick) { onClick(post); } else { window.location.href = `/blog/${post.slug}`; } }; return /* @__PURE__ */ jsxRuntime.jsx( "article", { className: "group cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800/50 p-3 -m-3 rounded-lg transition-colors", onClick: handleClick, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [ post.featuredImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: post.featuredImage, alt: post.title, className: `w-16 h-16 object-cover ${themeClasses.radius} group-hover:scale-105 transition-transform` } ) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-block px-2 py-0.5 ${themeClasses.radius} bg-zinc-100 dark:bg-zinc-800 text-xs font-medium text-zinc-600 dark:text-zinc-400`, children: post.category }) }), /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-zinc-900 dark:text-zinc-100 text-sm line-clamp-2 group-hover:text-[var(--fireship-primary)] transition-colors mb-2", children: post.title }), /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-zinc-600 dark:text-zinc-400 line-clamp-2 mb-2", children: post.excerpt }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 text-xs text-zinc-500 dark:text-zinc-500", children: [ /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: post.publishedAt, children: new Date(post.publishedAt).toLocaleDateString("en-US", { month: "short", day: "numeric" }) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "w-3 h-3" }), /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [ post.readTime, " min" ] }) ] }) ] }) ] }) ] }) } ); }; var RelatedPostSkeleton = () => { const { theme } = useFireshipTheme(); const themeClasses = exports.getThemeClasses(theme || { primaryColor: "#3b82f6", secondaryColor: "#64748b", fontFamily: "inter", fontSize: "base", showSidebar: true, glassEffect: true, borderRadius: "xl" }); return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-16 h-16 bg-zinc-200 dark:bg-zinc-800 ${themeClasses.radius} flex-shrink-0` }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `h-4 w-16 bg-zinc-200 dark:bg-zinc-800 ${themeClasses.radius} mb-1` }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 mb-2", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-full" }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-3/4" }) ] }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 mb-2", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 bg-zinc-200 dark:bg-zinc-800 rounded w-full" }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 bg-zinc-200 dark:bg-zinc-800 rounded w-2/3" }) ] }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 bg-zinc-200 dark:bg-zinc-800 rounded w-12" }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 bg-zinc-200 dark:bg-zinc-800 rounded w-16" }) ] }) ] }) ] }) }); }; var TrendingPostsSidebar = ({ limit = 5, className = "", title = "Trending Posts", timeframe = "week" }) => { const { theme } = useFireshipTheme(); const themeClasses = exports.getThemeClasses(theme || { primaryColor: "#3b82f6", secondaryColor: "#64748b", fontFamily: "inter", fontSize: "base", showSidebar: true, glassEffect: true, borderRadius: "xl" }); return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: [ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-6 flex items-center gap-2", children: [ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingUp, { className: "w-5 h-5 text-[var(--fireship-primary)]" }), title ] }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 mb-6", children: ["day", "week", "month", "year"].map((period) => /* @__PURE__ */ jsxRuntime.jsx( "button", { className: `px-3 py-1 text-xs font-medium ${themeClasses.radius} transition-colors ${timeframe === period ? "bg-[var(--fireship-primary)] text-white" : "bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700"}`, children: period.charAt(0).toUpperCase() + period.slice(1) }, period )) }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: Array.from({ length: limit }).map((_, index2) => /* @__PURE__ */ jsxRuntime.jsx(RelatedPostSkeleton, {}, index2)) }) ] }); }; init_theme(); var NextPostNavigation = ({ currentPostId, className = "", showThumbnails = true }) => { const [previousPost, setPreviousPost] = React3.useState(null); const [nextPost, setNextPost] = React3.useState(null); const [loading, setLoading] = React3.useState(true); const { theme } = useFireshipTheme(); const themeClasses = exports.getThemeClasses(theme || { primaryColor: "#3b82f6", secondaryColor: "#64748b", fontFamily: "inter", fontSize: "base", showSidebar: true, glassEffect: true, borderRadius: "xl" }); React3.useEffect(() => { const fetchNavigationPosts = async () => { try { setLoading(true); const baseUrl = typeof window === "undefined" ? process.env.NEXT_PUBLIC_BASE_URL || "" : ""; const res = await fetch(`${baseUrl}/api/blog/posts/${currentPostId}/navigation`); if (!res.ok) throw new Error(`Failed to fetch navigation: ${res.status}`); const data = await res.json(); setPreviousPost(data.previous); setNextPost(data.next); } catch (error) { console.error("Error fetching navigation posts:", error); } finally { setLoading(false); } }; if (currentPostId) { fetchNavigationPosts(); } }, [currentPostId]); if (loading) { return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [ /* @__PURE__ */ jsxRuntime.jsx(NavigationItemSkeleton, {}), /* @__PURE__ */ jsxRuntime.jsx(NavigationItemSkeleton, {}) ] }) }); } if (!previousPost && !nextPost) { return null; } return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-start", children: previousPost ? /* @__PURE__ */ jsxRuntime.jsx( NavigationItem, { post: previousPost, direction: "previous", showThumbnail: showThumbnails } ) : /* @__PURE__ */ jsxRuntime.jsx("div", {}) }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: nextPost ? /* @__PURE__ */ jsxRuntime.jsx( NavigationItem, { post: nextPost, direction: "next", showThumbnail: showThumbnails } ) : /* @__PURE__ */ jsxRuntime.jsx("div", {}) }) ] }) }); }; var NavigationItem = ({ post, direction, showThumbnail = true }) => { const { theme } = useFireshipTheme(); const themeClasses = exports.getThemeClasses(theme || { primaryColor: "#3b82f6", secondaryColor: "#64748b", fontFamily: "inter", fontSize: "base", showSidebar: true, glassEffect: true, borderRadius: "xl" }); const isPrevious = direction === "previous"; const Icon = isPrevious ? lucideReact.ArrowLeft : lucideReact.ArrowRight; const ChevronIcon = isPrevious ? lucideReact.ChevronLeft : lucideReact.ChevronRight; const handleClick = () => { window.location.href = `/blog/${post.slug}`; }; return /* @__PURE__ */ jsxRuntime.jsxs( "button", { onClick: handleClick, className: `group flex items-center gap-4 p-4 ${themeClasses.radius} bg-zinc-50 dark:bg-zinc-800/50 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-all duration-300 hover:scale-105 hover:shadow-lg max-w-sm ${isPrevious ? "flex-row" : "flex-row-reverse text-right"}`, children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 p-2 ${themeClasses.radius} bg-[var(--fireship-primary)]/10 text-[var(--fireship-primary)] group-hover:bg-[var(--fireship-primary)] group-hover:text-white transition-colors`, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "w-5 h-5" }) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex-1 min-w-0 ${isPrevious ? "text-left" : "text-right"}`, children: [ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-zinc-500 dark:text-zinc-500 mb-1", children: [ isPrevious && /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, { className: "w-3 h-3" }), /* @__PURE__ */ jsxRuntime.jsx("span", { className: "uppercase tracking-wide font-medium", children: isPrevious ? "Previous" : "Next" }), !isPrevious && /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, { className: "w-3 h-3" }) ] }), /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-zinc-900 dark:text-zinc-100 text-sm line-clamp-2 group-hover:text-[var(--fireship-primary)] transition-colors mb-1", children: post.title }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2 py-0.5 ${themeClasses.radius} bg-zinc-200 dark:bg-zinc-700 text-xs font-medium text-zinc-600 dark:text-zinc-400`, children: post.category }), /* @__PURE__ */ jsxRuntime.jsx("time", { className: "text-xs text-zinc-500 dark:text-zinc-500", children: new Date(post.publishedAt).toLocaleDateString("en-US", { month: "short", day: "numeric" }) }) ] }) ] }), showThumbnail && post.featuredImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx( "img", { src: post.featuredImage, alt: post.title, className: `w-16 h-16 object-cover ${themeClasses.radius} group-hover:scale-110 transition-transform` } ) }) ] } ); }; var Naviga