From 1cbc9d7b8ecdae24aa74c47f654c4f0a892151ce Mon Sep 17 00:00:00 2001 From: chark1es Date: Fri, 28 Mar 2025 14:34:22 -0700 Subject: [PATCH] fix colors --- .../dashboard/SettingsSection.astro | 398 ++++++++++-------- .../SettingsSection/DisplaySettings.tsx | 235 +++++------ src/scripts/database/DexieService.ts | 10 + src/scripts/database/ThemeService.ts | 238 +++++++++++ src/utils/themeUtils.ts | 50 +++ tailwind.config.mjs | 32 ++ 6 files changed, 651 insertions(+), 312 deletions(-) create mode 100644 src/scripts/database/ThemeService.ts create mode 100644 src/utils/themeUtils.ts diff --git a/src/components/dashboard/SettingsSection.astro b/src/components/dashboard/SettingsSection.astro index c85a37b..248f4d6 100644 --- a/src/components/dashboard/SettingsSection.astro +++ b/src/components/dashboard/SettingsSection.astro @@ -6,6 +6,7 @@ import NotificationSettings from "./SettingsSection/NotificationSettings"; import DisplaySettings from "./SettingsSection/DisplaySettings"; import ResumeSettings from "./SettingsSection/ResumeSettings"; import EmailRequestSettings from "./SettingsSection/EmailRequestSettings"; +import ThemeToggle from "./universal/ThemeToggle"; // Import environment variables const logtoAppId = import.meta.env.LOGTO_APP_ID; @@ -19,199 +20,238 @@ const safeLogtoAppId = logtoAppId || "missing_app_id"; const safeLogtoAppSecret = logtoAppSecret || "missing_app_secret"; const safeLogtoEndpoint = logtoEndpoint || "https://auth.ieeeucsd.org"; const safeLogtoTokenEndpoint = - logtoTokenEndpoint || "https://auth.ieeeucsd.org/oidc/token"; + logtoTokenEndpoint || "https://auth.ieeeucsd.org/oidc/token"; const safeLogtoApiEndpoint = logtoApiEndpoint || ""; ---
-
-

Settings

-

Manage your account settings and preferences

-
- - - { - import.meta.env.DEV && ( -
-

- Debug Environment Variables -

-

- This section is only visible in development mode -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VariableValueStatus
LOGTO_APP_ID{logtoAppId ? "********" : "Not set"}{logtoAppId ? "✅" : "❌"}
LOGTO_APP_SECRET{logtoAppSecret ? "********" : "Not set"}{logtoAppSecret ? "✅" : "❌"}
LOGTO_ENDPOINT{logtoEndpoint || "Not set"}{logtoEndpoint ? "✅" : "❌"}
LOGTO_TOKEN_ENDPOINT{logtoTokenEndpoint || "Not set"}{logtoTokenEndpoint ? "✅" : "❌"}
LOGTO_API_ENDPOINT{logtoApiEndpoint || "Not set"}{logtoApiEndpoint ? "✅" : "❌"}
-
-
- ) - } - - -
-
-

-
- -
- Profile Information -

-

- Update your personal information and profile details -

-
- +
+

Settings

+

Manage your account settings and preferences

-
- -
-
-

-
- -
- Resume Management -

-

- Upload and manage your resume for recruiters and career opportunities -

-
- -
-
+ + { + import.meta.env.DEV && ( +
+

+ Debug Environment Variables +

+

+ This section is only visible in development mode +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableValueStatus
LOGTO_APP_ID{logtoAppId ? "********" : "Not set"}{logtoAppId ? "✅" : "❌"}
LOGTO_APP_SECRET + {logtoAppSecret ? "********" : "Not set"} + {logtoAppSecret ? "✅" : "❌"}
LOGTO_ENDPOINT{logtoEndpoint || "Not set"}{logtoEndpoint ? "✅" : "❌"}
LOGTO_TOKEN_ENDPOINT{logtoTokenEndpoint || "Not set"}{logtoTokenEndpoint ? "✅" : "❌"}
LOGTO_API_ENDPOINT{logtoApiEndpoint || "Not set"}{logtoApiEndpoint ? "✅" : "❌"}
+
+
+ ) + } - -
-
-

-
- -
- IEEE Email Address -

-

- Request an official IEEE UCSD email address (officers only) -

-
- -
-
- - -
-
-

-
- -
- Account Security -

-

- Manage your account security settings and authentication options -

-
- -
-
- - -
- +
-
-

Coming Soon

-

- Notification settings will be available in a future update -

-
+
+

+
+ +
+ Profile Information +

+

+ Update your personal information and profile details +

+
+ +
-
-

-
- + +
+
+

+
+ +
+ Resume Management +

+

+ Upload and manage your resume for recruiters and career + opportunities +

+
+
- Notification Preferences -

-

- Customize how and when you receive notifications -

-
-
-
- -
-
-

-
- + +
+
+

+
+ +
+ IEEE Email Address +

+

+ Request an official IEEE UCSD email address (officers only) +

+
+ +
+
+ + +
+
+

+
+ +
+ Account Security +

+

+ Manage your account security settings and authentication options +

+
+ +
+
+ + +
+ +
+
+

Coming Soon

+

+ Notification settings will be available in a future update +

+
+
+ +
+

+
+ +
+ Notification Preferences +

+

+ Customize how and when you receive notifications +

+
+ +
+
+ + +
+
+

+
+ +
+ Display Settings +

+

+ Customize your dashboard appearance and display preferences +

+
+ +
+ +
+

Light Mode Experimental

+

+ Light mode is still experimental and some UI elements + may not display correctly. +

+
+
+ +
- Display Settings -

-

- Customize your dashboard appearance and display preferences -

-
-
-
diff --git a/src/components/dashboard/SettingsSection/DisplaySettings.tsx b/src/components/dashboard/SettingsSection/DisplaySettings.tsx index 1085b77..193f82b 100644 --- a/src/components/dashboard/SettingsSection/DisplaySettings.tsx +++ b/src/components/dashboard/SettingsSection/DisplaySettings.tsx @@ -3,59 +3,44 @@ import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { Update } from '../../../scripts/pocketbase/Update'; import { Collections } from '../../../schemas/pocketbase/schema'; import { toast } from 'react-hot-toast'; - -// Default display preferences -const DEFAULT_DISPLAY_PREFERENCES = { - theme: 'dark', - fontSize: 'medium' -}; - -// Default accessibility settings -const DEFAULT_ACCESSIBILITY_SETTINGS = { - colorBlindMode: false, - reducedMotion: false -}; +import { ThemeService, DEFAULT_THEME_SETTINGS, type ThemeSettings } from '../../../scripts/database/ThemeService'; export default function DisplaySettings() { const auth = Authentication.getInstance(); const update = Update.getInstance(); - const [theme, setTheme] = useState(DEFAULT_DISPLAY_PREFERENCES.theme); - const [fontSize, setFontSize] = useState(DEFAULT_DISPLAY_PREFERENCES.fontSize); - const [colorBlindMode, setColorBlindMode] = useState(DEFAULT_ACCESSIBILITY_SETTINGS.colorBlindMode); - const [reducedMotion, setReducedMotion] = useState(DEFAULT_ACCESSIBILITY_SETTINGS.reducedMotion); + const themeService = ThemeService.getInstance(); + + // Current applied settings + const [currentSettings, setCurrentSettings] = useState(null); + + // Form state (unsaved changes) + const [theme, setTheme] = useState<'light' | 'dark'>(DEFAULT_THEME_SETTINGS.theme); + const [fontSize, setFontSize] = useState(DEFAULT_THEME_SETTINGS.fontSize); + const [colorBlindMode, setColorBlindMode] = useState(DEFAULT_THEME_SETTINGS.colorBlindMode); + const [reducedMotion, setReducedMotion] = useState(DEFAULT_THEME_SETTINGS.reducedMotion); const [saving, setSaving] = useState(false); + // Track if form has unsaved changes + const [hasChanges, setHasChanges] = useState(false); + // Load saved preferences on component mount useEffect(() => { const loadPreferences = async () => { try { - // First check localStorage for immediate UI updates - const savedTheme = localStorage.getItem('theme') || DEFAULT_DISPLAY_PREFERENCES.theme; - // Ensure theme is either light or dark - const validTheme = ['light', 'dark'].includes(savedTheme) ? savedTheme : DEFAULT_DISPLAY_PREFERENCES.theme; - const savedFontSize = localStorage.getItem('fontSize') || DEFAULT_DISPLAY_PREFERENCES.fontSize; - const savedColorBlindMode = localStorage.getItem('colorBlindMode') === 'true'; - const savedReducedMotion = localStorage.getItem('reducedMotion') === 'true'; + // First load theme settings from IndexedDB + const themeSettings = await themeService.getThemeSettings(); - setTheme(validTheme); - setFontSize(savedFontSize); - setColorBlindMode(savedColorBlindMode); - setReducedMotion(savedReducedMotion); + // Store current settings + setCurrentSettings(themeSettings); - // Apply theme to document - document.documentElement.setAttribute('data-theme', validTheme); + // Set form state from theme settings + setTheme(themeSettings.theme); + setFontSize(themeSettings.fontSize); + setColorBlindMode(themeSettings.colorBlindMode); + setReducedMotion(themeSettings.reducedMotion); - // Apply font size - applyFontSize(savedFontSize); - - // Apply accessibility settings - if (savedColorBlindMode) { - document.documentElement.classList.add('color-blind-mode'); - } - - if (savedReducedMotion) { - document.documentElement.classList.add('reduced-motion'); - } + // Reset changes flag + setHasChanges(false); // Then check if user has saved preferences in their profile const user = auth.getCurrentUser(); @@ -68,20 +53,20 @@ export default function DisplaySettings() { try { const userPrefs = JSON.parse(user.display_preferences); - // Only update if values exist and are different from localStorage - if (userPrefs.theme && ['light', 'dark'].includes(userPrefs.theme) && userPrefs.theme !== validTheme) { - setTheme(userPrefs.theme); - localStorage.setItem('theme', userPrefs.theme); - document.documentElement.setAttribute('data-theme', userPrefs.theme); + // Only update if values exist and are different from IndexedDB + if (userPrefs.theme && ['light', 'dark'].includes(userPrefs.theme) && userPrefs.theme !== themeSettings.theme) { + setTheme(userPrefs.theme as 'light' | 'dark'); + // Don't update theme service yet, wait for save + setHasChanges(true); } else if (!['light', 'dark'].includes(userPrefs.theme)) { // If theme is not valid, mark for update needsDisplayPrefsUpdate = true; } - if (userPrefs.fontSize && userPrefs.fontSize !== savedFontSize) { + if (userPrefs.fontSize && userPrefs.fontSize !== themeSettings.fontSize) { setFontSize(userPrefs.fontSize); - localStorage.setItem('fontSize', userPrefs.fontSize); - applyFontSize(userPrefs.fontSize); + // Don't update theme service yet, wait for save + setHasChanges(true); } } catch (e) { console.error('Error parsing display preferences:', e); @@ -97,27 +82,17 @@ export default function DisplaySettings() { const accessibilityPrefs = JSON.parse(user.accessibility_settings); if (typeof accessibilityPrefs.colorBlindMode === 'boolean' && - accessibilityPrefs.colorBlindMode !== savedColorBlindMode) { + accessibilityPrefs.colorBlindMode !== themeSettings.colorBlindMode) { setColorBlindMode(accessibilityPrefs.colorBlindMode); - localStorage.setItem('colorBlindMode', accessibilityPrefs.colorBlindMode.toString()); - - if (accessibilityPrefs.colorBlindMode) { - document.documentElement.classList.add('color-blind-mode'); - } else { - document.documentElement.classList.remove('color-blind-mode'); - } + // Don't update theme service yet, wait for save + setHasChanges(true); } if (typeof accessibilityPrefs.reducedMotion === 'boolean' && - accessibilityPrefs.reducedMotion !== savedReducedMotion) { + accessibilityPrefs.reducedMotion !== themeSettings.reducedMotion) { setReducedMotion(accessibilityPrefs.reducedMotion); - localStorage.setItem('reducedMotion', accessibilityPrefs.reducedMotion.toString()); - - if (accessibilityPrefs.reducedMotion) { - document.documentElement.classList.add('reduced-motion'); - } else { - document.documentElement.classList.remove('reduced-motion'); - } + // Don't update theme service yet, wait for save + setHasChanges(true); } } catch (e) { console.error('Error parsing accessibility settings:', e); @@ -141,6 +116,23 @@ export default function DisplaySettings() { loadPreferences(); }, []); + // Check for changes when form values change + useEffect(() => { + if (!currentSettings) return; + + const hasThemeChanged = theme !== currentSettings.theme; + const hasFontSizeChanged = fontSize !== currentSettings.fontSize; + const hasColorBlindModeChanged = colorBlindMode !== currentSettings.colorBlindMode; + const hasReducedMotionChanged = reducedMotion !== currentSettings.reducedMotion; + + setHasChanges( + hasThemeChanged || + hasFontSizeChanged || + hasColorBlindModeChanged || + hasReducedMotionChanged + ); + }, [theme, fontSize, colorBlindMode, reducedMotion, currentSettings]); + // Initialize default settings if not set const initializeDefaultSettings = async (userId: string, updateDisplayPrefs: boolean, updateAccessibility: boolean) => { try { @@ -162,91 +154,38 @@ export default function DisplaySettings() { if (Object.keys(updateData).length > 0) { await update.updateFields(Collections.USERS, userId, updateData); - // console.log('Initialized default display and accessibility settings'); } } catch (error) { console.error('Error initializing default settings:', error); } }; - // Apply font size to document - const applyFontSize = (size: string) => { - const htmlElement = document.documentElement; - - // Remove existing font size classes - htmlElement.classList.remove('text-sm', 'text-base', 'text-lg', 'text-xl'); - - // Add new font size class - switch (size) { - case 'small': - htmlElement.classList.add('text-sm'); - break; - case 'medium': - htmlElement.classList.add('text-base'); - break; - case 'large': - htmlElement.classList.add('text-lg'); - break; - case 'extra-large': - htmlElement.classList.add('text-xl'); - break; - } - }; - // Handle theme change const handleThemeChange = (e: React.ChangeEvent) => { - const newTheme = e.target.value; + const newTheme = e.target.value as 'light' | 'dark'; setTheme(newTheme); - - // Apply theme to document - document.documentElement.setAttribute('data-theme', newTheme); - - // Save to localStorage - localStorage.setItem('theme', newTheme); + // Changes will be applied on save }; // Handle font size change const handleFontSizeChange = (e: React.ChangeEvent) => { - const newSize = e.target.value; + const newSize = e.target.value as 'small' | 'medium' | 'large' | 'extra-large'; setFontSize(newSize); - - // Apply font size - applyFontSize(newSize); - - // Save to localStorage - localStorage.setItem('fontSize', newSize); + // Changes will be applied on save }; // Handle color blind mode toggle const handleColorBlindModeChange = (e: React.ChangeEvent) => { const enabled = e.target.checked; setColorBlindMode(enabled); - - // Apply to document - if (enabled) { - document.documentElement.classList.add('color-blind-mode'); - } else { - document.documentElement.classList.remove('color-blind-mode'); - } - - // Save to localStorage - localStorage.setItem('colorBlindMode', enabled.toString()); + // Changes will be applied on save }; // Handle reduced motion toggle const handleReducedMotionChange = (e: React.ChangeEvent) => { const enabled = e.target.checked; setReducedMotion(enabled); - - // Apply to document - if (enabled) { - document.documentElement.classList.add('reduced-motion'); - } else { - document.documentElement.classList.remove('reduced-motion'); - } - - // Save to localStorage - localStorage.setItem('reducedMotion', enabled.toString()); + // Changes will be applied on save }; // Handle form submission @@ -270,7 +209,17 @@ export default function DisplaySettings() { reducedMotion }; - // Update user record + // First update IndexedDB with the new settings + await themeService.saveThemeSettings({ + id: "current", + theme, + fontSize, + colorBlindMode, + reducedMotion, + updatedAt: Date.now() + }); + + // Then update user record in PocketBase await update.updateFields( Collections.USERS, user.id, @@ -280,6 +229,19 @@ export default function DisplaySettings() { } ); + // Update current settings state to match the new settings + setCurrentSettings({ + id: "current", + theme, + fontSize, + colorBlindMode, + reducedMotion, + updatedAt: Date.now() + }); + + // Reset changes flag + setHasChanges(false); + // Show success message toast.success('Display settings saved successfully!'); } catch (error) { @@ -367,19 +329,26 @@ export default function DisplaySettings() {

- These settings are saved to your browser and your IEEE UCSD account. They will be applied whenever you log in. + These settings are saved to your browser using IndexedDB and your IEEE UCSD account. They will be applied whenever you log in.

- +
+ {hasChanges && ( +

+ You have unsaved changes. Click "Save Settings" to apply them. +

+ )} + +
); -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/scripts/database/DexieService.ts b/src/scripts/database/DexieService.ts index bde5197..24eee46 100644 --- a/src/scripts/database/DexieService.ts +++ b/src/scripts/database/DexieService.ts @@ -27,6 +27,9 @@ interface OfflineChange { syncAttempts: number; } +// Import ThemeSettings interface +import type { ThemeSettings } from "./ThemeService"; + export class DashboardDatabase extends Dexie { users!: Dexie.Table; events!: Dexie.Table; @@ -38,6 +41,7 @@ export class DashboardDatabase extends Dexie { receipts!: Dexie.Table; sponsors!: Dexie.Table; offlineChanges!: Dexie.Table; + themeSettings!: Dexie.Table; // Store last sync timestamps syncInfo!: Dexie.Table< @@ -77,6 +81,11 @@ export class DashboardDatabase extends Dexie { events: "id, event_name, event_code, start_date, end_date, published, files", }); + + // Add version 5 with themeSettings table + this.version(5).stores({ + themeSettings: "id, theme, fontSize, updatedAt", + }); } // Initialize the database with default values @@ -178,6 +187,7 @@ export class DexieService { await db.receipts.clear(); await db.sponsors.clear(); await db.offlineChanges.clear(); + // Note: We don't clear themeSettings as they should persist across logins // Reset sync timestamps const collections = [ diff --git a/src/scripts/database/ThemeService.ts b/src/scripts/database/ThemeService.ts new file mode 100644 index 0000000..36cdb58 --- /dev/null +++ b/src/scripts/database/ThemeService.ts @@ -0,0 +1,238 @@ +import Dexie from "dexie"; +import { DexieService } from "./DexieService"; + +// Check if we're in a browser environment +const isBrowser = + typeof window !== "undefined" && typeof window.indexedDB !== "undefined"; + +// Interface for theme settings +export interface ThemeSettings { + id: string; + theme: "light" | "dark"; + fontSize: "small" | "medium" | "large" | "extra-large"; + colorBlindMode: boolean; + reducedMotion: boolean; + updatedAt: number; +} + +// Default theme settings +export const DEFAULT_THEME_SETTINGS: Omit = { + theme: "dark", + fontSize: "medium", + colorBlindMode: false, + reducedMotion: false, +}; + +/** + * Service for managing theme settings using IndexedDB + */ +export class ThemeService { + private static instance: ThemeService; + private dexieService: DexieService; + + private constructor() { + this.dexieService = DexieService.getInstance(); + + // Initialize the theme table if it doesn't exist + if (isBrowser) { + const db = this.dexieService.getDB(); + + // Add theme table if it doesn't exist in the schema + if (!db.tables.some(table => table.name === "themeSettings")) { + db.version(db.verno + 1).stores({ + themeSettings: "id, theme, fontSize, updatedAt" + }); + } + } + } + + public static getInstance(): ThemeService { + if (!ThemeService.instance) { + ThemeService.instance = new ThemeService(); + } + return ThemeService.instance; + } + + /** + * Get the current theme settings + * @returns The current theme settings or default settings if none exist + */ + public async getThemeSettings(): Promise { + if (!isBrowser) { + return { + id: "default", + ...DEFAULT_THEME_SETTINGS, + updatedAt: Date.now(), + }; + } + + try { + const db = this.dexieService.getDB(); + + // Check if themeSettings table exists + if (!db.tables.some(table => table.name === "themeSettings")) { + return { + id: "default", + ...DEFAULT_THEME_SETTINGS, + updatedAt: Date.now(), + }; + } + + // Get the theme settings + const settings = await db.table("themeSettings").get("current"); + + if (!settings) { + // If no settings exist, create default settings + const defaultSettings: ThemeSettings = { + id: "current", + ...DEFAULT_THEME_SETTINGS, + updatedAt: Date.now(), + }; + + await this.saveThemeSettings(defaultSettings); + return defaultSettings; + } + + return settings; + } catch (error) { + console.error("Error getting theme settings:", error); + + // Return default settings if there's an error + return { + id: "default", + ...DEFAULT_THEME_SETTINGS, + updatedAt: Date.now(), + }; + } + } + + /** + * Save theme settings to IndexedDB + * @param settings The theme settings to save + */ + public async saveThemeSettings(settings: ThemeSettings): Promise { + if (!isBrowser) return; + + try { + const db = this.dexieService.getDB(); + + // Check if themeSettings table exists + if (!db.tables.some(table => table.name === "themeSettings")) { + return; + } + + // Update the updatedAt timestamp + settings.updatedAt = Date.now(); + + // Save the settings + await db.table("themeSettings").put(settings); + + // Apply the theme to the document + this.applyThemeToDocument(settings); + } catch (error) { + console.error("Error saving theme settings:", error); + } + } + + /** + * Update a specific theme setting + * @param key The setting key to update + * @param value The new value + */ + public async updateThemeSetting>( + key: K, + value: ThemeSettings[K] + ): Promise { + if (!isBrowser) return; + + try { + // Get current settings + const currentSettings = await this.getThemeSettings(); + + // Update the specific setting + const updatedSettings: ThemeSettings = { + ...currentSettings, + [key]: value, + updatedAt: Date.now(), + }; + + // Save the updated settings + await this.saveThemeSettings(updatedSettings); + } catch (error) { + console.error(`Error updating theme setting ${key}:`, error); + } + } + + /** + * Apply theme settings to the document + * @param settings The theme settings to apply + */ + public applyThemeToDocument(settings: ThemeSettings): void { + if (!isBrowser) return; + + // Get current theme before applying new one + const oldTheme = document.documentElement.getAttribute("data-theme"); + + // Apply theme + document.documentElement.setAttribute("data-theme", settings.theme); + + // Apply font size + document.documentElement.classList.remove("text-sm", "text-base", "text-lg", "text-xl"); + switch (settings.fontSize) { + case "small": + document.documentElement.classList.add("text-sm"); + break; + case "medium": + document.documentElement.classList.add("text-base"); + break; + case "large": + document.documentElement.classList.add("text-lg"); + break; + case "extra-large": + document.documentElement.classList.add("text-xl"); + break; + } + + // Apply accessibility settings + if (settings.colorBlindMode) { + document.documentElement.classList.add("color-blind-mode"); + } else { + document.documentElement.classList.remove("color-blind-mode"); + } + + if (settings.reducedMotion) { + document.documentElement.classList.add("reduced-motion"); + } else { + document.documentElement.classList.remove("reduced-motion"); + } + + // Dispatch theme change event if theme changed + if (oldTheme !== settings.theme) { + const event = new CustomEvent('themechange', { + detail: { + oldTheme, + newTheme: settings.theme + } + }); + window.dispatchEvent(event); + } + } + + /** + * Initialize theme from IndexedDB or create default settings + * This should be called on app startup + */ + public async initializeTheme(): Promise { + if (!isBrowser) return; + + try { + const settings = await this.getThemeSettings(); + this.applyThemeToDocument(settings); + } catch (error) { + console.error("Error initializing theme:", error); + + // Apply default theme if there's an error + document.documentElement.setAttribute("data-theme", DEFAULT_THEME_SETTINGS.theme); + } + } +} \ No newline at end of file diff --git a/src/utils/themeUtils.ts b/src/utils/themeUtils.ts new file mode 100644 index 0000000..31e7085 --- /dev/null +++ b/src/utils/themeUtils.ts @@ -0,0 +1,50 @@ +import { ThemeService } from "../scripts/database/ThemeService"; + +/** + * Initialize theme settings from IndexedDB + * This function can be used in client-side scripts + */ +export const initializeTheme = async (): Promise => { + // Check if we're in a browser environment + if (typeof window === "undefined") return; + + try { + const themeService = ThemeService.getInstance(); + await themeService.initializeTheme(); + } catch (error) { + console.error("Error initializing theme:", error); + // Apply default theme if there's an error + if (typeof document !== "undefined") { + document.documentElement.setAttribute("data-theme", "dark"); + } + } +}; + +/** + * Get the current theme + * @returns The current theme ('light' or 'dark') + */ +export const getCurrentTheme = (): 'light' | 'dark' => { + // Check if we're in a browser environment + if (typeof document === "undefined") return 'dark'; + + const theme = document.documentElement.getAttribute("data-theme"); + return (theme === 'light' ? 'light' : 'dark'); +}; + +/** + * Toggle between light and dark themes + */ +export const toggleTheme = async (): Promise => { + // Check if we're in a browser environment + if (typeof window === "undefined") return; + + try { + const themeService = ThemeService.getInstance(); + const settings = await themeService.getThemeSettings(); + const newTheme = settings.theme === 'light' ? 'dark' : 'light'; + await themeService.updateThemeSetting('theme', newTheme); + } catch (error) { + console.error("Error toggling theme:", error); + } +}; \ No newline at end of file diff --git a/tailwind.config.mjs b/tailwind.config.mjs index b649791..1c81a27 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -47,4 +47,36 @@ export default { }, heroui(), ], + daisyui: { + themes: [ + { + light: { + primary: "#06659d", + secondary: "#4b92db", + accent: "#F3C135", + neutral: "#2a323c", + "base-100": "#ffffff", + "base-200": "#f8f9fa", + "base-300": "#e9ecef", + info: "#3abff8", + success: "#36d399", + warning: "#fbbd23", + error: "#f87272", + }, + dark: { + primary: "#88BFEC", + secondary: "#4b92db", + accent: "#F3C135", + neutral: "#191D24", + "base-100": "#0A0E1A", + "base-200": "#0d1324", + "base-300": "#1a2035", + info: "#3abff8", + success: "#36d399", + warning: "#fbbd23", + error: "#f87272", + }, + }, + ], + }, };