colour scheme problem fixed
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
|||||||
setSessionContext,
|
setSessionContext,
|
||||||
SESSION_COOKIE,
|
SESSION_COOKIE,
|
||||||
} from "@/lib/auth"
|
} from "@/lib/auth"
|
||||||
import { query } from "@/lib/db"
|
|
||||||
|
|
||||||
function jsonResponse(data: unknown, status: number) {
|
function jsonResponse(data: unknown, status: number) {
|
||||||
return new Response(JSON.stringify(data), {
|
return new Response(JSON.stringify(data), {
|
||||||
@@ -115,11 +115,6 @@ export async function POST(request: NextRequest) {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
await query(
|
|
||||||
`UPDATE users SET preferences = preferences || $2::jsonb WHERE id = $1`,
|
|
||||||
[dbUser.id, JSON.stringify({ website_theme: "default" })],
|
|
||||||
)
|
|
||||||
|
|
||||||
const token = await createSession(dbUser.id, dbUser.role_name)
|
const token = await createSession(dbUser.id, dbUser.role_name)
|
||||||
await setSessionContext(dbUser.id, ipAddress)
|
await setSessionContext(dbUser.id, ipAddress)
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ export async function GET() {
|
|||||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||||
|
|
||||||
const result = await query(
|
const result = await query(
|
||||||
`SELECT preferences->>'website_theme' AS website_theme FROM users WHERE id = $1`,
|
`SELECT preferences->>'website_theme' AS website_theme, preferences->>'color_theme' AS color_theme FROM users WHERE id = $1`,
|
||||||
[user.id],
|
[user.id],
|
||||||
)
|
)
|
||||||
|
|
||||||
const websiteTheme = result.rows[0]?.website_theme || "default"
|
const websiteTheme = result.rows[0]?.website_theme || "default"
|
||||||
return NextResponse.json({ websiteTheme })
|
const colorTheme = result.rows[0]?.color_theme || null
|
||||||
|
return NextResponse.json({ websiteTheme, colorTheme })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Website theme GET error:", error)
|
console.error("Website theme GET error:", error)
|
||||||
return NextResponse.json({ error: "Failed to load website theme" }, { status: 500 })
|
return NextResponse.json({ error: "Failed to load website theme" }, { status: 500 })
|
||||||
@@ -26,14 +27,31 @@ export async function PUT(request: NextRequest) {
|
|||||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
|
||||||
|
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const theme = body.websiteTheme || "default"
|
const update: Record<string, string> = {}
|
||||||
|
if (body.websiteTheme !== undefined) {
|
||||||
|
update.website_theme = body.websiteTheme || "default"
|
||||||
|
}
|
||||||
|
if (body.colorTheme !== undefined) {
|
||||||
|
update.color_theme = body.colorTheme || "default"
|
||||||
|
}
|
||||||
|
|
||||||
await query(
|
if (Object.keys(update).length > 0) {
|
||||||
`UPDATE users SET preferences = preferences || $2::jsonb WHERE id = $1`,
|
await query(
|
||||||
[user.id, JSON.stringify({ website_theme: theme })],
|
`UPDATE users SET preferences = preferences || $2::jsonb WHERE id = $1`,
|
||||||
|
[user.id, JSON.stringify(update)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query(
|
||||||
|
`SELECT preferences->>'website_theme' AS website_theme, preferences->>'color_theme' AS color_theme FROM users WHERE id = $1`,
|
||||||
|
[user.id],
|
||||||
)
|
)
|
||||||
|
|
||||||
return NextResponse.json({ success: true, websiteTheme: theme })
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
websiteTheme: result.rows[0]?.website_theme || "default",
|
||||||
|
colorTheme: result.rows[0]?.color_theme || null,
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Website theme PUT error:", error)
|
console.error("Website theme PUT error:", error)
|
||||||
return NextResponse.json({ error: "Failed to save website theme" }, { status: 500 })
|
return NextResponse.json({ error: "Failed to save website theme" }, { status: 500 })
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className={`${inter.variable} min-h-screen antialiased`}>
|
<body className={`${inter.variable} min-h-screen antialiased`}>
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `(function(){try{var t=localStorage.getItem("crm-color-theme");if(t&&t!=="default")document.documentElement.classList.add(t);var w=localStorage.getItem("crm-website-theme");if(w==="spidey")document.documentElement.classList.add("theme-spidey")}catch(e){}})()`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<ThemeProvider attribute="class" defaultTheme="light" disableTransitionOnChange>
|
<ThemeProvider attribute="class" defaultTheme="light" disableTransitionOnChange>
|
||||||
<WebsiteThemeProvider>
|
<WebsiteThemeProvider>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import { cn } from "@/lib/utils"
|
|||||||
import { Sun, Moon, Monitor, Palette, Shield } from "lucide-react"
|
import { Sun, Moon, Monitor, Palette, Shield } from "lucide-react"
|
||||||
import { useWebsiteTheme } from "@/providers/website-theme-provider"
|
import { useWebsiteTheme } from "@/providers/website-theme-provider"
|
||||||
|
|
||||||
const COLOR_THEME_KEY = "crm-color-theme"
|
|
||||||
|
|
||||||
const modeOptions = [
|
const modeOptions = [
|
||||||
{ value: "light", icon: Sun, label: "Light" },
|
{ value: "light", icon: Sun, label: "Light" },
|
||||||
{ value: "dark", icon: Moon, label: "Dark" },
|
{ value: "dark", icon: Moon, label: "Dark" },
|
||||||
@@ -35,38 +33,15 @@ const backgroundOptions = [
|
|||||||
{ value: "spidey", label: "Spidey", icon: Shield, color: "bg-red-600", ring: "ring-red-600", desc: "Dark theme with red accents" },
|
{ value: "spidey", label: "Spidey", icon: Shield, color: "bg-red-600", ring: "ring-red-600", desc: "Dark theme with red accents" },
|
||||||
]
|
]
|
||||||
|
|
||||||
function getStoredColorTheme(): string {
|
|
||||||
if (typeof window === "undefined") return "default"
|
|
||||||
return localStorage.getItem(COLOR_THEME_KEY) || "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyColorTheme(theme: string) {
|
|
||||||
const colorThemes = ["default","ocean","forest","sunset","midnight","rose","amber","violet","slate","ruby"]
|
|
||||||
const classes = document.documentElement.className.split(" ").filter(c => !colorThemes.includes(c))
|
|
||||||
if (theme !== "default") {
|
|
||||||
classes.push(theme)
|
|
||||||
}
|
|
||||||
document.documentElement.className = classes.join(" ").trim()
|
|
||||||
localStorage.setItem(COLOR_THEME_KEY, theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThemeSettings() {
|
export function ThemeSettings() {
|
||||||
const { theme, setTheme } = useTheme()
|
const { theme, setTheme } = useTheme()
|
||||||
const { websiteTheme, setWebsiteTheme } = useWebsiteTheme()
|
const { websiteTheme, setWebsiteTheme, colorTheme, setColorTheme } = useWebsiteTheme()
|
||||||
const [colorTheme, setColorTheme] = useState("default")
|
|
||||||
const [mounted, setMounted] = useState(false)
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true)
|
||||||
setColorTheme(getStoredColorTheme())
|
|
||||||
applyColorTheme(getStoredColorTheme())
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function handleColorChange(value: string) {
|
|
||||||
setColorTheme(value)
|
|
||||||
applyColorTheme(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mounted) return null
|
if (!mounted) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -107,7 +82,7 @@ export function ThemeSettings() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<RadioGroup value={colorTheme} onValueChange={handleColorChange} className="grid grid-cols-5 gap-4">
|
<RadioGroup value={colorTheme} onValueChange={setColorTheme} className="grid grid-cols-5 gap-4">
|
||||||
{themeOptions.map(({ value, label, color, ring }) => (
|
{themeOptions.map(({ value, label, color, ring }) => (
|
||||||
<div key={value}>
|
<div key={value}>
|
||||||
<RadioGroupItem value={value} id={`color-${value}`} className="peer sr-only" />
|
<RadioGroupItem value={value} id={`color-${value}`} className="peer sr-only" />
|
||||||
|
|||||||
@@ -3,14 +3,19 @@
|
|||||||
import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from "react"
|
import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from "react"
|
||||||
|
|
||||||
const WEBSITE_THEME_KEY = "crm-website-theme"
|
const WEBSITE_THEME_KEY = "crm-website-theme"
|
||||||
|
const COLOR_THEME_KEY = "crm-color-theme"
|
||||||
|
|
||||||
const themeClasses: Record<string, string> = {
|
const themeClasses: Record<string, string> = {
|
||||||
spidey: "theme-spidey",
|
spidey: "theme-spidey",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const colorThemes = ["default","ocean","forest","sunset","midnight","rose","amber","violet","slate","ruby"]
|
||||||
|
|
||||||
interface WebsiteThemeContextValue {
|
interface WebsiteThemeContextValue {
|
||||||
websiteTheme: string
|
websiteTheme: string
|
||||||
setWebsiteTheme: (theme: string) => Promise<void>
|
setWebsiteTheme: (theme: string) => Promise<void>
|
||||||
|
colorTheme: string
|
||||||
|
setColorTheme: (theme: string) => Promise<void>
|
||||||
loading: boolean
|
loading: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,9 +32,19 @@ function applyWebsiteTheme(theme: string) {
|
|||||||
root.className = classes.join(" ").trim()
|
root.className = classes.join(" ").trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStoredTheme(): string | null {
|
function applyColorTheme(theme: string) {
|
||||||
|
const root = document.documentElement
|
||||||
|
const classes = root.className.split(" ").filter((c) => !colorThemes.includes(c))
|
||||||
|
if (theme !== "default") {
|
||||||
|
classes.push(theme)
|
||||||
|
}
|
||||||
|
root.className = classes.join(" ").trim()
|
||||||
|
localStorage.setItem(COLOR_THEME_KEY, theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStored(key: string): string | null {
|
||||||
if (typeof window === "undefined") return null
|
if (typeof window === "undefined") return null
|
||||||
return localStorage.getItem(WEBSITE_THEME_KEY)
|
return localStorage.getItem(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeTheme(theme: string) {
|
function storeTheme(theme: string) {
|
||||||
@@ -39,22 +54,40 @@ function storeTheme(theme: string) {
|
|||||||
|
|
||||||
export function WebsiteThemeProvider({ children }: { children: ReactNode }) {
|
export function WebsiteThemeProvider({ children }: { children: ReactNode }) {
|
||||||
const [websiteTheme, setWebsiteThemeState] = useState<string>("default")
|
const [websiteTheme, setWebsiteThemeState] = useState<string>("default")
|
||||||
|
const [colorTheme, setColorThemeState] = useState<string>("default")
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("/api/settings/website-theme")
|
fetch("/api/settings/website-theme")
|
||||||
.then((res) => (res.ok ? res.json() : null))
|
.then((res) => (res.ok ? res.json() : null))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
const theme = data?.websiteTheme || "default"
|
const wt = data?.websiteTheme || "default"
|
||||||
setWebsiteThemeState(theme)
|
setWebsiteThemeState(wt)
|
||||||
storeTheme(theme)
|
storeTheme(wt)
|
||||||
applyWebsiteTheme(theme)
|
applyWebsiteTheme(wt)
|
||||||
|
|
||||||
|
if (data?.colorTheme) {
|
||||||
|
setColorThemeState(data.colorTheme)
|
||||||
|
applyColorTheme(data.colorTheme)
|
||||||
|
} else {
|
||||||
|
const stored = getStored(COLOR_THEME_KEY)
|
||||||
|
if (stored) {
|
||||||
|
setColorThemeState(stored)
|
||||||
|
applyColorTheme(stored)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
const stored = getStoredTheme()
|
const stored = getStored(WEBSITE_THEME_KEY)
|
||||||
const theme = stored || "default"
|
const theme = stored || "default"
|
||||||
setWebsiteThemeState(theme)
|
setWebsiteThemeState(theme)
|
||||||
applyWebsiteTheme(theme)
|
applyWebsiteTheme(theme)
|
||||||
|
|
||||||
|
const storedColor = getStored(COLOR_THEME_KEY)
|
||||||
|
if (storedColor) {
|
||||||
|
setColorThemeState(storedColor)
|
||||||
|
applyColorTheme(storedColor)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
}, [])
|
}, [])
|
||||||
@@ -74,8 +107,22 @@ export function WebsiteThemeProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const setColorTheme = useCallback(async (theme: string) => {
|
||||||
|
setColorThemeState(theme)
|
||||||
|
applyColorTheme(theme)
|
||||||
|
try {
|
||||||
|
await fetch("/api/settings/website-theme", {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ colorTheme: theme }),
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
console.warn("Failed to persist color theme")
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WebsiteThemeContext.Provider value={{ websiteTheme, setWebsiteTheme, loading }}>
|
<WebsiteThemeContext.Provider value={{ websiteTheme, setWebsiteTheme, colorTheme, setColorTheme, loading }}>
|
||||||
{children}
|
{children}
|
||||||
</WebsiteThemeContext.Provider>
|
</WebsiteThemeContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user