diff --git a/apps/access-api/tsconfig.json b/apps/access-api/tsconfig.json index 85b7beb..f9cffee 100644 --- a/apps/access-api/tsconfig.json +++ b/apps/access-api/tsconfig.json @@ -5,6 +5,7 @@ "outDir": "dist", "composite": false, "baseUrl": ".", + "ignoreDeprecations": "6.0", "paths": { "@/*": ["./src/*"] } diff --git a/apps/dashboard/ThemeProvider.tsx b/apps/dashboard/ThemeProvider.tsx new file mode 100644 index 0000000..80f588e --- /dev/null +++ b/apps/dashboard/ThemeProvider.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from "react"; + +type Theme = "light" | "dark"; + +type ThemeContextValue = { + theme: Theme; + setTheme: (theme: Theme) => void; + toggleTheme: () => void; +}; + +const ThemeContext = createContext( + undefined, +); + +function applyTheme(theme: Theme) { + document.documentElement.classList.toggle("dark", theme === "dark"); +} + +export function ThemeProvider({ + children, +}: { + children: React.ReactNode; +}) { + const [theme, setThemeState] = useState("light"); + + useEffect(() => { + const storedTheme = window.localStorage.getItem("theme"); + + if (storedTheme === "light" || storedTheme === "dark") { + setThemeState(storedTheme); + applyTheme(storedTheme); + return; + } + + const systemTheme = window.matchMedia( + "(prefers-color-scheme: dark)", + ).matches + ? "dark" + : "light"; + + setThemeState(systemTheme); + applyTheme(systemTheme); + }, []); + + const setTheme = useCallback((newTheme: Theme) => { + setThemeState(newTheme); + window.localStorage.setItem("theme", newTheme); + applyTheme(newTheme); + }, []); + + const toggleTheme = useCallback(() => { + setThemeState((currentTheme) => { + const newTheme = currentTheme === "light" ? "dark" : "light"; + + window.localStorage.setItem("theme", newTheme); + applyTheme(newTheme); + + return newTheme; + }); + }, []); + + return ( + + {children} + + ); +} + +export function useTheme() { + const context = useContext(ThemeContext); + + if (!context) { + throw new Error( + "useTheme must be used within a ThemeProvider", + ); + } + + return context; +} \ No newline at end of file diff --git a/apps/dashboard/app/layout.tsx b/apps/dashboard/app/layout.tsx index 505106e..6c9ec25 100644 --- a/apps/dashboard/app/layout.tsx +++ b/apps/dashboard/app/layout.tsx @@ -1,22 +1,54 @@ import type { Metadata } from "next"; + import "./globals.css"; + import { GuildProvider } from "@/lib/guild/GuildProvider"; +import { ThemeProvider } from "@/ThemeProvider"; export const metadata: Metadata = { title: "GuildPass Dashboard", - description: "GuildPass web dashboard for managing access, passes, and communities", + description: + "GuildPass web dashboard for managing access, passes, and communities", }; +const themeScript = ` +(function() { + try { + var theme = localStorage.getItem("theme"); + + if (theme === "dark") { + document.documentElement.classList.add("dark"); + } else if (theme === "light") { + document.documentElement.classList.remove("dark"); + } else if ( + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + document.documentElement.classList.add("dark"); + } + } catch (e) {} +})(); +`; + export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( - + + +