11import type { ReactNode } from "react" ;
2- import { useEffect } from "react" ;
2+ import { useEffect , useRef } from "react" ;
33import { observer } from "mobx-react" ;
44import { useParams } from "next/navigation" ;
55import { useTheme } from "next-themes" ;
@@ -28,6 +28,13 @@ function StoreWrapper(props: TStoreWrapper) {
2828 const { data : userProfile } = useUserProfile ( ) ;
2929 const { changeLanguage } = useTranslation ( ) ;
3030
31+ // Track if we've initialized theme from server (one-time only)
32+ const hasInitializedThemeRef = useRef ( false ) ;
33+ // Track current user to reset on logout/login
34+ const currentUserIdRef = useRef < string | undefined > ( undefined ) ;
35+ // Track previous theme to detect transitions from custom theme
36+ const previousThemeRef = useRef < string | undefined > ( undefined ) ;
37+
3138 /**
3239 * Sidebar collapsed fetching from local storage
3340 */
@@ -38,25 +45,59 @@ function StoreWrapper(props: TStoreWrapper) {
3845 } , [ sidebarCollapsed , setTheme , toggleSidebar ] ) ;
3946
4047 /**
41- * Setting up the theme of the user by fetching it from profile
48+ * Effect 1: Initial theme sync from server (one-time only)
49+ *
50+ * This effect runs ONCE per user session to load theme from server.
51+ * After initial load, all theme changes are localStorage-driven (next-themes).
52+ * This prevents a feedback loop where server updates trigger UI updates in a cycle.
53+ */
54+ useEffect ( ( ) => {
55+ const userId = userProfile ?. id ;
56+
57+ // Reset initialization flag when user changes (logout/login)
58+ if ( userId && userId !== currentUserIdRef . current ) {
59+ hasInitializedThemeRef . current = false ;
60+ currentUserIdRef . current = userId ;
61+ }
62+
63+ // Only initialize theme from server on FIRST load for this user
64+ if ( ! userProfile ?. theme ?. theme || hasInitializedThemeRef . current ) {
65+ return ; // Skip if already initialized or no profile data
66+ }
67+
68+ // Apply theme from server profile (one-time only)
69+ setTheme ( userProfile ?. theme ?. theme || "system" ) ;
70+
71+ // Mark as initialized - prevents future syncs from server
72+ hasInitializedThemeRef . current = true ;
73+ } , [ userProfile ?. theme , userProfile ?. id , setTheme ] ) ;
74+
75+ /**
76+ * Effect 2: Custom theme CSS application (runs on every change)
77+ *
78+ * This effect applies or clears custom theme CSS variables whenever
79+ * the theme changes. It runs independently of the initial sync effect.
4280 */
4381 useEffect ( ( ) => {
4482 if ( ! userProfile ?. theme ?. theme ) return ;
45- const currentTheme = userProfile ?. theme ?. theme || "system" ;
46- const theme = userProfile ?. theme ;
47-
48- if ( currentTheme ) {
49- setTheme ( currentTheme ) ;
50- if ( currentTheme === "custom" ) {
51- // New 2-color palette system
52- if ( theme . primary && theme . background && theme . darkPalette !== undefined ) {
53- applyCustomTheme ( theme . primary , theme . background , theme . darkPalette ? "dark" : "light" ) ;
54- }
55- } else {
56- clearCustomTheme ( ) ;
57- }
83+
84+ const currentTheme = userProfile ?. theme ?. theme ;
85+ const previousTheme = previousThemeRef . current ;
86+ const themeData = userProfile ?. theme ;
87+
88+ // Apply custom theme if current theme is custom
89+ if ( currentTheme === "custom" && themeData . primary && themeData . background && themeData . darkPalette !== undefined ) {
90+ applyCustomTheme ( themeData . primary , themeData . background , themeData . darkPalette ? "dark" : "light" ) ;
91+ }
92+ // Clear custom theme CSS when switching away from custom
93+ else if ( previousTheme === "custom" && currentTheme !== "custom" ) {
94+ clearCustomTheme ( ) ;
95+ // No reload needed - let CSS cascade handle it naturally
5896 }
59- } , [ userProfile ?. theme , setTheme ] ) ;
97+
98+ // Update previous theme for next comparison
99+ previousThemeRef . current = currentTheme ;
100+ } , [ userProfile ?. theme ] ) ;
60101
61102 useEffect ( ( ) => {
62103 if ( ! userProfile ?. language ) return ;
0 commit comments