From 68fb272e67027b19dec43a94d612408b2a121c3d Mon Sep 17 00:00:00 2001 From: Isha Dewangan Date: Fri, 17 Jul 2026 15:35:06 +0530 Subject: [PATCH 1/2] feat: implement theme persistence with localStorage and add tests --- dashboard/index.html | 6 +++ dashboard/src/faculty/FacultySettingsPage.tsx | 22 +---------- dashboard/src/lib/theme.ts | 20 ++++++++++ dashboard/src/pages/SettingsPage.tsx | 22 +---------- e2e/tests/student-theme.spec.ts | 38 +++++++++++++++++++ playwright.config.ts | 2 +- 6 files changed, 69 insertions(+), 41 deletions(-) create mode 100644 dashboard/src/lib/theme.ts create mode 100644 e2e/tests/student-theme.spec.ts diff --git a/dashboard/index.html b/dashboard/index.html index 7da8104..893b4a0 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -5,6 +5,12 @@ CS17 Portal + diff --git a/dashboard/src/faculty/FacultySettingsPage.tsx b/dashboard/src/faculty/FacultySettingsPage.tsx index e91516e..88c6d0b 100644 --- a/dashboard/src/faculty/FacultySettingsPage.tsx +++ b/dashboard/src/faculty/FacultySettingsPage.tsx @@ -9,29 +9,11 @@ import { DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; - -function getInitialTheme(): "light" | "dark" { - try { - const stored = localStorage.getItem("theme"); - if (stored === "dark" || stored === "light") return stored; - } catch {} - return document.documentElement.classList.contains("dark") ? "dark" : "light"; -} - -function applyTheme(theme: "light" | "dark") { - if (theme === "dark") { - document.documentElement.classList.add("dark"); - } else { - document.documentElement.classList.remove("dark"); - } - try { - localStorage.setItem("theme", theme); - } catch {} -} +import { applyTheme, getStoredTheme, type Theme } from "@/lib/theme"; export default function FacultySettingsPage() { const { profile } = useCurrentProfile(); - const [theme, setTheme] = useState<"light" | "dark">(getInitialTheme); + const [theme, setTheme] = useState(getStoredTheme); const [showLogoutDialog, setShowLogoutDialog] = useState(false); function toggleTheme() { diff --git a/dashboard/src/lib/theme.ts b/dashboard/src/lib/theme.ts new file mode 100644 index 0000000..5bac4c2 --- /dev/null +++ b/dashboard/src/lib/theme.ts @@ -0,0 +1,20 @@ +export type Theme = "light" | "dark"; + +export function getStoredTheme(): Theme { + try { + const stored = localStorage.getItem("theme"); + if (stored === "dark" || stored === "light") return stored; + } catch {} + return document.documentElement.classList.contains("dark") ? "dark" : "light"; +} + +export function applyTheme(theme: Theme): void { + setThemeClass(theme); + try { + localStorage.setItem("theme", theme); + } catch {} +} + +function setThemeClass(theme: Theme): void { + document.documentElement.classList.toggle("dark", theme === "dark"); +} diff --git a/dashboard/src/pages/SettingsPage.tsx b/dashboard/src/pages/SettingsPage.tsx index 7db5428..29c14ae 100644 --- a/dashboard/src/pages/SettingsPage.tsx +++ b/dashboard/src/pages/SettingsPage.tsx @@ -9,29 +9,11 @@ import { DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; - -function getInitialTheme(): "light" | "dark" { - try { - const stored = localStorage.getItem("theme"); - if (stored === "dark" || stored === "light") return stored; - } catch {} - return document.documentElement.classList.contains("dark") ? "dark" : "light"; -} - -function applyTheme(theme: "light" | "dark") { - if (theme === "dark") { - document.documentElement.classList.add("dark"); - } else { - document.documentElement.classList.remove("dark"); - } - try { - localStorage.setItem("theme", theme); - } catch {} -} +import { applyTheme, getStoredTheme, type Theme } from "@/lib/theme"; export default function SettingsPage() { const { student } = useCurrentStudent(); - const [theme, setTheme] = useState<"light" | "dark">(getInitialTheme); + const [theme, setTheme] = useState(getStoredTheme); const [showLogoutDialog, setShowLogoutDialog] = useState(false); function toggleTheme() { diff --git a/e2e/tests/student-theme.spec.ts b/e2e/tests/student-theme.spec.ts new file mode 100644 index 0000000..fc86a6b --- /dev/null +++ b/e2e/tests/student-theme.spec.ts @@ -0,0 +1,38 @@ +import { test, expect } from "@playwright/test"; + +test.describe("Theme persistence", () => { + test("keeps dark mode across a refresh and on other pages", async ({ page }) => { + await page.goto("/dashboard/settings"); + await page.getByRole("switch").click(); + await expect(page.locator("html")).toHaveClass(/dark/); + + await page.reload(); + await expect(page.locator("html")).toHaveClass(/dark/); + await expect(page.getByRole("switch")).toHaveAttribute("aria-checked", "true"); + + await page.goto("/dashboard/projects"); + await expect(page.locator("html")).toHaveClass(/dark/); + + await page.goto("/dashboard/settings"); + await page.getByRole("switch").click(); + await page.reload(); + await expect(page.locator("html")).not.toHaveClass(/dark/); + }); + + test("applies dark before the app bundle runs, so there is no flash", async ({ + page, + }) => { + await page.goto("/dashboard/settings"); + await page.getByRole("switch").click(); + await expect(page.locator("html")).toHaveClass(/dark/); + + await page.route("**/dashboard/assets/*.js", async (route) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + await route.continue(); + }); + + await page.goto("/dashboard/projects", { waitUntil: "commit" }); + await expect(page.locator("html")).toHaveClass(/dark/, { timeout: 1000 }); + await expect(page.locator("#root")).toBeEmpty(); + }); +}); diff --git a/playwright.config.ts b/playwright.config.ts index 52c9900..895dfeb 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -4,7 +4,7 @@ import path from "path"; const authFile = path.join(__dirname, "e2e", ".auth", "user.json"); const studentAuthFile = path.join(__dirname, "e2e", ".auth", "student.json"); const facultyAuthFile = path.join(__dirname, "e2e", ".auth", "faculty.json"); -const studentSpecs = /student-submission\.spec\.ts/; +const studentSpecs = /(student-submission|student-theme)\.spec\.ts/; const facultySpecs = /faculty-assignments-ui\.spec\.ts/; const SITE_HOST = process.env.SITE_HOST || "cs17.portal:8000"; From 48668887c0b60917111fb4bd1474bb79164b98b6 Mon Sep 17 00:00:00 2001 From: Isha Dewangan Date: Mon, 20 Jul 2026 12:03:42 +0530 Subject: [PATCH 2/2] fix: update comment for theme persistence in index.html --- dashboard/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/dashboard/index.html b/dashboard/index.html index 893b4a0..ea59423 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -5,6 +5,7 @@ CS17 Portal +