diff --git a/public/scripts/theme-init.js b/public/scripts/theme-init.js index 12adc95f47..937d3d7ac1 100644 --- a/public/scripts/theme-init.js +++ b/public/scripts/theme-init.js @@ -1,14 +1,22 @@ (() => { + let theme: string | null = null; + let defaultDark = false; + let navbarColor: string | null = null; + let navbarTextColor: string | null = null; try { - var s = localStorage.getItem("theme"); - var dd = document.querySelector('meta[name="theme-default-dark"]'); - var defaultDark = dd ? dd.getAttribute("content") === "true" : false; - if (s === "dark" || (!s && defaultDark)) - document.documentElement.classList.add("dark"); - var nc = localStorage.getItem("navbarColor"), - nt = localStorage.getItem("navbarTextColor"); - if (nc) document.documentElement.style.setProperty("--color-navbar", nc); - if (nt) - document.documentElement.style.setProperty("--color-navbar-text", nt); + theme = localStorage.getItem("theme"); + const dd = document.querySelector('meta[name="theme-default-dark"]'); + defaultDark = dd ? dd.getAttribute("content") === "true" : false; + navbarColor = localStorage.getItem("navbarColor"); + navbarTextColor = localStorage.getItem("navbarTextColor"); } catch (_e) {} + if (theme === "dark" || (!theme && defaultDark)) + document.documentElement.classList.add("dark"); + if (navbarColor) + document.documentElement.style.setProperty("--color-navbar", navbarColor); + if (navbarTextColor) + document.documentElement.style.setProperty( + "--color-navbar-text", + navbarTextColor, + ); })(); diff --git a/src/actions/set-locale.ts b/src/actions/set-locale.ts new file mode 100644 index 0000000000..6fcddafbc9 --- /dev/null +++ b/src/actions/set-locale.ts @@ -0,0 +1,13 @@ +"use server"; + +import { cookies } from "next/headers"; + +export async function setLocaleCookie(code: string): Promise { + const store = await cookies(); + store.set("NEXT_LOCALE", code, { + path: "/", + maxAge: 31536000, + sameSite: "strict", + secure: true, + }); +} diff --git a/src/app/admin/housekeeping/page.tsx b/src/app/admin/housekeeping/page.tsx index ff86ed5e8b..2562acef59 100644 --- a/src/app/admin/housekeeping/page.tsx +++ b/src/app/admin/housekeeping/page.tsx @@ -48,7 +48,9 @@ export default async function AdminHousekeeping({ .split(",") .map((i) => Number(i.trim())) .filter((i) => !Number.isNaN(i)); - importIds.forEach((id) => selectedIds.add(id)); + importIds.forEach((id) => { + selectedIds.add(id); + }); } } diff --git a/src/app/admin/polls/poll-form.tsx b/src/app/admin/polls/poll-form.tsx index 2a31944155..9888388828 100644 --- a/src/app/admin/polls/poll-form.tsx +++ b/src/app/admin/polls/poll-form.tsx @@ -41,7 +41,7 @@ export function PollForm({ defaultValues }: PollFormProps) { // biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code function onSubmit(data: any) { if (isEditing) { - run(() => updatePoll({ id: defaultValues?.id!, ...data }), { + run(() => updatePoll({ id: defaultValues!.id, ...data }), { successMessage: "Poll updated!", }); } else { diff --git a/src/app/admin/radio/api-keys/page.tsx b/src/app/admin/radio/api-keys/page.tsx index 4eef95ec11..bfba7b210d 100644 --- a/src/app/admin/radio/api-keys/page.tsx +++ b/src/app/admin/radio/api-keys/page.tsx @@ -78,7 +78,7 @@ export default async function AdminRadioApiKeysPage() { {t("autoDj")} ·{" "} {t("embed")} ·{" "} {t("points")} ·{" "} - {t("monitoring")} + {t("monitoring.title")}
diff --git a/src/app/admin/radio/autodj/page.tsx b/src/app/admin/radio/autodj/page.tsx index 05056b4b33..f2196202f4 100644 --- a/src/app/admin/radio/autodj/page.tsx +++ b/src/app/admin/radio/autodj/page.tsx @@ -137,7 +137,7 @@ export default async function AdminRadioAutoDjPage() { htmlFor="new_title" style={{ display: "block", fontWeight: 700 }} > - {t("autoDjForm.title")} + {t("autoDjForm.trackTitle")} { // The create endpoint records ownership before returning the approval URL. // Do not let a signed-in user submit somebody else's approved order id. - let authorized; + let authorized: AuthorizedTopup; try { authorized = await authorizeTopupCapture( userId, @@ -138,7 +140,7 @@ export async function POST(req: Request): Promise { } } - let result; + let result: CaptureResult; try { result = await captureOrder(orderId); } catch (e) { diff --git a/src/app/error.tsx b/src/app/error.tsx index 2cefa1a810..bd08cc9356 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -8,7 +8,7 @@ import { useEffect } from "react"; * stays), shows a friendly card, and offers a reset. The raw error is logged to * the console, never shown to the user. */ -export default function Error({ +export default function ErrorPage({ error, reset, }: { diff --git a/src/app/globals.css b/src/app/globals.css index a30056822f..c942587ee0 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -205,7 +205,9 @@ summary, letter-spacing: 0.03em; text-transform: uppercase; white-space: nowrap; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out; + transition: + color 0.15s ease-in-out, + background-color 0.15s ease-in-out; border-radius: 0.5rem; } .nav-item:hover { @@ -223,7 +225,9 @@ summary, .dropdown-item { @apply block py-2.5 px-4 text-sm font-semibold rounded-lg mx-1; color: var(--color-dropdown-text-readable, var(--color-text-readable)); - transition: background-color 0.1s ease-in-out, color 0.1s ease-in-out; + transition: + background-color 0.1s ease-in-out, + color 0.1s ease-in-out; } .dropdown-item:hover { background-color: color-mix(in srgb, var(--color-primary) 12%, transparent); diff --git a/src/app/help/[category]/page.tsx b/src/app/help/[category]/page.tsx index 9ed9ef765c..e5b587920c 100644 --- a/src/app/help/[category]/page.tsx +++ b/src/app/help/[category]/page.tsx @@ -117,11 +117,12 @@ export default async function HelpCategoryPage({ /> ) : null} - {/* content is author-supplied HTML — sanitised server-side. */} -
+ {/* content is author-supplied HTML — sanitised server-side. */} + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: sanitised article body */} +
{hasButton ? (
diff --git a/src/app/me/page.tsx b/src/app/me/page.tsx index ff87802016..4a42e8a548 100644 --- a/src/app/me/page.tsx +++ b/src/app/me/page.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import { redirect } from "next/navigation"; -import type { CSSProperties } from "react"; +import type { CSSProperties, ReactNode } from "react"; import { claimReferral } from "@/actions/referral"; import { ContentCard, @@ -70,7 +70,7 @@ export default async function MePage({ const { claimed, error } = await searchParams; - let content; + let content: ReactNode; try { // --- Account + reward config ----------------------------------------- const [user, imagerBase, neededRaw, amountRaw, currencyRaw] = diff --git a/src/components/language-switcher.tsx b/src/components/language-switcher.tsx index f22b129958..2ea6b38b2f 100644 --- a/src/components/language-switcher.tsx +++ b/src/components/language-switcher.tsx @@ -5,6 +5,7 @@ import Image from "next/image"; import { useRouter } from "next/navigation"; import { useLocale } from "next-intl"; import { useEffect, useMemo, useRef, useState, useTransition } from "react"; +import { setLocaleCookie } from "@/actions/set-locale"; const LOCALES: { code: string; label: string; lang: string }[] = [ { code: "nl", label: "Nederlands", lang: "nl" }, @@ -66,8 +67,10 @@ export function LanguageSwitcher() { function switchLocale(code: string) { if (code === locale) return; - document.cookie = `NEXT_LOCALE=${code};path=/;max-age=31536000;samesite=strict;secure`; - startTransition(() => router.refresh()); + startTransition(async () => { + await setLocaleCookie(code); + router.refresh(); + }); setOpen(false); } diff --git a/src/components/mobile-nav.tsx b/src/components/mobile-nav.tsx index 3eb239cba1..3200005e38 100644 --- a/src/components/mobile-nav.tsx +++ b/src/components/mobile-nav.tsx @@ -1,8 +1,7 @@ "use client"; -import { type ReactNode, useRef } from "react"; import Image from "next/image"; -import Link from "next/link"; +import { type ReactNode, useRef } from "react"; interface MobileNavProps { children: ReactNode; @@ -37,7 +36,7 @@ export function MobileNav({ hover:bg-[color-mix(in_srgb,var(--color-primary)_12%,transparent)] active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary-readable,var(--color-primary))] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-navbar)]" - aria-label={"Menu"} + aria-label={menuLabel} > {brandLabel} diff --git a/src/components/nav-dropdown.tsx b/src/components/nav-dropdown.tsx index 2eaf4bbdba..5210bcd636 100644 --- a/src/components/nav-dropdown.tsx +++ b/src/components/nav-dropdown.tsx @@ -1,5 +1,5 @@ import Image from "next/image"; -import { type ReactNode } from "react"; +import type { ReactNode } from "react"; interface NavDropdownProps { label: string; diff --git a/src/components/top-header.tsx b/src/components/top-header.tsx index e55225741e..1321a14efa 100644 --- a/src/components/top-header.tsx +++ b/src/components/top-header.tsx @@ -14,7 +14,8 @@ function Currency({ amount: number; label: string; }) { - const textColor = "var(--color-navbar-text-readable, var(--color-navbar-text))"; + const textColor = + "var(--color-navbar-text-readable, var(--color-navbar-text))"; const valueColor = "var(--color-navbar-text-readable, var(--color-navbar-text, var(--color-text)))"; @@ -24,7 +25,10 @@ function Currency({ className={`h-[20px] w-[20px] sm:h-[25px] sm:w-[25px] rounded-full outline-offset-[3px] ${icon}`} />
- + {amount.toLocaleString()} {" "} {label} diff --git a/src/lib/foundation/database.ts b/src/lib/foundation/database.ts index e79aca4e92..05749da425 100644 --- a/src/lib/foundation/database.ts +++ b/src/lib/foundation/database.ts @@ -152,5 +152,5 @@ export class DbService { } } -export const db = globalForDb._db ?? (globalForDb._db = new DbService()); +export const db = globalForDb._db ?? new DbService(); if (env.NODE_ENV !== "production") globalForDb._db = db; diff --git a/src/lib/redis.ts b/src/lib/redis.ts index da44a572e7..27dca96b7c 100644 --- a/src/lib/redis.ts +++ b/src/lib/redis.ts @@ -22,9 +22,8 @@ function createRedis(): Redis | null { } export const redis: Redis | null = - globalForRedis.redis !== undefined - ? globalForRedis.redis - : (globalForRedis.redis = createRedis()); + globalForRedis.redis !== undefined ? globalForRedis.redis : createRedis(); +if (globalForRedis.redis === undefined) globalForRedis.redis = redis; export async function withRedis( fallback: () => Promise, diff --git a/src/messages/en.json b/src/messages/en.json index 996bbf272a..2e75c6d4a6 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -1462,7 +1462,6 @@ "apiKeys": "API Keys", "points": "Points", "autoDj": "AutoDJ", - "monitoring": "Monitoring", "user": "User", "message": "Message", "posted": "Posted", @@ -1652,7 +1651,7 @@ "active": "Active", "inactive": "Inactive", "addTrack": "Add track", - "title": "Title", + "trackTitle": "Title", "titlePlaceholder": "Track title", "artist": "Artist", "album": "Album", diff --git a/src/messages/nl.json b/src/messages/nl.json index d64fa5ac20..fb0f8a5e7b 100644 --- a/src/messages/nl.json +++ b/src/messages/nl.json @@ -1462,7 +1462,6 @@ "apiKeys": "API-sleutels", "points": "Punten", "autoDj": "AutoDJ", - "monitoring": "Monitoring", "user": "User", "message": "Message", "posted": "Posted", @@ -1652,7 +1651,7 @@ "active": "Active", "inactive": "Inactive", "addTrack": "Add track", - "title": "Title", + "trackTitle": "Title", "titlePlaceholder": "Track title", "artist": "Artist", "album": "Album",