From 343f814569a6dd2c34c3747bad22798f1d235adc Mon Sep 17 00:00:00 2001 From: Hannah_Bagga Date: Fri, 26 Jun 2026 11:21:10 +0200 Subject: [PATCH] npm --- src/app/api/settings/website-theme/route.ts | 41 +++++++++ src/app/layout.tsx | 7 +- src/components/settings/theme-settings.tsx | 77 ++++++++++++++++- src/providers/website-theme-provider.tsx | 92 +++++++++++++++++++++ 4 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 src/app/api/settings/website-theme/route.ts create mode 100644 src/providers/website-theme-provider.tsx diff --git a/src/app/api/settings/website-theme/route.ts b/src/app/api/settings/website-theme/route.ts new file mode 100644 index 0000000..4996f7d --- /dev/null +++ b/src/app/api/settings/website-theme/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from "next/server" +import { getSessionUser } from "@/lib/auth" +import { query } from "@/lib/db" + +export async function GET() { + try { + const user = await getSessionUser() + if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + + const result = await query( + `SELECT preferences->>'website_theme' AS website_theme FROM users WHERE id = $1`, + [user.id], + ) + + const websiteTheme = result.rows[0]?.website_theme || "spidey" + return NextResponse.json({ websiteTheme }) + } catch (error) { + console.error("Website theme GET error:", error) + return NextResponse.json({ error: "Failed to load website theme" }, { status: 500 }) + } +} + +export async function PUT(request: NextRequest) { + try { + const user = await getSessionUser() + if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + + const body = await request.json() + const theme = body.websiteTheme || "spidey" + + await query( + `UPDATE users SET preferences = preferences || $2::jsonb WHERE id = $1`, + [user.id, JSON.stringify({ website_theme: theme })], + ) + + return NextResponse.json({ success: true, websiteTheme: theme }) + } catch (error) { + console.error("Website theme PUT error:", error) + return NextResponse.json({ error: "Failed to save website theme" }, { status: 500 }) + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 08e274e..a64d357 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata, Viewport } from "next" import { Inter } from "next/font/google" import { ThemeProvider } from "@/providers/theme-provider" +import { WebsiteThemeProvider } from "@/providers/website-theme-provider" import { Toaster } from "@/components/ui/sonner" import "./globals.css" @@ -31,8 +32,10 @@ export default function RootLayout({ - {children} - + + {children} + + diff --git a/src/components/settings/theme-settings.tsx b/src/components/settings/theme-settings.tsx index 3879277..22ceb70 100644 --- a/src/components/settings/theme-settings.tsx +++ b/src/components/settings/theme-settings.tsx @@ -6,7 +6,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Label } from "@/components/ui/label" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { cn } from "@/lib/utils" -import { Sun, Moon, Monitor } from "lucide-react" +import { useWebsiteTheme } from "@/providers/website-theme-provider" +import { Sun, Moon, Monitor, Check } from "lucide-react" const COLOR_THEME_KEY = "crm-color-theme" @@ -44,8 +45,17 @@ function applyColorTheme(theme: string) { localStorage.setItem(COLOR_THEME_KEY, theme) } +const websiteThemes = [ + { + id: "spidey", + name: "Spidey", + description: "Current website theme", + }, +] + export function ThemeSettings() { const { theme, setTheme } = useTheme() + const { websiteTheme, setWebsiteTheme } = useWebsiteTheme() const [colorTheme, setColorTheme] = useState("default") const [mounted, setMounted] = useState(false) @@ -119,6 +129,71 @@ export function ThemeSettings() { + + + + Website Themes + + Select your website's visual theme. + + + +
+ {websiteThemes.map((wt) => { + const isActive = websiteTheme === wt.id + return ( + + ) + })} +
+
+
) } diff --git a/src/providers/website-theme-provider.tsx b/src/providers/website-theme-provider.tsx new file mode 100644 index 0000000..f35f7c8 --- /dev/null +++ b/src/providers/website-theme-provider.tsx @@ -0,0 +1,92 @@ +"use client" + +import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from "react" + +const WEBSITE_THEME_KEY = "crm-website-theme" + +const themeClasses: Record = { + spidey: "", +} + +interface WebsiteThemeContextValue { + websiteTheme: string + setWebsiteTheme: (theme: string) => Promise + loading: boolean +} + +const WebsiteThemeContext = createContext(null) + +function applyWebsiteTheme(theme: string) { + const prefix = "theme-" + const root = document.documentElement + const classes = root.className.split(" ").filter((c) => !c.startsWith(prefix)) + const cls = themeClasses[theme] + if (cls) { + classes.push(cls) + } + root.className = classes.join(" ").trim() +} + +function getStoredTheme(): string | null { + if (typeof window === "undefined") return null + return localStorage.getItem(WEBSITE_THEME_KEY) +} + +function storeTheme(theme: string) { + if (typeof window === "undefined") return + localStorage.setItem(WEBSITE_THEME_KEY, theme) +} + +export function WebsiteThemeProvider({ children }: { children: ReactNode }) { + const [websiteTheme, setWebsiteThemeState] = useState("spidey") + const [loading, setLoading] = useState(true) + + useEffect(() => { + const stored = getStoredTheme() + if (stored) { + setWebsiteThemeState(stored) + applyWebsiteTheme(stored) + setLoading(false) + return + } + fetch("/api/settings/website-theme") + .then((res) => (res.ok ? res.json() : null)) + .then((data) => { + const theme = data?.websiteTheme || "spidey" + setWebsiteThemeState(theme) + storeTheme(theme) + applyWebsiteTheme(theme) + }) + .catch(() => { + applyWebsiteTheme("spidey") + }) + .finally(() => setLoading(false)) + }, []) + + const setWebsiteTheme = useCallback(async (theme: string) => { + setWebsiteThemeState(theme) + storeTheme(theme) + applyWebsiteTheme(theme) + try { + await fetch("/api/settings/website-theme", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ websiteTheme: theme }), + }) + } catch { + console.warn("Failed to persist website theme") + } + }, []) + + return ( + + {children} + + ) +} + +export function useWebsiteTheme() { + const ctx = useContext(WebsiteThemeContext) + if (!ctx) throw new Error("useWebsiteTheme must be used within WebsiteThemeProvider") + return ctx +}