diff --git a/internal/services/buildsystem.go b/internal/services/buildsystem.go index 1c4fc02..4cf18f2 100644 --- a/internal/services/buildsystem.go +++ b/internal/services/buildsystem.go @@ -221,8 +221,11 @@ func GenerateMavenWrapper(serviceDir string) error { return fmt.Errorf("%s path %s is not executable or inaccessible: %v", mvnExecutable, mvnPath, err) } - // Execute mvn -N wrapper:wrapper - cmd := exec.Command(mvnPath, "-N", "wrapper:wrapper") + // Execute mvn -N io.takari:maven:wrapper + cmd := exec.Command(mvnPath, "-N", "io.takari:maven:wrapper") + + fmt.Printf("[DEBUG] Executing command: %s\n", strings.Join(cmd.Args, " ")) + cmd.Dir = serviceDir output, err := cmd.CombinedOutput() if err != nil { diff --git a/web/src/components/ui/toast.tsx b/web/src/components/ui/toast.tsx index 619139d..eda7071 100644 --- a/web/src/components/ui/toast.tsx +++ b/web/src/components/ui/toast.tsx @@ -1,79 +1,81 @@ -import * as React from "react" -import { cn } from "@/lib/utils" -import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from "lucide-react" +import * as React from "react"; +import { cn } from "@/lib/utils"; +import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from "lucide-react"; -const TOAST_LIMIT = 5 +const TOAST_LIMIT = 5; -export type ToastVariant = "default" | "success" | "error" | "warning" | "info" +export type ToastVariant = "default" | "success" | "error" | "warning" | "info"; export interface Toast { - id: string - title?: string - description?: string - variant?: ToastVariant - duration?: number - action?: React.ReactNode + id: string; + title?: string; + description?: string; + variant?: ToastVariant; + duration?: number; + action?: React.ReactNode; } interface ToastContextType { - toasts: Toast[] - addToast: (toast: Omit) => void - removeToast: (id: string) => void - removeAllToasts: () => void + toasts: Toast[]; + addToast: (toast: Omit) => void; + removeToast: (id: string) => void; + removeAllToasts: () => void; } -const ToastContext = React.createContext(undefined) +const ToastContext = React.createContext( + undefined, +); export function useToast() { - const context = React.useContext(ToastContext) + const context = React.useContext(ToastContext); if (!context) { - throw new Error("useToast must be used within a ToastProvider") + throw new Error("useToast must be used within a ToastProvider"); } - return context + return context; } -let toastCount = 0 +let toastCount = 0; function generateId() { - toastCount = (toastCount + 1) % Number.MAX_SAFE_INTEGER - return toastCount.toString() + toastCount = (toastCount + 1) % Number.MAX_SAFE_INTEGER; + return toastCount.toString(); } export function ToastProvider({ children }: { children: React.ReactNode }) { - const [toasts, setToasts] = React.useState([]) + const [toasts, setToasts] = React.useState([]); const addToast = React.useCallback((toast: Omit) => { - const id = generateId() + const id = generateId(); const newToast: Toast = { ...toast, id, duration: toast.duration ?? 5000, - } + }; setToasts((currentToasts) => { - const updatedToasts = [...currentToasts, newToast] + const updatedToasts = [...currentToasts, newToast]; if (updatedToasts.length > TOAST_LIMIT) { - return updatedToasts.slice(-TOAST_LIMIT) + return updatedToasts.slice(-TOAST_LIMIT); } - return updatedToasts - }) + return updatedToasts; + }); if (newToast.duration && newToast.duration > 0) { setTimeout(() => { - removeToast(id) - }, newToast.duration) + removeToast(id); + }, newToast.duration); } - }, []) + }, []); const removeToast = React.useCallback((id: string) => { - setToasts((currentToasts) => - currentToasts.filter((toast) => toast.id !== id) - ) - }, []) + setToasts((currentToasts) => + currentToasts.filter((toast) => toast.id !== id), + ); + }, []); const removeAllToasts = React.useCallback(() => { - setToasts([]) - }, []) + setToasts([]); + }, []); return ( {children} - ) + ); } const toastVariants = { - default: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-900 dark:text-gray-100", - success: "bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-700 text-green-900 dark:text-green-100", - error: "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-700 text-red-900 dark:text-red-100", - warning: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-700 text-yellow-900 dark:text-yellow-100", + default: + "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-900 dark:text-gray-100", + success: + "bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-700 text-green-900 dark:text-green-100", + error: + "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-700 text-red-900 dark:text-red-100", + warning: + "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-700 text-yellow-900 dark:text-yellow-100", info: "bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-700 text-blue-900 dark:text-blue-100", -} +}; const iconVariants = { default: Info, @@ -103,7 +109,7 @@ const iconVariants = { error: AlertCircle, warning: AlertTriangle, info: Info, -} +}; const iconColors = { default: "text-gray-500 dark:text-gray-400", @@ -111,24 +117,29 @@ const iconColors = { error: "text-red-500 dark:text-red-400", warning: "text-yellow-500 dark:text-yellow-400", info: "text-blue-500 dark:text-blue-400", -} +}; export function ToastContainer() { - const { toasts, removeToast } = useToast() + const { toasts, removeToast } = useToast(); return (
{toasts.map((toast) => { - const Icon = iconVariants[toast.variant || "default"] + const Icon = iconVariants[toast.variant || "default"]; return (
- +
{toast.title && (
{toast.title}
@@ -136,9 +147,7 @@ export function ToastContainer() { {toast.description && (
{toast.description}
)} - {toast.action && ( -
{toast.action}
- )} + {toast.action &&
{toast.action}
}
- ) + ); })}
- ) + ); } // Convenience functions @@ -175,4 +184,4 @@ export const toast = { description, variant: "info" as const, }), -} \ No newline at end of file +}; diff --git a/web/src/containers/ServiceModals.tsx b/web/src/containers/ServiceModals.tsx index d75216d..8465ccc 100644 --- a/web/src/containers/ServiceModals.tsx +++ b/web/src/containers/ServiceModals.tsx @@ -62,12 +62,20 @@ export function ServiceModals({ serviceManagement }: ServiceModalsProps) { serviceName={serviceManagement.wrapperManagementData?.name || ""} isOpen={serviceManagement.isWrapperManagementOpen} onClose={serviceManagement.closeWrapperManagement} - onValidateWrapper={() => ServiceOperations.validateWrapper(serviceManagement.wrapperManagementData?.id || "")} + onValidateWrapper={() => + ServiceOperations.validateWrapper( + serviceManagement.wrapperManagementData?.id || "", + ) + } onGenerateWrapper={async () => { - await ServiceOperations.generateWrapper(serviceManagement.wrapperManagementData?.id || ""); + await ServiceOperations.generateWrapper( + serviceManagement.wrapperManagementData?.id || "", + ); }} onRepairWrapper={async () => { - await ServiceOperations.repairWrapper(serviceManagement.wrapperManagementData?.id || ""); + await ServiceOperations.repairWrapper( + serviceManagement.wrapperManagementData?.id || "", + ); }} /> diff --git a/web/src/contexts/AuthContext.tsx b/web/src/contexts/AuthContext.tsx index 0bbd8d7..522d190 100644 --- a/web/src/contexts/AuthContext.tsx +++ b/web/src/contexts/AuthContext.tsx @@ -1,4 +1,10 @@ -import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { + createContext, + useContext, + useState, + useEffect, + ReactNode, +} from "react"; interface User { id: string; @@ -24,7 +30,7 @@ const AuthContext = createContext(undefined); export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { - throw new Error('useAuth must be used within an AuthProvider'); + throw new Error("useAuth must be used within an AuthProvider"); } return context; } @@ -43,20 +49,20 @@ export function AuthProvider({ children }: AuthProviderProps) { const login = (userData: User, authToken: string) => { setUser(userData); setToken(authToken); - localStorage.setItem('authToken', authToken); - localStorage.setItem('user', JSON.stringify(userData)); + localStorage.setItem("authToken", authToken); + localStorage.setItem("user", JSON.stringify(userData)); }; const logout = () => { setUser(null); setToken(null); - localStorage.removeItem('authToken'); - localStorage.removeItem('user'); + localStorage.removeItem("authToken"); + localStorage.removeItem("user"); }; const checkAuth = async (): Promise => { - const storedToken = localStorage.getItem('authToken'); - const storedUser = localStorage.getItem('user'); + const storedToken = localStorage.getItem("authToken"); + const storedUser = localStorage.getItem("user"); if (!storedToken || !storedUser) { setIsLoading(false); @@ -65,10 +71,10 @@ export function AuthProvider({ children }: AuthProviderProps) { try { // Verify token is still valid - const response = await fetch('/api/auth/user', { + const response = await fetch("/api/auth/user", { headers: { - 'Authorization': `Bearer ${storedToken}`, - 'Content-Type': 'application/json', + Authorization: `Bearer ${storedToken}`, + "Content-Type": "application/json", }, }); @@ -85,7 +91,7 @@ export function AuthProvider({ children }: AuthProviderProps) { return false; } } catch (error) { - console.error('Auth check failed:', error); + console.error("Auth check failed:", error); logout(); setIsLoading(false); return false; @@ -106,9 +112,5 @@ export function AuthProvider({ children }: AuthProviderProps) { checkAuth, }; - return ( - - {children} - - ); -} \ No newline at end of file + return {children}; +} diff --git a/web/src/contexts/ProfileContext.tsx b/web/src/contexts/ProfileContext.tsx index 7823833..35e23c3 100644 --- a/web/src/contexts/ProfileContext.tsx +++ b/web/src/contexts/ProfileContext.tsx @@ -1,37 +1,62 @@ -import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; -import { ServiceProfile, UserProfile, CreateProfileRequest, UpdateProfileRequest, UserProfileUpdateRequest, ProfileContext as ProfileContextData } from '@/types'; -import { useAuth } from './AuthContext'; -import { useTheme } from './ThemeContext'; +import { + createContext, + useContext, + useState, + useEffect, + ReactNode, +} from "react"; +import { + ServiceProfile, + UserProfile, + CreateProfileRequest, + UpdateProfileRequest, + UserProfileUpdateRequest, + ProfileContext as ProfileContextData, +} from "@/types"; +import { useAuth } from "./AuthContext"; +import { useTheme } from "./ThemeContext"; interface ProfileContextType { // User Profile userProfile: UserProfile | null; updateUserProfile: (req: UserProfileUpdateRequest) => Promise; - + // Service Profiles serviceProfiles: ServiceProfile[]; activeProfile: ServiceProfile | null; createProfile: (req: CreateProfileRequest) => Promise; - updateProfile: (id: string, req: UpdateProfileRequest) => Promise; + updateProfile: ( + id: string, + req: UpdateProfileRequest, + ) => Promise; deleteProfile: (id: string) => Promise; applyProfile: (id: string) => Promise; setActiveProfile: (profile: ServiceProfile | null) => void; - + // Profile-scoped configuration activateProfile: (id: string) => Promise; getProfileContext: (id: string) => Promise; getProfileEnvVars: (id: string) => Promise>; - setProfileEnvVar: (id: string, name: string, value: string, description?: string, isRequired?: boolean) => Promise; + setProfileEnvVar: ( + id: string, + name: string, + value: string, + description?: string, + isRequired?: boolean, + ) => Promise; deleteProfileEnvVar: (id: string, name: string) => Promise; - removeServiceFromProfile: (profileId: string, serviceName: string) => Promise; - + removeServiceFromProfile: ( + profileId: string, + serviceName: string, + ) => Promise; + // Loading states isLoading: boolean; isCreating: boolean; isUpdating: boolean; isDeleting: boolean; isApplying: boolean; - + // Refresh data refreshProfiles: () => Promise; refreshUserProfile: () => Promise; @@ -42,7 +67,7 @@ const ProfileContext = createContext(undefined); export function useProfile() { const context = useContext(ProfileContext); if (context === undefined) { - throw new Error('useProfile must be used within a ProfileProvider'); + throw new Error("useProfile must be used within a ProfileProvider"); } return context; } @@ -56,8 +81,9 @@ export function ProfileProvider({ children }: ProfileProviderProps) { const { syncWithUserProfile } = useTheme(); const [userProfile, setUserProfile] = useState(null); const [serviceProfiles, setServiceProfiles] = useState([]); - const [activeProfile, setActiveProfileState] = useState(null); - + const [activeProfile, setActiveProfileState] = + useState(null); + // Loading states const [isLoading, setIsLoading] = useState(false); const [isCreating, setIsCreating] = useState(false); @@ -67,21 +93,21 @@ export function ProfileProvider({ children }: ProfileProviderProps) { // Get auth token for API calls const getAuthToken = () => { - return localStorage.getItem('authToken'); + return localStorage.getItem("authToken"); }; // API Helper function const apiCall = async (url: string, options: RequestInit = {}) => { const token = getAuthToken(); if (!token) { - throw new Error('No authentication token'); + throw new Error("No authentication token"); } const response = await fetch(url, { ...options, headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, ...options.headers, }, }); @@ -89,12 +115,12 @@ export function ProfileProvider({ children }: ProfileProviderProps) { if (!response.ok) { // Handle authentication errors specifically if (response.status === 401) { - console.warn('Authentication failed, token may be invalid'); + console.warn("Authentication failed, token may be invalid"); // You could trigger a logout here if needed // logout(); - throw new Error('Authentication failed. Please log in again.'); + throw new Error("Authentication failed. Please log in again."); } - + const errorText = await response.text(); throw new Error(`API Error: ${response.status} - ${errorText}`); } @@ -109,15 +135,15 @@ export function ProfileProvider({ children }: ProfileProviderProps) { // User Profile Operations const fetchUserProfile = async () => { try { - const profile = await apiCall('/api/user/profile'); + const profile = await apiCall("/api/user/profile"); setUserProfile(profile); - + // Sync theme with user profile preferences if (profile?.preferences?.theme) { syncWithUserProfile(profile.preferences.theme); } } catch (error) { - console.error('Failed to fetch user profile:', error); + console.error("Failed to fetch user profile:", error); throw error; } }; @@ -125,22 +151,22 @@ export function ProfileProvider({ children }: ProfileProviderProps) { const updateUserProfile = async (req: UserProfileUpdateRequest) => { try { setIsUpdating(true); - console.log('Updating user profile with request:', req); - - const updatedProfile = await apiCall('/api/user/profile', { - method: 'PUT', + console.log("Updating user profile with request:", req); + + const updatedProfile = await apiCall("/api/user/profile", { + method: "PUT", body: JSON.stringify(req), }); - - console.log('User profile updated successfully:', updatedProfile); + + console.log("User profile updated successfully:", updatedProfile); setUserProfile(updatedProfile); - + // Sync theme with updated profile preferences if (updatedProfile?.preferences?.theme) { syncWithUserProfile(updatedProfile.preferences.theme); } } catch (error) { - console.error('Failed to update user profile:', error); + console.error("Failed to update user profile:", error); throw error; } finally { setIsUpdating(false); @@ -149,17 +175,25 @@ export function ProfileProvider({ children }: ProfileProviderProps) { // Set the active profile based on the isActive flag from backend const setActiveProfileFromProfiles = (profiles: ServiceProfile[]) => { - console.log('Setting active profile from profiles:', profiles.map((p: ServiceProfile) => ({ name: p.name, isActive: p.isActive }))); - + console.log( + "Setting active profile from profiles:", + profiles.map((p: ServiceProfile) => ({ + name: p.name, + isActive: p.isActive, + })), + ); + // Find the profile marked as active in the backend data - const activeProfile = profiles.find(p => p.isActive); + const activeProfile = profiles.find((p) => p.isActive); if (activeProfile) { - console.log('Found active profile from backend:', activeProfile.name); + console.log("Found active profile from backend:", activeProfile.name); setActiveProfileState(activeProfile); return; } - - console.log('No active profile found in backend data, clearing active profile state'); + + console.log( + "No active profile found in backend data, clearing active profile state", + ); setActiveProfileState(null); }; @@ -167,68 +201,79 @@ export function ProfileProvider({ children }: ProfileProviderProps) { const fetchServiceProfiles = async () => { try { setIsLoading(true); - const profiles = await apiCall('/api/profiles'); - console.log('Fetched profiles:', profiles?.map((p: ServiceProfile) => ({ name: p.name, isActive: p.isActive }))); + const profiles = await apiCall("/api/profiles"); + console.log( + "Fetched profiles:", + profiles?.map((p: ServiceProfile) => ({ + name: p.name, + isActive: p.isActive, + })), + ); setServiceProfiles(profiles || []); - + // Set the active profile based on the isActive flag from backend setActiveProfileFromProfiles(profiles || []); } catch (error) { - console.error('Failed to fetch service profiles:', error); + console.error("Failed to fetch service profiles:", error); throw error; } finally { setIsLoading(false); } }; - const createProfile = async (req: CreateProfileRequest): Promise => { + const createProfile = async ( + req: CreateProfileRequest, + ): Promise => { try { setIsCreating(true); - const newProfile = await apiCall('/api/profiles', { - method: 'POST', + const newProfile = await apiCall("/api/profiles", { + method: "POST", body: JSON.stringify(req), }); - setServiceProfiles(prev => [...prev, newProfile]); - + setServiceProfiles((prev) => [...prev, newProfile]); + // Set as active if it's the default profile if (req.isDefault) { setActiveProfileState(newProfile); } - + return newProfile; } catch (error) { - console.error('Failed to create profile:', error); + console.error("Failed to create profile:", error); throw error; } finally { setIsCreating(false); } }; - const updateProfile = async (id: string, req: UpdateProfileRequest): Promise => { + const updateProfile = async ( + id: string, + req: UpdateProfileRequest, + ): Promise => { try { setIsUpdating(true); const updatedProfile = await apiCall(`/api/profiles/${id}`, { - method: 'PUT', + method: "PUT", body: JSON.stringify(req), }); - - setServiceProfiles(prev => - prev.map(profile => profile.id === id ? updatedProfile : profile) + + setServiceProfiles((prev) => + prev.map((profile) => (profile.id === id ? updatedProfile : profile)), ); - + // Update active profile if it's the one being updated if (activeProfile?.id === id) { setActiveProfileState(updatedProfile); } - + // Set as active if it's now the default profile if (req.isDefault) { setActiveProfileState(updatedProfile); } - + return updatedProfile; } catch (error) { - console.error('Failed to update profile:', error); + console.error("Failed to update profile:", error); throw error; } finally { setIsUpdating(false); @@ -239,19 +284,19 @@ export function ProfileProvider({ children }: ProfileProviderProps) { try { setIsDeleting(true); await apiCall(`/api/profiles/${id}`, { - method: 'DELETE', + method: "DELETE", }); - - setServiceProfiles(prev => prev.filter(profile => profile.id !== id)); - + + setServiceProfiles((prev) => prev.filter((profile) => profile.id !== id)); + // Clear active profile if it's the one being deleted if (activeProfile?.id === id) { - const remaining = serviceProfiles.filter(p => p.id !== id); - const defaultProfile = remaining.find(p => p.isDefault); + const remaining = serviceProfiles.filter((p) => p.id !== id); + const defaultProfile = remaining.find((p) => p.isDefault); setActiveProfileState(defaultProfile || remaining[0] || null); } } catch (error) { - console.error('Failed to delete profile:', error); + console.error("Failed to delete profile:", error); throw error; } finally { setIsDeleting(false); @@ -262,16 +307,16 @@ export function ProfileProvider({ children }: ProfileProviderProps) { try { setIsApplying(true); await apiCall(`/api/profiles/${id}/apply`, { - method: 'POST', + method: "POST", }); - + // Find and set the applied profile as active - const appliedProfile = serviceProfiles.find(p => p.id === id); + const appliedProfile = serviceProfiles.find((p) => p.id === id); if (appliedProfile) { setActiveProfileState(appliedProfile); } } catch (error) { - console.error('Failed to apply profile:', error); + console.error("Failed to apply profile:", error); throw error; } finally { setIsApplying(false); @@ -294,18 +339,18 @@ export function ProfileProvider({ children }: ProfileProviderProps) { const activateProfile = async (id: string) => { try { - console.log('Activating profile:', id); + console.log("Activating profile:", id); await apiCall(`/api/profiles/${id}/activate`, { - method: 'POST', + method: "POST", }); - console.log('Profile activation API call successful'); - + console.log("Profile activation API call successful"); + // Re-fetch profiles to get the updated isActive flags from backend // This ensures consistency between frontend and backend state await fetchServiceProfiles(); - console.log('Profiles re-fetched after activation'); + console.log("Profiles re-fetched after activation"); } catch (error) { - console.error('Failed to activate profile:', error); + console.error("Failed to activate profile:", error); throw error; } }; @@ -314,30 +359,32 @@ export function ProfileProvider({ children }: ProfileProviderProps) { try { return await apiCall(`/api/profiles/${id}/context`); } catch (error) { - console.error('Failed to get profile context:', error); + console.error("Failed to get profile context:", error); throw error; } }; - const getProfileEnvVars = async (id: string): Promise> => { + const getProfileEnvVars = async ( + id: string, + ): Promise> => { try { return await apiCall(`/api/profiles/${id}/env-vars`); } catch (error) { - console.error('Failed to get profile env vars:', error); + console.error("Failed to get profile env vars:", error); throw error; } }; const setProfileEnvVar = async ( - id: string, - name: string, - value: string, - description: string = '', - isRequired: boolean = false + id: string, + name: string, + value: string, + description: string = "", + isRequired: boolean = false, ) => { try { await apiCall(`/api/profiles/${id}/env-vars`, { - method: 'POST', + method: "POST", body: JSON.stringify({ name, value, @@ -346,29 +393,38 @@ export function ProfileProvider({ children }: ProfileProviderProps) { }), }); } catch (error) { - console.error('Failed to set profile env var:', error); + console.error("Failed to set profile env var:", error); throw error; } }; const deleteProfileEnvVar = async (id: string, name: string) => { try { - await apiCall(`/api/profiles/${id}/env-vars/${encodeURIComponent(name)}`, { - method: 'DELETE', - }); + await apiCall( + `/api/profiles/${id}/env-vars/${encodeURIComponent(name)}`, + { + method: "DELETE", + }, + ); } catch (error) { - console.error('Failed to delete profile env var:', error); + console.error("Failed to delete profile env var:", error); throw error; } }; - const removeServiceFromProfile = async (profileId: string, serviceName: string) => { + const removeServiceFromProfile = async ( + profileId: string, + serviceName: string, + ) => { try { - await apiCall(`/api/profiles/${profileId}/services/${encodeURIComponent(serviceName)}`, { - method: 'DELETE', - }); + await apiCall( + `/api/profiles/${profileId}/services/${encodeURIComponent(serviceName)}`, + { + method: "DELETE", + }, + ); } catch (error) { - console.error('Failed to remove service from profile:', error); + console.error("Failed to remove service from profile:", error); throw error; } }; @@ -390,7 +446,7 @@ export function ProfileProvider({ children }: ProfileProviderProps) { // User Profile userProfile, updateUserProfile, - + // Service Profiles serviceProfiles, activeProfile, @@ -399,7 +455,7 @@ export function ProfileProvider({ children }: ProfileProviderProps) { deleteProfile, applyProfile, setActiveProfile, - + // Profile-scoped configuration activateProfile, getProfileContext, @@ -407,22 +463,20 @@ export function ProfileProvider({ children }: ProfileProviderProps) { setProfileEnvVar, deleteProfileEnvVar, removeServiceFromProfile, - + // Loading states isLoading, isCreating, isUpdating, isDeleting, isApplying, - + // Refresh data refreshProfiles, refreshUserProfile, }; return ( - - {children} - + {children} ); -} \ No newline at end of file +} diff --git a/web/src/contexts/ThemeContext.tsx b/web/src/contexts/ThemeContext.tsx index cdc6dfa..55c2940 100644 --- a/web/src/contexts/ThemeContext.tsx +++ b/web/src/contexts/ThemeContext.tsx @@ -1,6 +1,12 @@ -import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { + createContext, + useContext, + useState, + useEffect, + ReactNode, +} from "react"; -type Theme = 'light' | 'dark'; +type Theme = "light" | "dark"; interface ThemeContextType { theme: Theme; @@ -14,7 +20,7 @@ const ThemeContext = createContext(undefined); export function useTheme() { const context = useContext(ThemeContext); if (context === undefined) { - throw new Error('useTheme must be used within a ThemeProvider'); + throw new Error("useTheme must be used within a ThemeProvider"); } return context; } @@ -24,58 +30,60 @@ interface ThemeProviderProps { } export function ThemeProvider({ children }: ThemeProviderProps) { - const [theme, setThemeState] = useState('light'); + const [theme, setThemeState] = useState("light"); useEffect(() => { // Check localStorage for saved theme - const savedTheme = localStorage.getItem('theme') as Theme; + const savedTheme = localStorage.getItem("theme") as Theme; if (savedTheme) { setThemeState(savedTheme); applyTheme(savedTheme); } else { // Check system preference - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - const initialTheme = prefersDark ? 'dark' : 'light'; + const prefersDark = window.matchMedia( + "(prefers-color-scheme: dark)", + ).matches; + const initialTheme = prefersDark ? "dark" : "light"; setThemeState(initialTheme); applyTheme(initialTheme); } // Listen for system theme changes - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const handleChange = (e: MediaQueryListEvent) => { - if (!localStorage.getItem('theme')) { - const newTheme = e.matches ? 'dark' : 'light'; + if (!localStorage.getItem("theme")) { + const newTheme = e.matches ? "dark" : "light"; setThemeState(newTheme); applyTheme(newTheme); } }; - mediaQuery.addEventListener('change', handleChange); - return () => mediaQuery.removeEventListener('change', handleChange); + mediaQuery.addEventListener("change", handleChange); + return () => mediaQuery.removeEventListener("change", handleChange); }, []); const applyTheme = (newTheme: Theme) => { const root = document.documentElement; - if (newTheme === 'dark') { - root.classList.add('dark'); + if (newTheme === "dark") { + root.classList.add("dark"); } else { - root.classList.remove('dark'); + root.classList.remove("dark"); } }; const setTheme = (newTheme: Theme) => { setThemeState(newTheme); - localStorage.setItem('theme', newTheme); + localStorage.setItem("theme", newTheme); applyTheme(newTheme); }; const toggleTheme = () => { - const newTheme = theme === 'light' ? 'dark' : 'light'; + const newTheme = theme === "light" ? "dark" : "light"; setTheme(newTheme); }; const syncWithUserProfile = (profileTheme: string) => { - if (profileTheme === 'light' || profileTheme === 'dark') { + if (profileTheme === "light" || profileTheme === "dark") { setThemeState(profileTheme); applyTheme(profileTheme); // Don't save to localStorage when syncing from profile to avoid conflicts @@ -90,8 +98,6 @@ export function ThemeProvider({ children }: ThemeProviderProps) { }; return ( - - {children} - + {children} ); -} \ No newline at end of file +}