Files
GMW/services/frontend/src/islands/SettingsForm.tsx
T
asepharyana 8ad888da28 feat: migrate frontend to Astro SSG with design system
- Replace monolithic React SPA (App.tsx) with 7 Astro pages (+ routing)
- Implement full design system from design/ (OKLCH tokens, Tailwind 4 @theme)
- Add 12 Astro components (Button, Badge, Card, Sidebar, Header, states)
- Add 11 React islands (AuthGuard, MessageFeed, VoiceControls, AudioVisualizer, etc.)
- Add 3 Zustand stores (UI, voice, message state)
- Add CSS architecture: dark/light themes, glassmorphism, fluid typography, animations
- Add 404, login, settings, recordings pages
- Remove 40+ legacy React SPA files
- Add Particles background and MascotChat deferred islands
- Wire WebSocket bridge for real-time messages and voice events

Build: 7 pages in ~900ms
Typecheck: clean (0 errors)
Lint: clean (0 errors)
2026-07-02 01:18:45 +07:00

90 lines
2.6 KiB
TypeScript

// ─── SettingsForm.tsx — Appearance settings island ─────────────────────────
// Reads/writes theme preference (dark/light/system) to localStorage key
// "bete-dashboard-theme", applies data-theme attribute and .dark class.
// ─────────────────────────────────────────────────────────────────────────────
import { useCallback, useEffect, useState } from "react";
type Theme = "dark" | "light" | "system";
const STORAGE_KEY = "bete-dashboard-theme";
const THEME_OPTIONS: { value: Theme; label: string }[] = [
{ value: "dark", label: "Dark" },
{ value: "light", label: "Light" },
{ value: "system", label: "System" },
];
function getStoredTheme(): Theme {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === "dark" || stored === "light" || stored === "system") {
return stored;
}
} catch {
// localStorage unavailable
}
return "dark";
}
function resolveTheme(theme: Theme): "dark" | "light" {
if (theme === "system") {
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
return theme;
}
function applyTheme(theme: Theme): void {
const root = document.documentElement;
const actual = resolveTheme(theme);
root.setAttribute("data-theme", actual);
root.classList.toggle("dark", actual === "dark");
}
function storeTheme(theme: Theme): void {
try {
localStorage.setItem(STORAGE_KEY, theme);
} catch {
// quota exceeded or unavailable
}
}
export default function SettingsForm() {
const [theme, setTheme] = useState<Theme>(getStoredTheme);
useEffect(() => {
applyTheme(theme);
storeTheme(theme);
}, [theme]);
const handleThemeChange = useCallback((value: Theme) => {
setTheme(value);
}, []);
return (
<section>
<h2 className="mb-4 text-lg font-semibold text-foreground">Appearance</h2>
<p className="mb-4 text-sm text-muted-foreground">
Choose your preferred color scheme for the dashboard.
</p>
<div className="flex gap-2">
{THEME_OPTIONS.map((option) => (
<button
key={option.value}
type="button"
className={`btn ${
theme === option.value ? "btn--primary" : "btn--outline"
} btn--sm`}
onClick={() => handleThemeChange(option.value)}
aria-pressed={theme === option.value}
>
{option.label}
</button>
))}
</div>
</section>
);
}