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,61 @@ 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+ // This handles both logout (userId becomes undefined) and login (userId changes)
59+ if ( userId !== currentUserIdRef . current ) {
60+ hasInitializedThemeRef . current = false ;
61+ previousThemeRef . current = undefined ;
62+ currentUserIdRef . current = userId ;
63+ }
64+
65+ // Only initialize theme from server on FIRST load for this user
66+ if ( ! userProfile ?. theme ?. theme || hasInitializedThemeRef . current ) {
67+ return ; // Skip if already initialized or no profile data
68+ }
69+
70+ // Apply theme from server profile (one-time only)
71+ setTheme ( userProfile ?. theme ?. theme || "system" ) ;
72+
73+ // Mark as initialized - prevents future syncs from server
74+ hasInitializedThemeRef . current = true ;
75+ } , [ userProfile ?. theme ?. theme , setTheme ] ) ;
76+
77+ /**
78+ * Effect 2: Custom theme CSS application (runs on every change)
79+ *
80+ * This effect applies or clears custom theme CSS variables whenever
81+ * the theme changes. It runs independently of the initial sync effect.
4282 */
4383 useEffect ( ( ) => {
4484 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- }
85+
86+ const currentTheme = userProfile ?. theme ?. theme ;
87+ const previousTheme = previousThemeRef . current ;
88+ const themeData = userProfile ?. theme ;
89+
90+ // Apply custom theme if current theme is custom
91+ if ( currentTheme === "custom" && themeData . primary && themeData . background && themeData . darkPalette !== undefined ) {
92+ applyCustomTheme ( themeData . primary , themeData . background , themeData . darkPalette ? "dark" : "light" ) ;
93+ }
94+ // Clear custom theme CSS when switching away from custom
95+ else if ( previousTheme === "custom" && currentTheme !== "custom" ) {
96+ clearCustomTheme ( ) ;
97+ // No reload needed - let CSS cascade handle it naturally
5898 }
59- } , [ userProfile ?. theme , setTheme ] ) ;
99+
100+ // Update previous theme for next comparison
101+ previousThemeRef . current = currentTheme ;
102+ } , [ userProfile ?. theme ] ) ;
60103
61104 useEffect ( ( ) => {
62105 if ( ! userProfile ?. language ) return ;
0 commit comments