fireship-ai-blog
Version:
A beautiful, SEO-friendly blog package for Next.js 13+ with Fireship.ai API integration
1,196 lines (1,190 loc) • 558 kB
JavaScript
import React3, { useState, useEffect, useCallback } from 'react';
import { jsx, jsxs } from 'react/jsx-runtime';
import { TrendingUp, Clock, ArrowLeft, ArrowRight, ChevronLeft, ChevronRight, User, Calendar, Heart, Share2, Bookmark, Tag, Eye, MessageCircle, Palette, EyeOff, RotateCcw, Loader2, Save, Type, Layout } from 'lucide-react';
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: () => api_default,
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, api_default, __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;
}
};
api_default = 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: () => applyTheme,
defaultTheme: () => defaultTheme,
generateColorVariations: () => generateColorVariations,
getSystemDarkMode: () => getSystemDarkMode,
getThemeClasses: () => getThemeClasses,
hexToRgb: () => hexToRgb,
initializeDarkMode: () => initializeDarkMode,
initializeTheme: () => initializeTheme,
loadThemeFromStorage: () => loadThemeFromStorage,
saveThemeToStorage: () => saveThemeToStorage,
toggleDarkMode: () => toggleDarkMode
});
var defaultTheme, applyTheme, getThemeClasses, hexToRgb, generateColorVariations, loadThemeFromStorage, saveThemeToStorage, initializeTheme, getSystemDarkMode, toggleDarkMode, initializeDarkMode;
var init_theme = __esm({
"utils/theme.ts"() {
defaultTheme = {
primaryColor: "#3b82f6",
// blue-500
secondaryColor: "#64748b",
// slate-500
fontFamily: "inter",
fontSize: "base",
showSidebar: true,
glassEffect: true,
borderRadius: "xl"
};
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");
};
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}`
};
};
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;
};
generateColorVariations = (baseColor) => {
const rgb = 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;
};
loadThemeFromStorage = () => {
if (typeof window === "undefined") return defaultTheme;
try {
const stored = localStorage.getItem("fireship-blog-theme");
if (stored) {
const parsed = JSON.parse(stored);
return { ...defaultTheme, ...parsed };
}
} catch (error) {
console.warn("Failed to load theme from localStorage:", error);
}
return defaultTheme;
};
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);
}
};
initializeTheme = (settings) => {
const theme = settings ? { ...defaultTheme, ...settings } : loadThemeFromStorage();
applyTheme(theme);
return theme;
};
getSystemDarkMode = () => {
if (typeof window === "undefined") return false;
return window.matchMedia("(prefers-color-scheme: dark)").matches;
};
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;
};
initializeDarkMode = () => {
if (typeof window === "undefined") return false;
try {
const stored = localStorage.getItem("fireship-blog-dark-mode");
const shouldBeDark = stored ? stored === "true" : getSystemDarkMode();
return toggleDarkMode(shouldBeDark);
} catch (error) {
console.warn("Failed to initialize dark mode:", error);
return 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] = useState(new URLSearchParams());
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
setSearchParams(new URLSearchParams(window.location.search));
}, []);
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] = useState([]);
const [currentPost, setCurrentPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [isClient, setIsClient] = useState(false);
const blogApiKey = getApiKey(apiKey);
useEffect(() => {
setIsClient(true);
}, []);
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]);
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__ */ jsx("div", { className: `fireship-blog ${className}`, children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ 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__ */ jsx("div", { className: `fireship-blog ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "text-center py-12", children: [
/* @__PURE__ */ jsxs("p", { className: "text-red-500 mb-4", children: [
"Error: ",
error
] }),
/* @__PURE__ */ 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__ */ jsxs("div", { className: `fireship-blog ${className}`, children: [
/* @__PURE__ */ 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__ */ 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__ */ jsx("div", { className: "mb-8 overflow-hidden rounded-xl", children: /* @__PURE__ */ jsx(
"img",
{
src: currentPost.featuredImage,
alt: currentPost.title,
className: "w-full h-64 object-cover"
}
) }),
/* @__PURE__ */ jsxs("header", { className: "mb-8", children: [
/* @__PURE__ */ jsx("h1", { className: "text-4xl font-thin text-zinc-900 dark:text-zinc-100 mb-4 tracking-tight", children: currentPost.title }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-4 text-sm text-zinc-600 dark:text-zinc-400", children: [
currentPost.author?.avatar && /* @__PURE__ */ jsx(
"img",
{
src: currentPost.author.avatar,
alt: currentPost.author.name,
className: "w-8 h-8 rounded-full"
}
),
/* @__PURE__ */ jsx("span", { className: "font-medium", children: currentPost.author?.name || "Anonymous" }),
currentPost.publishedAt && /* @__PURE__ */ jsx("span", { children: "\u2022" }),
currentPost.publishedAt && /* @__PURE__ */ jsx("span", { children: new Date(currentPost.publishedAt).toLocaleDateString() })
] })
] }),
/* @__PURE__ */ jsx("div", { className: "prose prose-zinc dark:prose-invert max-w-none", children: currentPost.content ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: currentPost.content } }) : /* @__PURE__ */ jsx("p", { children: currentPost.excerpt || "No content available." }) })
] })
] });
}
return /* @__PURE__ */ jsxs("div", { className: `fireship-blog ${className}`, children: [
/* @__PURE__ */ jsxs("div", { className: "mb-12 text-center", children: [
/* @__PURE__ */ jsx("h1", { className: "text-5xl font-thin text-zinc-900 dark:text-zinc-100 mb-4 tracking-tight", children: "Blog" }),
/* @__PURE__ */ jsx("p", { className: "text-xl text-zinc-600 dark:text-zinc-400 font-light", children: "Latest insights and updates" })
] }),
/* @__PURE__ */ jsx("div", { className: "grid gap-8 md:grid-cols-2 lg:grid-cols-3", children: posts.map((post) => /* @__PURE__ */ jsx(
"div",
{
className: "group cursor-pointer",
onClick: () => handlePostClick(post),
children: /* @__PURE__ */ 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__ */ jsx("div", { className: "mb-4 overflow-hidden rounded-xl", children: /* @__PURE__ */ jsx(
"img",
{
src: post.featuredImage,
alt: post.title,
className: "w-full h-48 object-cover transition-transform duration-300 group-hover:scale-105"
}
) }),
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
/* @__PURE__ */ 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__ */ jsx("p", { className: "text-zinc-600 dark:text-zinc-400 text-sm line-clamp-3 leading-relaxed", children: post.excerpt }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
post.author?.avatar && /* @__PURE__ */ jsx(
"img",
{
src: post.author.avatar,
alt: post.author.name,
className: "w-6 h-6 rounded-full"
}
),
/* @__PURE__ */ jsx("span", { className: "text-xs text-zinc-500 dark:text-zinc-500 font-medium", children: post.author?.name || "Anonymous" })
] }),
post.publishedAt && /* @__PURE__ */ jsx("span", { className: "text-xs text-zinc-500 dark:text-zinc-500", children: new Date(post.publishedAt).toLocaleDateString() })
] })
] })
] })
},
post.id
)) }),
posts.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-center py-16", children: /* @__PURE__ */ 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__ */ 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] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [hasMore, setHasMore] = useState(true);
const [totalCount, setTotalCount] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const {
category,
tag,
limit = 10,
page = 1,
campaignId
} = options;
const fetchPosts = 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 = useCallback(() => {
if (!loading && hasMore) {
fetchPosts(currentPage + 1, true);
}
}, [loading, hasMore, currentPage, fetchPosts]);
const refresh = useCallback(() => {
setCurrentPage(1);
fetchPosts(1, false);
}, [fetchPosts]);
useEffect(() => {
fetchPosts(page, false);
}, [fetchPosts, page]);
return {
posts,
loading,
error,
hasMore,
loadMore,
refresh,
totalCount
};
};
var useFireshipPost = (slug) => {
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
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] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
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] = useState(() => {
if (typeof window !== "undefined") {
const { loadThemeFromStorage: loadThemeFromStorage2 } = (init_theme(), __toCommonJS(theme_exports));
return loadThemeFromStorage2();
}
return null;
});
const updateTheme = 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);
}
}, []);
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] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchAnalytics = 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]);
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 = getThemeClasses(theme || {
primaryColor: "#3b82f6",
secondaryColor: "#64748b",
fontFamily: "inter",
fontSize: "base",
showSidebar: true,
glassEffect: true,
borderRadius: "xl"
});
if (loading) {
return /* @__PURE__ */ jsxs("aside", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: [
/* @__PURE__ */ jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-6 flex items-center gap-2", children: [
/* @__PURE__ */ jsx(TrendingUp, { className: "w-5 h-5" }),
title
] }),
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: Array.from({ length: 3 }).map((_, index2) => /* @__PURE__ */ jsx(RelatedPostSkeleton, {}, index2)) })
] });
}
if (error || !relatedPosts || !relatedPosts.length) {
return null;
}
return /* @__PURE__ */ jsxs("aside", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: [
/* @__PURE__ */ jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-6 flex items-center gap-2", children: [
/* @__PURE__ */ jsx(TrendingUp, { className: "w-5 h-5 text-[var(--fireship-primary)]" }),
title
] }),
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: relatedPosts.map((post) => /* @__PURE__ */ jsx(RelatedPostItem, { post }, post.id)) }),
/* @__PURE__ */ jsx("div", { className: "mt-6 pt-4 border-t border-zinc-200 dark:border-zinc-800", children: /* @__PURE__ */ 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 = 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__ */ 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__ */ jsxs("div", { className: "flex gap-3", children: [
post.featuredImage && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
"img",
{
src: post.featuredImage,
alt: post.title,
className: `w-16 h-16 object-cover ${themeClasses.radius} group-hover:scale-105 transition-transform`
}
) }),
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
/* @__PURE__ */ jsx("div", { className: "mb-1", children: /* @__PURE__ */ 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__ */ 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__ */ jsx("p", { className: "text-xs text-zinc-600 dark:text-zinc-400 line-clamp-2 mb-2", children: post.excerpt }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-zinc-500 dark:text-zinc-500", children: [
/* @__PURE__ */ jsx("time", { dateTime: post.publishedAt, children: new Date(post.publishedAt).toLocaleDateString("en-US", {
month: "short",
day: "numeric"
}) }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
/* @__PURE__ */ jsx(Clock, { className: "w-3 h-3" }),
/* @__PURE__ */ jsxs("span", { children: [
post.readTime,
" min"
] })
] })
] })
] })
] })
}
);
};
var RelatedPostSkeleton = () => {
const { theme } = useFireshipTheme();
const themeClasses = getThemeClasses(theme || {
primaryColor: "#3b82f6",
secondaryColor: "#64748b",
fontFamily: "inter",
fontSize: "base",
showSidebar: true,
glassEffect: true,
borderRadius: "xl"
});
return /* @__PURE__ */ jsx("div", { className: "animate-pulse", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
/* @__PURE__ */ jsx("div", { className: `w-16 h-16 bg-zinc-200 dark:bg-zinc-800 ${themeClasses.radius} flex-shrink-0` }),
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
/* @__PURE__ */ jsx("div", { className: `h-4 w-16 bg-zinc-200 dark:bg-zinc-800 ${themeClasses.radius} mb-1` }),
/* @__PURE__ */ jsxs("div", { className: "space-y-1 mb-2", children: [
/* @__PURE__ */ jsx("div", { className: "h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-full" }),
/* @__PURE__ */ jsx("div", { className: "h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-3/4" })
] }),
/* @__PURE__ */ jsxs("div", { className: "space-y-1 mb-2", children: [
/* @__PURE__ */ jsx("div", { className: "h-3 bg-zinc-200 dark:bg-zinc-800 rounded w-full" }),
/* @__PURE__ */ jsx("div", { className: "h-3 bg-zinc-200 dark:bg-zinc-800 rounded w-2/3" })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
/* @__PURE__ */ jsx("div", { className: "h-3 bg-zinc-200 dark:bg-zinc-800 rounded w-12" }),
/* @__PURE__ */ 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 = getThemeClasses(theme || {
primaryColor: "#3b82f6",
secondaryColor: "#64748b",
fontFamily: "inter",
fontSize: "base",
showSidebar: true,
glassEffect: true,
borderRadius: "xl"
});
return /* @__PURE__ */ jsxs("aside", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: [
/* @__PURE__ */ jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-6 flex items-center gap-2", children: [
/* @__PURE__ */ jsx(TrendingUp, { className: "w-5 h-5 text-[var(--fireship-primary)]" }),
title
] }),
/* @__PURE__ */ jsx("div", { className: "flex gap-1 mb-6", children: ["day", "week", "month", "year"].map((period) => /* @__PURE__ */ 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__ */ jsx("div", { className: "space-y-4", children: Array.from({ length: limit }).map((_, index2) => /* @__PURE__ */ jsx(RelatedPostSkeleton, {}, index2)) })
] });
};
init_theme();
var NextPostNavigation = ({
currentPostId,
className = "",
showThumbnails = true
}) => {
const [previousPost, setPreviousPost] = useState(null);
const [nextPost, setNextPost] = useState(null);
const [loading, setLoading] = useState(true);
const { theme } = useFireshipTheme();
const themeClasses = getThemeClasses(theme || {
primaryColor: "#3b82f6",
secondaryColor: "#64748b",
fontFamily: "inter",
fontSize: "base",
showSidebar: true,
glassEffect: true,
borderRadius: "xl"
});
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__ */ jsx("nav", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsx(NavigationItemSkeleton, {}),
/* @__PURE__ */ jsx(NavigationItemSkeleton, {})
] }) });
}
if (!previousPost && !nextPost) {
return null;
}
return /* @__PURE__ */ jsx("nav", { className: `${themeClasses.glass} ${themeClasses.radius} p-6 shadow-lg ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [
/* @__PURE__ */ jsx("div", { className: "flex justify-start", children: previousPost ? /* @__PURE__ */ jsx(
NavigationItem,
{
post: previousPost,
direction: "previous",
showThumbnail: showThumbnails
}
) : /* @__PURE__ */ jsx("div", {}) }),
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: nextPost ? /* @__PURE__ */ jsx(
NavigationItem,
{
post: nextPost,
direction: "next",
showThumbnail: showThumbnails
}
) : /* @__PURE__ */ jsx("div", {}) })
] }) });
};
var NavigationItem = ({
post,
direction,
showThumbnail = true
}) => {
const { theme } = useFireshipTheme();
const themeClasses = getThemeClasses(theme || {
primaryColor: "#3b82f6",
secondaryColor: "#64748b",
fontFamily: "inter",
fontSize: "base",
showSidebar: true,
glassEffect: true,
borderRadius: "xl"
});
const isPrevious = direction === "previous";
const Icon = isPrevious ? ArrowLeft : ArrowRight;
const ChevronIcon = isPrevious ? ChevronLeft : ChevronRight;
const handleClick = () => {
window.location.href = `/blog/${post.slug}`;
};
return /* @__PURE__ */ 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__ */ 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__ */ jsx(Icon, { className: "w-5 h-5" }) }),
/* @__PURE__ */ jsxs("div", { className: `flex-1 min-w-0 ${isPrevious ? "text-left" : "text-right"}`, children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-zinc-500 dark:text-zinc-500 mb-1", children: [
isPrevious && /* @__PURE__ */ jsx(ChevronIcon, { className: "w-3 h-3" }),
/* @__PURE__ */ jsx("span", { className: "uppercase tracking-wide font-medium", children: isPrevious ? "Previous" : "Next" }),
!isPrevious && /* @__PURE__ */ jsx(ChevronIcon, { className: "w-3 h-3" })
] }),
/* @__PURE__ */ 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__ */ jsxs("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ 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__ */ 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__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
"img",
{
src: post.featuredImage,
alt: post.title,
className: `w-16 h-16 object-cover ${themeClasses.radius} group-hover:scale-110 transition-transform`
}
) })
]
}
);
};
var NavigationItemSkeleton = () => {
const { theme } = useFireshipTheme();
const themeClasses = getThemeClasses(theme || {
primaryColor: "#3b82f6",
secondaryColor: "#64748b",
fontFamily: "inter",
fontSize: "base",
showSidebar: true,
glassEffect: true,
borderRadius: "xl"
});
return /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-4 p-4 ${themeClasses.radius} bg-zinc-50 dark:bg-zinc-800/50 max-w-sm animate-pulse`, children: [
/* @__PURE__ */ jsx("div", { className: `w-9 h-9 bg-zinc-200 dark:bg-zinc-700 ${themeClasses.radius} flex-shrink-0` }),
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
/* @__PURE__ */ jsx("div", { className: "h-3 bg-zinc-200 dark:bg-zinc-700 rounded w-16 mb-1" }),
/* @__PURE__ */ jsxs("div", { className: "space-y-1 mb-1", children: [
/* @__PURE__ */ jsx("div", { className: "h-4 bg-zinc-200 dark:bg-zinc-700 rounded w-full" }),
/* @__PURE__ */ jsx("div", { className: "h-4 bg-zinc-200 dark:bg-zinc-700 rounded w-3/4" })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsx("div", { className: "h-4 bg-zinc-200 dark:bg-zinc-700 rounded w-12" }),
/* @__PURE__ */ jsx("div", { className: "h-4 bg-zinc-200 dark:bg-zinc-700 rounded w-16" })
] })
] }),
/* @__PURE__ */ jsx("div", { className: `w-16 h-16 bg-zinc-200 dark:bg-zinc-700 ${themeClasses.radius} flex-shrink-0` })
] });
};
var SimplePostNavigation = ({
currentPostId,
className = ""
}) => {
const [previousPost, setPreviousPost] = useState(null);
const [nextPost, setNextPost] = useState(null);
const [loading, setLoading] = useState(true);
const { theme } = useFireshipTheme();
const themeClasses = getThemeClasses(theme || {
primaryColor: "#3b82f6",
secondaryColor: "#64748b",
fontFamily: "inter",
fontSize: "base",
showSidebar: true,
glassEffect: true,
borderRadius: "xl"
});
useEffect(() => {
const fetchNavigationPosts = async () => {
try {
setLoading(true);
const baseUrl = t