diff --git a/artifacts/skillguard/src/App.tsx b/artifacts/skillguard/src/App.tsx
index 43b324c..af80756 100644
--- a/artifacts/skillguard/src/App.tsx
+++ b/artifacts/skillguard/src/App.tsx
@@ -2,6 +2,7 @@ import { Switch, Route, Router as WouterRouter } from "wouter";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
+import { ThemeProvider } from "@/lib/theme";
import { PublicLayout } from "@/components/public-layout";
import { AppLayout } from "@/components/layout";
import { RequireAdmin } from "@/components/require-admin";
@@ -99,9 +100,11 @@ function AppRoutes() {
function App() {
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/artifacts/skillguard/src/components/layout.tsx b/artifacts/skillguard/src/components/layout.tsx
index 3a14f95..b536db1 100644
--- a/artifacts/skillguard/src/components/layout.tsx
+++ b/artifacts/skillguard/src/components/layout.tsx
@@ -1,10 +1,13 @@
import { Link, useLocation } from "wouter";
import { useTranslation } from "react-i18next";
-import { Shield, LayoutDashboard, History, Settings, LogOut, ExternalLink } from "lucide-react";
+import { Shield, LayoutDashboard, History, Settings, LogOut, ExternalLink, Sun, Moon } from "lucide-react";
import { useGetMe } from "@workspace/api-client-react";
import { useQueryClient } from "@tanstack/react-query";
import { Sidebar, SidebarContent, SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarProvider, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarFooter } from "@/components/ui/sidebar";
import { LanguageSwitcher } from "@/components/language-switcher";
+import { useTheme } from "@/lib/theme";
+import { Button } from "@/components/ui/button";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
const basePath = import.meta.env.BASE_URL.replace(/\/$/, "");
@@ -13,6 +16,7 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
const { t } = useTranslation();
const { data: me } = useGetMe();
const qc = useQueryClient();
+ const { theme, toggleTheme } = useTheme();
async function handleSignOut() {
await fetch(`${basePath}/api/auth/logout`, {
@@ -98,6 +102,18 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
+
+
+
+
+
+
+ {t(theme === "dark" ? "common.theme.switchToLight" : "common.theme.switchToDark")}
+
+
+
diff --git a/artifacts/skillguard/src/components/public-layout.tsx b/artifacts/skillguard/src/components/public-layout.tsx
index 5ac384f..2ee7fef 100644
--- a/artifacts/skillguard/src/components/public-layout.tsx
+++ b/artifacts/skillguard/src/components/public-layout.tsx
@@ -1,8 +1,10 @@
import { Link, useLocation } from "wouter";
import { useTranslation } from "react-i18next";
-import { Shield, ShieldCheck, Settings } from "lucide-react";
+import { Shield, ShieldCheck, Settings, Sun, Moon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { LanguageSwitcher } from "@/components/language-switcher";
+import { useTheme } from "@/lib/theme";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
const CATALOG_ANCHOR_ID = "skill-katalog";
@@ -18,6 +20,7 @@ function scrollToCatalog(attempts = 20) {
export function PublicLayout({ children }: { children: React.ReactNode }) {
const [location, setLocation] = useLocation();
const { t } = useTranslation();
+ const { theme, toggleTheme } = useTheme();
const handleCatalogClick = (e: React.MouseEvent) => {
e.preventDefault();
@@ -62,6 +65,16 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
+
+
+
+
+
+ {t(theme === "dark" ? "common.theme.switchToLight" : "common.theme.switchToDark")}
+
+
diff --git a/artifacts/skillguard/src/i18n/locales/de/common.ts b/artifacts/skillguard/src/i18n/locales/de/common.ts
index 530e0b4..ee2ade0 100644
--- a/artifacts/skillguard/src/i18n/locales/de/common.ts
+++ b/artifacts/skillguard/src/i18n/locales/de/common.ts
@@ -64,6 +64,10 @@ export default {
signedIn: "Angemeldet",
signOut: "Abmelden",
},
+ theme: {
+ switchToDark: "Dunkelmodus aktivieren",
+ switchToLight: "Hellmodus aktivieren",
+ },
actions: {
back: "Zurück",
cancel: "Abbrechen",
diff --git a/artifacts/skillguard/src/i18n/locales/en/common.ts b/artifacts/skillguard/src/i18n/locales/en/common.ts
index 17b31ca..6fb2cb6 100644
--- a/artifacts/skillguard/src/i18n/locales/en/common.ts
+++ b/artifacts/skillguard/src/i18n/locales/en/common.ts
@@ -64,6 +64,10 @@ export default {
signedIn: "Signed in",
signOut: "Sign out",
},
+ theme: {
+ switchToDark: "Switch to dark mode",
+ switchToLight: "Switch to light mode",
+ },
actions: {
back: "Back",
cancel: "Cancel",
diff --git a/artifacts/skillguard/src/i18n/locales/es/common.ts b/artifacts/skillguard/src/i18n/locales/es/common.ts
index 068e0f5..db109de 100644
--- a/artifacts/skillguard/src/i18n/locales/es/common.ts
+++ b/artifacts/skillguard/src/i18n/locales/es/common.ts
@@ -64,6 +64,10 @@ export default {
signedIn: "Sesión iniciada",
signOut: "Cerrar sesión",
},
+ theme: {
+ switchToDark: "Activar modo oscuro",
+ switchToLight: "Activar modo claro",
+ },
actions: {
back: "Atrás",
cancel: "Cancelar",
diff --git a/artifacts/skillguard/src/lib/theme.tsx b/artifacts/skillguard/src/lib/theme.tsx
new file mode 100644
index 0000000..88ee2af
--- /dev/null
+++ b/artifacts/skillguard/src/lib/theme.tsx
@@ -0,0 +1,54 @@
+import { createContext, useContext, useEffect, useState } from "react";
+
+type Theme = "light" | "dark";
+
+const STORAGE_KEY = "skillguard-theme";
+
+function getInitialTheme(): Theme {
+ try {
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored === "dark" || stored === "light") return stored;
+ } catch {}
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+}
+
+function applyTheme(theme: Theme) {
+ document.documentElement.classList.toggle("dark", theme === "dark");
+}
+
+interface ThemeContextValue {
+ theme: Theme;
+ toggleTheme: () => void;
+}
+
+const ThemeContext = createContext({
+ theme: "light",
+ toggleTheme: () => {},
+});
+
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const [theme, setTheme] = useState(() => {
+ const t = getInitialTheme();
+ applyTheme(t);
+ return t;
+ });
+
+ useEffect(() => {
+ applyTheme(theme);
+ try { localStorage.setItem(STORAGE_KEY, theme); } catch {}
+ }, [theme]);
+
+ function toggleTheme() {
+ setTheme(t => (t === "dark" ? "light" : "dark"));
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ return useContext(ThemeContext);
+}