fixed theme and user profile settings

This commit is contained in:
Muhammad Ibrahim
2025-10-31 22:17:24 +00:00
parent 1547af6986
commit 53ff3bb1e2
6 changed files with 176 additions and 85 deletions

View File

@@ -450,8 +450,8 @@ function AppRoutes() {
function App() {
return (
<ThemeProvider>
<AuthProvider>
<AuthProvider>
<ThemeProvider>
<SettingsProvider>
<ColorThemeProvider>
<UpdateNotificationProvider>
@@ -461,8 +461,8 @@ function App() {
</UpdateNotificationProvider>
</ColorThemeProvider>
</SettingsProvider>
</AuthProvider>
</ThemeProvider>
</ThemeProvider>
</AuthProvider>
);
}

View File

@@ -138,6 +138,9 @@ export const AuthProvider = ({ children }) => {
setPermissions(userPermissions);
}
// Note: User preferences will be automatically fetched by ColorThemeContext
// when the component mounts, so no need to invalidate here
return { success: true };
} else {
// Handle HTTP error responses (like 500 CORS errors)
@@ -224,8 +227,6 @@ export const AuthProvider = ({ children }) => {
const data = await response.json();
if (response.ok) {
console.log("Profile updated - received user data:", data.user);
// Validate that we received user data with expected fields
if (!data.user || !data.user.id) {
console.error("Invalid user data in response:", data);
@@ -239,15 +240,6 @@ export const AuthProvider = ({ children }) => {
setUser(data.user);
localStorage.setItem("user", JSON.stringify(data.user));
// Log update for debugging (only in non-production)
if (process.env.NODE_ENV !== "production") {
console.log("User data updated in localStorage:", {
id: data.user.id,
first_name: data.user.first_name,
last_name: data.user.last_name,
});
}
return { success: true, user: data.user };
} else {
// Handle HTTP error responses (like 500 CORS errors)

View File

@@ -1,6 +1,15 @@
import { useQuery } from "@tanstack/react-query";
import { createContext, useContext, useEffect, useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { userPreferencesAPI } from "../utils/api";
import { useAuth } from "./AuthContext";
const ColorThemeContext = createContext();
@@ -123,48 +132,108 @@ export const THEME_PRESETS = {
};
export const ColorThemeProvider = ({ children }) => {
const [colorTheme, setColorTheme] = useState(() => {
// Initialize from localStorage for immediate render
return localStorage.getItem("colorTheme") || "cyber_blue";
});
const [isLoading, setIsLoading] = useState(true);
const queryClient = useQueryClient();
const lastThemeRef = useRef(null);
// Fetch user preferences from backend
const { data: userPreferences } = useQuery({
// Use reactive authentication state from AuthContext
// This ensures the query re-enables when user logs in
const { user } = useAuth();
const isAuthenticated = !!user;
// Source of truth: Database (via userPreferences query)
// localStorage is only used as a temporary cache until DB loads
// Only fetch if user is authenticated to avoid 401 errors on login page
const { data: userPreferences, isLoading: preferencesLoading } = useQuery({
queryKey: ["userPreferences"],
queryFn: () => userPreferencesAPI.get().then((res) => res.data),
retry: 1,
enabled: isAuthenticated, // Only run query if user is authenticated
retry: 2,
staleTime: 5 * 60 * 1000, // 5 minutes
refetchOnWindowFocus: true, // Refetch when user returns to tab
});
// Update theme when preferences are loaded
// Get theme from database (source of truth), fallback to user object from login, then localStorage cache, then default
// Memoize to prevent recalculation on every render
const colorThemeValue = useMemo(() => {
return (
userPreferences?.color_theme ||
user?.color_theme ||
localStorage.getItem("colorTheme") ||
"cyber_blue"
);
}, [userPreferences?.color_theme, user?.color_theme]);
// Only update state if the theme value actually changed (prevent loops)
const [colorTheme, setColorTheme] = useState(() => colorThemeValue);
useEffect(() => {
// Only update if the value actually changed from what we last saw (prevent loops)
if (colorThemeValue !== lastThemeRef.current) {
setColorTheme(colorThemeValue);
lastThemeRef.current = colorThemeValue;
}
}, [colorThemeValue]);
const isLoading = preferencesLoading;
// Sync localStorage cache when DB data is available (for offline/performance)
useEffect(() => {
if (userPreferences?.color_theme) {
setColorTheme(userPreferences.color_theme);
localStorage.setItem("colorTheme", userPreferences.color_theme);
}
setIsLoading(false);
}, [userPreferences]);
}, [userPreferences?.color_theme]);
const updateColorTheme = async (theme) => {
setColorTheme(theme);
localStorage.setItem("colorTheme", theme);
const updateColorTheme = useCallback(
async (theme) => {
// Store previous theme for potential revert
const previousTheme = colorTheme;
// Save to backend
try {
await userPreferencesAPI.update({ color_theme: theme });
} catch (error) {
console.error("Failed to save color theme preference:", error);
// Theme is already set locally, so user still sees the change
}
};
// Immediately update state for instant UI feedback
setColorTheme(theme);
lastThemeRef.current = theme;
const value = {
colorTheme,
setColorTheme: updateColorTheme,
themeConfig: THEME_PRESETS[colorTheme] || THEME_PRESETS.default,
isLoading,
};
// Also update localStorage cache
localStorage.setItem("colorTheme", theme);
// Save to backend (source of truth)
try {
await userPreferencesAPI.update({ color_theme: theme });
// Invalidate and refetch user preferences to ensure sync across tabs/browsers
await queryClient.invalidateQueries({ queryKey: ["userPreferences"] });
} catch (error) {
console.error("Failed to save color theme preference:", error);
// Revert to previous theme if save failed
setColorTheme(previousTheme);
lastThemeRef.current = previousTheme;
localStorage.setItem("colorTheme", previousTheme);
// Invalidate to refresh from DB
await queryClient.invalidateQueries({ queryKey: ["userPreferences"] });
// Show error to user if possible (could add toast notification here)
throw error; // Re-throw so calling code can handle it
}
},
[colorTheme, queryClient],
);
// Memoize themeConfig to prevent unnecessary re-renders
const themeConfig = useMemo(
() => THEME_PRESETS[colorTheme] || THEME_PRESETS.default,
[colorTheme],
);
// Memoize the context value to prevent unnecessary re-renders
const value = useMemo(
() => ({
colorTheme,
setColorTheme: updateColorTheme,
themeConfig,
isLoading,
}),
[colorTheme, themeConfig, isLoading, updateColorTheme],
);
return (
<ColorThemeContext.Provider value={value}>

View File

@@ -1,6 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import { createContext, useContext, useEffect, useState } from "react";
import { userPreferencesAPI } from "../utils/api";
import { useAuth } from "./AuthContext";
const ThemeContext = createContext();
@@ -26,21 +27,29 @@ export const ThemeProvider = ({ children }) => {
return "light";
});
// Fetch user preferences from backend
// Use reactive authentication state from AuthContext
// This ensures the query re-enables when user logs in
const { user } = useAuth();
const isAuthenticated = !!user;
// Fetch user preferences from backend (only if authenticated)
const { data: userPreferences } = useQuery({
queryKey: ["userPreferences"],
queryFn: () => userPreferencesAPI.get().then((res) => res.data),
enabled: isAuthenticated, // Only run query if user is authenticated
retry: 1,
staleTime: 5 * 60 * 1000, // 5 minutes
});
// Sync with user preferences from backend
// Sync with user preferences from backend or user object from login
useEffect(() => {
if (userPreferences?.theme_preference) {
setTheme(userPreferences.theme_preference);
localStorage.setItem("theme", userPreferences.theme_preference);
const preferredTheme =
userPreferences?.theme_preference || user?.theme_preference;
if (preferredTheme) {
setTheme(preferredTheme);
localStorage.setItem("theme", preferredTheme);
}
}, [userPreferences]);
}, [userPreferences, user?.theme_preference]);
useEffect(() => {
// Apply theme to document

View File

@@ -248,7 +248,8 @@ const Login = () => {
} catch (error) {
console.error("Failed to fetch GitHub data:", error);
// Set fallback data if nothing cached
if (!latestRelease) {
const cachedRelease = localStorage.getItem("githubLatestRelease");
if (!cachedRelease) {
setLatestRelease({
version: "v1.3.0",
name: "Latest Release",
@@ -260,7 +261,7 @@ const Login = () => {
};
fetchGitHubData();
}, [latestRelease]);
}, []); // Run once on mount
const handleSubmit = async (e) => {
e.preventDefault();