From 1d0e396776a30876c9cded0423c0c1ac8f37f1be Mon Sep 17 00:00:00 2001
From: Xor290
Date: Fri, 19 Jun 2026 19:42:37 +0200
Subject: [PATCH] chore: build
---
frontend-admin/src/api/api_admin.ts | 10 +++
frontend-admin/src/context/ThemeContext.tsx | 43 +++++++++++-
.../src/screens/admin/SettingsScreen.tsx | 70 +++++++++++++++++++
frontend-admin/src/theme/colors.ts | 2 +
.../src/pages/User/ConsultationHistorique.tsx | 2 +-
mobile/src/api/api.ts | 15 ++++
mobile/src/context/ThemeContext.tsx | 40 ++++++++++-
mobile/src/theme/colors.ts | 2 +
8 files changed, 177 insertions(+), 7 deletions(-)
diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts
index edad1337..bafe97d9 100644
--- a/frontend-admin/src/api/api_admin.ts
+++ b/frontend-admin/src/api/api_admin.ts
@@ -1066,6 +1066,16 @@ export interface AppSettings {
delivery_mode: DeliveryModeConfig;
shop_name: string;
contact_telegram: string;
+ admin_color_primary: string;
+ admin_color_secondary: string;
+ admin_color_success: string;
+ admin_color_danger: string;
+ admin_color_warning: string;
+ client_color_primary: string;
+ client_color_secondary: string;
+ client_color_success: string;
+ client_color_danger: string;
+ client_color_warning: string;
}
export const getSettings = async (): Promise<{
diff --git a/frontend-admin/src/context/ThemeContext.tsx b/frontend-admin/src/context/ThemeContext.tsx
index 18db59fe..fbc07d98 100644
--- a/frontend-admin/src/context/ThemeContext.tsx
+++ b/frontend-admin/src/context/ThemeContext.tsx
@@ -1,6 +1,8 @@
-import React, { createContext, useContext, useState, useEffect, type ReactNode } from "react";
+import React, { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react";
+import { AppState } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { darkColors, lightColors, type Colors } from "../theme/colors";
+import { getSettings } from "../api/api_admin";
type ThemeMode = "dark" | "light";
@@ -12,16 +14,48 @@ interface ThemeContextType {
}
const STORAGE_KEY = "@theme_mode";
+const COLORS_CACHE_KEY = "@admin_colors_cache";
const ThemeContext = createContext(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [mode, setMode] = useState("dark");
+ const [colorOverrides, setColorOverrides] = useState>({});
+
+ const fetchAdminColors = useCallback(async () => {
+ try {
+ const result = await getSettings();
+ if (result.success && result.settings) {
+ const s = result.settings;
+ const overrides: Partial = {};
+ if (s.admin_color_primary) overrides.accent = s.admin_color_primary;
+ if (s.admin_color_secondary) overrides.secondary = s.admin_color_secondary;
+ if (s.admin_color_success) overrides.success = s.admin_color_success;
+ if (s.admin_color_danger) overrides.danger = s.admin_color_danger;
+ if (s.admin_color_warning) overrides.warning = s.admin_color_warning;
+ setColorOverrides(overrides);
+ AsyncStorage.setItem(COLORS_CACHE_KEY, JSON.stringify(overrides)).catch(() => {});
+ }
+ } catch {}
+ }, []);
useEffect(() => {
AsyncStorage.getItem(STORAGE_KEY).then((val) => {
if (val === "light" || val === "dark") setMode(val);
});
- }, []);
+ AsyncStorage.getItem(COLORS_CACHE_KEY).then((val) => {
+ if (val) {
+ try { setColorOverrides(JSON.parse(val)); } catch {}
+ }
+ });
+ fetchAdminColors();
+ }, [fetchAdminColors]);
+
+ useEffect(() => {
+ const sub = AppState.addEventListener("change", (state) => {
+ if (state === "active") fetchAdminColors();
+ });
+ return () => sub.remove();
+ }, [fetchAdminColors]);
const toggleTheme = () => {
const next = mode === "dark" ? "light" : "dark";
@@ -29,8 +63,11 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
AsyncStorage.setItem(STORAGE_KEY, next);
};
+ const baseColors = mode === "dark" ? darkColors : lightColors;
+ const colors: Colors = { ...baseColors, ...colorOverrides };
+
const value: ThemeContextType = {
- colors: mode === "dark" ? darkColors : lightColors,
+ colors,
mode,
toggleTheme,
isDark: mode === "dark",
diff --git a/frontend-admin/src/screens/admin/SettingsScreen.tsx b/frontend-admin/src/screens/admin/SettingsScreen.tsx
index b60e098c..94ec9512 100644
--- a/frontend-admin/src/screens/admin/SettingsScreen.tsx
+++ b/frontend-admin/src/screens/admin/SettingsScreen.tsx
@@ -1194,6 +1194,16 @@ export default function SettingsScreen() {
shop_name: "Milieu-Nantais",
contact_telegram: "",
points_reward: null,
+ admin_color_primary: "#7c3aed",
+ admin_color_secondary: "#22d3ee",
+ admin_color_success: "#4ade80",
+ admin_color_danger: "#ef4444",
+ admin_color_warning: "#f59e0b",
+ client_color_primary: "#7c3aed",
+ client_color_secondary: "#22d3ee",
+ client_color_success: "#4ade80",
+ client_color_danger: "#ef4444",
+ client_color_warning: "#f59e0b",
});
const [showApiKey, setShowApiKey] = useState(false);
const [showIpnSecret, setShowIpnSecret] = useState(false);
@@ -2182,6 +2192,66 @@ export default function SettingsScreen() {
)}
+ {/* ============================================ */}
+ {/* 🎨 COULEURS DE L'INTERFACE */}
+ {/* ============================================ */}
+
+
+ Personnalisez les couleurs de l'espace admin et de l'app client / site web.
+
+
+ {(["admin", "client"] as const).map((scope) => (
+
+
+ {scope === "admin" ? "Espace admin" : "App client & site web"}
+
+ {([
+ { label: "Principale", key: `${scope}_color_primary` as const },
+ { label: "Secondaire", key: `${scope}_color_secondary` as const },
+ { label: "Succès", key: `${scope}_color_success` as const },
+ { label: "Danger", key: `${scope}_color_danger` as const },
+ { label: "Avertissement", key: `${scope}_color_warning` as const },
+ ] as { label: string; key: keyof typeof settings }[]).map(({ label, key }) => (
+
+ {label}
+
+
+ setSettings((p) => ({ ...p, [key]: v }))}
+ placeholder="#7c3aed"
+ placeholderTextColor={colors.textMuted}
+ autoCapitalize="none"
+ autoCorrect={false}
+ />
+
+
+ {["#7c3aed", "#2563eb", "#0891b2", "#059669", "#d97706", "#dc2626", "#db2777", "#f59e0b", "#4ade80", "#ef4444", "#22d3ee", "#0f172a"].map((color) => (
+ setSettings((p) => ({ ...p, [key]: color }))}
+ style={{
+ width: 26, height: 26, borderRadius: 13,
+ backgroundColor: color,
+ borderWidth: (settings[key] as string) === color ? 2.5 : 1,
+ borderColor: (settings[key] as string) === color ? colors.textPrimary : colors.border,
+ }}
+ />
+ ))}
+
+
+ ))}
+
+ ))}
+
+
= 3 ? "value-danger" : penaltyCount > 0 ? "value-warning" : ""}`}
>
- {penaltyCount}
+ {penaltyCount} €
Score amendes
diff --git a/mobile/src/api/api.ts b/mobile/src/api/api.ts
index 29f9c894..3cd79596 100644
--- a/mobile/src/api/api.ts
+++ b/mobile/src/api/api.ts
@@ -778,6 +778,11 @@ export interface PublicSettings {
telegram_notifications_enabled: boolean;
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 => {
@@ -794,6 +799,11 @@ export const getPublicSettings = async (): Promise => {
telegram_notifications_enabled: false,
two_fa_enabled: false,
contact_telegram: "",
+ client_color_primary: "#7c3aed",
+ client_color_secondary: "#22d3ee",
+ client_color_success: "#4ade80",
+ client_color_danger: "#ef4444",
+ client_color_warning: "#f59e0b",
};
try {
const { data } = await apiClient.get(`${V1}/app-settings`);
@@ -816,6 +826,11 @@ export const getPublicSettings = async (): Promise => {
data.telegram_notifications_enabled ?? false,
two_fa_enabled: data.two_fa_enabled ?? false,
contact_telegram: data.contact_telegram || "",
+ client_color_primary: data.client_color_primary || "#7c3aed",
+ client_color_secondary: data.client_color_secondary || "#22d3ee",
+ client_color_success: data.client_color_success || "#4ade80",
+ client_color_danger: data.client_color_danger || "#ef4444",
+ client_color_warning: data.client_color_warning || "#f59e0b",
};
} catch {
return defaults;
diff --git a/mobile/src/context/ThemeContext.tsx b/mobile/src/context/ThemeContext.tsx
index 18db59fe..d2c0ecfd 100644
--- a/mobile/src/context/ThemeContext.tsx
+++ b/mobile/src/context/ThemeContext.tsx
@@ -1,6 +1,8 @@
-import React, { createContext, useContext, useState, useEffect, type ReactNode } from "react";
+import React, { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react";
+import { AppState } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { darkColors, lightColors, type Colors } from "../theme/colors";
+import { getPublicSettings } from "../api/api";
type ThemeMode = "dark" | "light";
@@ -12,16 +14,45 @@ interface ThemeContextType {
}
const STORAGE_KEY = "@theme_mode";
+const COLORS_CACHE_KEY = "@client_colors_cache";
const ThemeContext = createContext(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [mode, setMode] = useState("dark");
+ const [colorOverrides, setColorOverrides] = useState>({});
+
+ const fetchClientColors = useCallback(async () => {
+ try {
+ const s = await getPublicSettings();
+ const overrides: Partial = {};
+ if (s.client_color_primary) overrides.accent = s.client_color_primary;
+ if (s.client_color_secondary) overrides.secondary = s.client_color_secondary;
+ if (s.client_color_success) overrides.success = s.client_color_success;
+ if (s.client_color_danger) overrides.danger = s.client_color_danger;
+ if (s.client_color_warning) overrides.warning = s.client_color_warning;
+ setColorOverrides(overrides);
+ AsyncStorage.setItem(COLORS_CACHE_KEY, JSON.stringify(overrides)).catch(() => {});
+ } catch {}
+ }, []);
useEffect(() => {
AsyncStorage.getItem(STORAGE_KEY).then((val) => {
if (val === "light" || val === "dark") setMode(val);
});
- }, []);
+ AsyncStorage.getItem(COLORS_CACHE_KEY).then((val) => {
+ if (val) {
+ try { setColorOverrides(JSON.parse(val)); } catch {}
+ }
+ });
+ fetchClientColors();
+ }, [fetchClientColors]);
+
+ useEffect(() => {
+ const sub = AppState.addEventListener("change", (state) => {
+ if (state === "active") fetchClientColors();
+ });
+ return () => sub.remove();
+ }, [fetchClientColors]);
const toggleTheme = () => {
const next = mode === "dark" ? "light" : "dark";
@@ -29,8 +60,11 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
AsyncStorage.setItem(STORAGE_KEY, next);
};
+ const baseColors = mode === "dark" ? darkColors : lightColors;
+ const colors: Colors = { ...baseColors, ...colorOverrides };
+
const value: ThemeContextType = {
- colors: mode === "dark" ? darkColors : lightColors,
+ colors,
mode,
toggleTheme,
isDark: mode === "dark",
diff --git a/mobile/src/theme/colors.ts b/mobile/src/theme/colors.ts
index 914a6238..3dd5a6ca 100644
--- a/mobile/src/theme/colors.ts
+++ b/mobile/src/theme/colors.ts
@@ -16,6 +16,7 @@ export const darkColors = {
accent: "#7c3aed",
accentDark: "#6d28d9",
accentLight: "#8b5cf6",
+ secondary: "#22d3ee",
// Status
success: "#4ade80",
@@ -62,6 +63,7 @@ export const lightColors: Colors = {
accent: "#7c3aed",
accentDark: "#6d28d9",
accentLight: "#8b5cf6",
+ secondary: "#0891b2",
// Status
success: "#16a34a",