fix bug#7
Build & Push / Build & Push Docker Image (push) Failing after 2m50s
Test / Type Check (all packages) (push) Failing after 55s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped

This commit is contained in:
root
2026-07-20 01:01:21 -04:00
parent 7ce0a5adf3
commit fb7b4eefc1
33 changed files with 342 additions and 87 deletions
+9 -4
View File
@@ -1,10 +1,11 @@
import type { Metadata } from 'next'
import { cookies } from 'next/headers'
import { JetBrains_Mono } from 'next/font/google'
import Script from 'next/script'
import '@fontsource-variable/inter/index.css'
import '@fontsource-variable/noto-sans-arabic/index.css'
import { DashboardI18nProvider } from '@/components/I18nProvider'
import { SHARED_LANGUAGE_COOKIE } from '@/lib/preferences'
import { HOMEPAGE_THEME_COOKIE, SHARED_LANGUAGE_COOKIE, SHARED_THEME_KEY } from '@/lib/preferences'
import './globals.css'
const jetbrainsMono = JetBrains_Mono({
@@ -33,14 +34,18 @@ export default async function RootLayout({ children }: { children: React.ReactNo
cookieStore.get(SHARED_LANGUAGE_COOKIE)?.value ?? cookieStore.get('dashboard-language')?.value,
)
const dir = initialLanguage === 'ar' ? 'rtl' : 'ltr'
const cookieTheme = cookieStore.get(SHARED_THEME_KEY)?.value ?? cookieStore.get(HOMEPAGE_THEME_COOKIE)?.value
const initialTheme = cookieTheme === 'light' ? 'light' : 'dark'
return (
<html lang={initialLanguage} dir={dir} className="light" suppressHydrationWarning>
<html lang={initialLanguage} dir={dir} className={initialTheme} suppressHydrationWarning>
<head>
<script
<Script
id="dashboard-theme-bootstrap"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html:
"(function(){try{var m=document.cookie.match(/(?:^|; )rentaldrivego-theme=([^;]+)/);var theme=m?decodeURIComponent(m[1]):(localStorage.getItem('rentaldrivego-theme')||localStorage.getItem('dashboard-theme'));if(theme!=='light'&&theme!=='dark'){theme=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}var rootTheme=theme==='light'?'light':'dark';document.documentElement.classList.remove('light','dark');document.documentElement.classList.add(rootTheme);document.documentElement.style.colorScheme=rootTheme;document.body&&document.body.setAttribute('data-theme',theme)}catch(e){}})();",
"(function(){try{var q=new URLSearchParams(location.search).get('theme');var m=document.cookie.match(/(?:^|; )rentaldrivego-theme=([^;]+)/);var h=document.cookie.match(/(?:^|; )hpc-theme=([^;]+)/);var theme=(q==='light'||q==='dark')?q:(m?decodeURIComponent(m[1]):(h?decodeURIComponent(h[1]):(localStorage.getItem('rentaldrivego-theme')||localStorage.getItem('dashboard-theme')||localStorage.getItem('hpc.theme.preference')||'dark')));if(theme!=='light'&&theme!=='dark'){theme='dark'}document.documentElement.classList.remove('light','dark');document.documentElement.classList.add(theme);document.documentElement.style.colorScheme=theme}catch(e){}})();",
}}
/>
</head>
+15 -15
View File
@@ -1,7 +1,7 @@
'use client'
import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { SHARED_LANGUAGE_COOKIE, SHARED_LANGUAGE_KEY, SHARED_THEME_KEY, readCurrentUserScopedPreference, readScopedPreference, writeScopedPreference } from '@/lib/preferences'
import { HOMEPAGE_THEME_COOKIE, HOMEPAGE_THEME_KEY, SHARED_LANGUAGE_COOKIE, SHARED_LANGUAGE_KEY, SHARED_THEME_KEY, readCurrentUserScopedPreference, readScopedPreference, writeScopedPreference } from '@/lib/preferences'
import { apiFetch } from '@/lib/api'
export type DashboardLanguage = 'en' | 'fr' | 'ar'
@@ -1427,7 +1427,7 @@ export function DashboardI18nProvider({
initialLanguage?: DashboardLanguage
}) {
const [language, setLanguageState] = useState<DashboardLanguage>(initialLanguage)
const [theme, setThemeState] = useState<DashboardTheme>('light')
const [theme, setThemeState] = useState<DashboardTheme>('dark')
const themeInitialized = useRef(false)
// Skip the very first write so we don't overwrite a stored preference with the
// server-resolved initialLanguage before the read effect has a chance to apply it.
@@ -1455,7 +1455,7 @@ export function DashboardI18nProvider({
document.documentElement.classList.add(nextTheme === 'light' ? 'light' : 'dark')
document.documentElement.style.colorScheme = nextTheme === 'light' ? 'light' : 'dark'
document.body.dataset.theme = nextTheme
writeScopedPreference(SHARED_THEME_KEY, nextTheme, ['dashboard-theme'])
writeScopedPreference(SHARED_THEME_KEY, nextTheme, ['dashboard-theme', HOMEPAGE_THEME_KEY], [HOMEPAGE_THEME_COOKIE])
}
setThemeState(nextTheme)
@@ -1473,15 +1473,14 @@ export function DashboardI18nProvider({
}, [])
useEffect(() => {
const storedTheme = readScopedPreference(SHARED_THEME_KEY, ['dashboard-theme'])
const queryTheme = new URLSearchParams(window.location.search).get('theme')
const storedTheme = queryTheme === 'light' || queryTheme === 'dark'
? queryTheme
: readScopedPreference(SHARED_THEME_KEY, ['dashboard-theme', HOMEPAGE_THEME_KEY], [HOMEPAGE_THEME_COOKIE])
if (storedTheme === 'light' || storedTheme === 'dark') {
if (storedTheme !== theme) setThemeState(storedTheme)
return
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches && theme !== 'dark') {
setThemeState('dark')
}
}, [])
useEffect(() => {
@@ -1497,16 +1496,17 @@ export function DashboardI18nProvider({
}, [language])
useEffect(() => {
if (!themeInitialized.current) {
// Skip first run: inline script already applied the correct class; writing 'light' here would flash and corrupt storage.
themeInitialized.current = true
return
}
document.documentElement.classList.remove('light', 'dark')
document.documentElement.classList.add(theme === 'light' ? 'light' : 'dark')
document.documentElement.style.colorScheme = theme === 'light' ? 'light' : 'dark'
document.body.dataset.theme = theme
writeScopedPreference(SHARED_THEME_KEY, theme, ['dashboard-theme'])
if (!themeInitialized.current) {
// Skip first write: inline script already applied the correct class; writing too early can corrupt stored preferences.
themeInitialized.current = true
return
}
writeScopedPreference(SHARED_THEME_KEY, theme, ['dashboard-theme', HOMEPAGE_THEME_KEY], [HOMEPAGE_THEME_COOKIE])
}, [theme])
useEffect(() => {
@@ -1530,7 +1530,7 @@ export function DashboardI18nProvider({
if (scopedTheme === 'light' || scopedTheme === 'dark') {
if (scopedTheme !== theme) setThemeState(scopedTheme)
} else {
writeScopedPreference(SHARED_THEME_KEY, theme, ['dashboard-theme'])
writeScopedPreference(SHARED_THEME_KEY, theme, ['dashboard-theme', HOMEPAGE_THEME_KEY], [HOMEPAGE_THEME_COOKIE])
}
}
@@ -84,6 +84,17 @@ describe('dashboard scoped preferences', () => {
expect(readScopedPreference('theme', ['legacy-theme'])).toBe('base')
})
it('can read and write legacy cookie names for cross-app preferences', () => {
const browser = installBrowser('hpc-theme=dark')
expect(readScopedPreference('rentaldrivego-theme', [], ['hpc-theme'])).toBe('dark')
writeScopedPreference('rentaldrivego-theme', 'light', [], ['hpc-theme'])
expect(browser.cookie).toContain('rentaldrivego-theme=light')
expect(browser.cookie).toContain('hpc-theme=light')
})
it('writes shared and legacy preference values without auth-token scoping', () => {
const browser = installBrowser()
+21 -2
View File
@@ -1,6 +1,8 @@
export const SHARED_LANGUAGE_COOKIE = 'rentaldrivego-language'
export const SHARED_LANGUAGE_KEY = 'rentaldrivego-language'
export const SHARED_THEME_KEY = 'rentaldrivego-theme'
export const HOMEPAGE_THEME_COOKIE = 'hpc-theme'
export const HOMEPAGE_THEME_KEY = 'hpc.theme.preference'
function readEmployeeToken() {
return null
@@ -54,7 +56,11 @@ export function readCurrentUserScopedPreference(baseKey: string) {
return window.localStorage.getItem(getScopedPreferenceKey(baseKey))
}
export function readScopedPreference(baseKey: string, legacyKeys: string[] = []) {
export function readScopedPreference(
baseKey: string,
legacyKeys: string[] = [],
legacyCookieNames: string[] = [],
) {
if (typeof window === 'undefined') return null
const scopedCookie = readCookie(getScopedPreferenceCookieName(baseKey))
@@ -63,6 +69,11 @@ export function readScopedPreference(baseKey: string, legacyKeys: string[] = [])
const sharedCookie = readCookie(baseKey)
if (sharedCookie) return sharedCookie
for (const cookieName of legacyCookieNames) {
const legacyCookie = readCookie(cookieName)
if (legacyCookie) return legacyCookie
}
const scopedKey = getScopedPreferenceKey(baseKey)
const candidates = [scopedKey, baseKey, ...legacyKeys]
@@ -74,7 +85,12 @@ export function readScopedPreference(baseKey: string, legacyKeys: string[] = [])
return null
}
export function writeScopedPreference(baseKey: string, value: string, legacyKeys: string[] = []) {
export function writeScopedPreference(
baseKey: string,
value: string,
legacyKeys: string[] = [],
legacyCookieNames: string[] = [],
) {
if (typeof window === 'undefined') return
const scopedKey = getScopedPreferenceKey(baseKey)
@@ -84,6 +100,9 @@ export function writeScopedPreference(baseKey: string, value: string, legacyKeys
if (scopedCookie !== baseKey) {
writeCookie(scopedCookie, value)
}
for (const cookieName of legacyCookieNames) {
writeCookie(cookieName, value)
}
window.localStorage.setItem(scopedKey, value)
window.localStorage.setItem(baseKey, value)