feat(branding): add global dynamic theme system and fix 2fa mail
This commit is contained in:
111
shop/components/ThemeProvider.tsx
Normal file
111
shop/components/ThemeProvider.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState, createContext, useContext } from 'react'
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { getBrandingSettings } from '@/lib/actions/branding'
|
||||
import { PRESET_COLOR_SCHEMES, BrandingSettings } from '@/lib/constants/branding'
|
||||
|
||||
function hexToHsl(hex: string): string {
|
||||
try {
|
||||
let c = hex.replace('#', '')
|
||||
if (c.length === 3) c = c.split('').map(x => x + x).join('')
|
||||
const r = parseInt(c.substring(0, 2), 16) / 255
|
||||
const g = parseInt(c.substring(2, 4), 16) / 255
|
||||
const b = parseInt(c.substring(4, 6), 16) / 255
|
||||
|
||||
const max = Math.max(r, g, b)
|
||||
const min = Math.min(r, g, b)
|
||||
let h = 0, s = 0, l = (max + min) / 2
|
||||
|
||||
if (max !== min) {
|
||||
const d = max - min
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
||||
switch (max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break
|
||||
case g: h = (b - r) / d + 2; break
|
||||
case b: h = (r - g) / d + 4; break
|
||||
}
|
||||
h /= 6
|
||||
}
|
||||
|
||||
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`
|
||||
} catch (e) {
|
||||
return '217 91% 60%'
|
||||
}
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<{
|
||||
branding: BrandingSettings | null
|
||||
refreshBranding: () => Promise<void>
|
||||
}>({
|
||||
branding: null,
|
||||
refreshBranding: async () => {},
|
||||
})
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
[key: string]: any
|
||||
}) {
|
||||
const [branding, setBranding] = useState<BrandingSettings | null>(null)
|
||||
|
||||
const loadTheme = async () => {
|
||||
try {
|
||||
const settings = await getBrandingSettings()
|
||||
setBranding(settings)
|
||||
|
||||
// Preset Ermittlung
|
||||
const preset = PRESET_COLOR_SCHEMES.find(p => p.id === settings.colorScheme) || PRESET_COLOR_SCHEMES[3]
|
||||
const isCustom = settings.colorScheme === 'custom'
|
||||
|
||||
const primary = isCustom ? (settings.primaryColor || '#2563eb') : preset.primary
|
||||
const accent = isCustom ? (settings.accentColor || '#38bdf8') : preset.accent
|
||||
|
||||
const primaryHsl = hexToHsl(primary)
|
||||
const accentHsl = hexToHsl(accent)
|
||||
|
||||
const root = document.documentElement
|
||||
|
||||
// 10 Color CSS Tokens applied globally onto :root
|
||||
root.style.setProperty('--primary', primaryHsl)
|
||||
root.style.setProperty('--ring', primaryHsl)
|
||||
root.style.setProperty('--accent', accentHsl)
|
||||
|
||||
root.style.setProperty('--primary-custom', primary)
|
||||
root.style.setProperty('--accent-custom', accent)
|
||||
|
||||
root.style.setProperty('--success-custom', settings.successColor || preset.success)
|
||||
root.style.setProperty('--warning-custom', settings.warningColor || preset.warning)
|
||||
root.style.setProperty('--destructive-custom', settings.destructiveColor || preset.destructive)
|
||||
|
||||
root.style.setProperty('--bg-glow-1', isCustom ? primary : preset.bgGlow1)
|
||||
root.style.setProperty('--bg-glow-2', isCustom ? accent : preset.bgGlow2)
|
||||
root.style.setProperty('--gradient-from', isCustom ? primary : preset.gradientFrom)
|
||||
root.style.setProperty('--gradient-to', isCustom ? accent : preset.gradientTo)
|
||||
root.style.setProperty('--card-border-glow', isCustom ? `${accent}40` : preset.cardBorder)
|
||||
root.style.setProperty('--text-highlight', isCustom ? accent : preset.textHighlight)
|
||||
root.style.setProperty('--button-bg', isCustom ? primary : preset.buttonBg)
|
||||
root.style.setProperty('--ring-color', isCustom ? primary : preset.ringColor)
|
||||
} catch (e) {
|
||||
console.error('Failed to load branding theme:', e)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadTheme()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<NextThemesProvider {...props}>
|
||||
<ThemeContext.Provider value={{ branding, refreshBranding: loadTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
</NextThemesProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
return useContext(ThemeContext)
|
||||
}
|
||||
Reference in New Issue
Block a user