diff --git a/frontend-prep/src/api/api.ts b/frontend-prep/src/api/api.ts index be7c0ff7..7d2ac84c 100644 --- a/frontend-prep/src/api/api.ts +++ b/frontend-prep/src/api/api.ts @@ -1863,6 +1863,11 @@ export interface PublicSettings { shop_name: string; two_fa_enabled: boolean; contact_telegram: string; + client_color_primary: string; + client_color_secondary: string; + client_color_success: string; + client_color_danger: string; + client_color_warning: string; } export const getPublicSettings = async (): Promise => { @@ -1880,6 +1885,11 @@ export const getPublicSettings = async (): Promise => { shop_name: "Milieu-Nantais", two_fa_enabled: false, contact_telegram: "", + client_color_primary: "#8b5cf6", + client_color_secondary: "#22d3ee", + client_color_success: "#22c55e", + client_color_danger: "#ef4444", + client_color_warning: "#f59e0b", }; try { const response = await fetch(`${API_URL}/app-settings`); @@ -1904,6 +1914,11 @@ export const getPublicSettings = async (): Promise => { shop_name: data.shop_name || "Milieu-Nantais", two_fa_enabled: data.two_fa_enabled ?? false, contact_telegram: data.contact_telegram || "", + client_color_primary: data.client_color_primary || "#8b5cf6", + client_color_secondary: data.client_color_secondary || "#22d3ee", + client_color_success: data.client_color_success || "#22c55e", + client_color_danger: data.client_color_danger || "#ef4444", + client_color_warning: data.client_color_warning || "#f59e0b", }; } catch { return defaults; diff --git a/frontend-prep/src/context/ThemeContext.tsx b/frontend-prep/src/context/ThemeContext.tsx index f11edfae..2455cdbc 100644 --- a/frontend-prep/src/context/ThemeContext.tsx +++ b/frontend-prep/src/context/ThemeContext.tsx @@ -1,5 +1,6 @@ import { createContext, useContext, useEffect, useState } from "react"; import type { ReactNode } from "react"; +import { getPublicSettings } from "../api/api"; type Theme = "dark" | "light"; @@ -13,6 +14,30 @@ const ThemeContext = createContext({ toggleTheme: () => {}, }); +function hexToRgba(hex: string, alpha: number): string { + const h = hex.replace("#", ""); + const r = parseInt(h.substring(0, 2), 16); + const g = parseInt(h.substring(2, 4), 16); + const b = parseInt(h.substring(4, 6), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +} + +function applyClientColors(s: { client_color_primary: string; client_color_secondary: string; client_color_success: string; client_color_danger: string; client_color_warning: string }) { + const root = document.documentElement; + if (s.client_color_primary) { + root.style.setProperty("--primary", s.client_color_primary); + root.style.setProperty("--primary-soft", hexToRgba(s.client_color_primary, 0.12)); + root.style.setProperty("--primary-glow", hexToRgba(s.client_color_primary, 0.25)); + } + if (s.client_color_secondary) { + root.style.setProperty("--cyan", s.client_color_secondary); + root.style.setProperty("--cyan-soft", hexToRgba(s.client_color_secondary, 0.1)); + } + if (s.client_color_danger) { + root.style.setProperty("--red", s.client_color_danger); + } +} + export function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setTheme] = useState(() => { const stored = localStorage.getItem("theme"); @@ -25,6 +50,21 @@ export function ThemeProvider({ children }: { children: ReactNode }) { localStorage.setItem("theme", theme); }, [theme]); + useEffect(() => { + const fetchColors = async () => { + try { + const settings = await getPublicSettings(); + applyClientColors(settings); + } catch {} + }; + fetchColors(); + const onVisibility = () => { + if (document.visibilityState === "visible") fetchColors(); + }; + document.addEventListener("visibilitychange", onVisibility); + return () => document.removeEventListener("visibilitychange", onVisibility); + }, []); + const toggleTheme = () => { setTheme((prev) => (prev === "dark" ? "light" : "dark")); };