'use client' import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react' import { usePathname, useRouter } from 'next/navigation' import { appPrivacyHref, footerPageHref } from '@/lib/footerContent' import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isStorefrontLanguage, type StorefrontLanguage } from '@/lib/i18n' import { SHARED_LANGUAGE_KEY, SHARED_THEME_KEY, readCurrentUserScopedPreference, readScopedPreference, writeScopedPreference } from '@/lib/preferences' type Theme = 'light' | 'dark' type Dictionary = { storefront: string explore: string signIn: string ownerSignIn: string language: string theme: string light: string dark: string preferences: string } const dictionaries: Record = { en: { storefront: 'Home', explore: 'Storefront', signIn: 'Sign in', ownerSignIn: 'Create Agency Space', language: 'Language', theme: 'Theme', light: 'Light', dark: 'Dark', preferences: 'Storefront preferences', }, fr: { storefront: 'Accueil', explore: 'Storefront', signIn: 'Connexion', ownerSignIn: "Créer un espace d'agence", language: 'Langue', theme: 'Mode', light: 'Clair', dark: 'Sombre', preferences: 'Préférences storefront', }, ar: { storefront: 'الرئيسية', explore: 'السوق', signIn: 'تسجيل الدخول', ownerSignIn: 'إنشاء مساحة الوكالة', language: 'اللغة', theme: 'الوضع', light: 'فاتح', dark: 'داكن', preferences: 'تفضيلات السوق', }, } const EMPLOYEE_PROFILE_KEY = 'employee_profile' export const localeOptions: Array<{ value: StorefrontLanguage; label: string; flag: string }> = [ { value: 'en', label: 'Global (English)', flag: '🇺🇸' }, { value: 'ar', label: 'North Africa (Arabic)', flag: '🇲🇦' }, { value: 'fr', label: 'Europe (French)', flag: '🇫🇷' }, ] export function getFooterContent(language: StorefrontLanguage): { primary: Array<{ label: string; href?: string }> secondary: Array<{ label: string; href?: string }> localeLabel: string rightsLabel: string } { switch (language) { case 'fr': return { primary: [ { label: 'À propos de nous', href: footerPageHref['about-us'] }, { label: "Conditions d'utilisation", href: footerPageHref['terms-of-service'] }, { label: 'Sécurité', href: footerPageHref.security }, { label: 'Conformité', href: footerPageHref.compliance }, { label: 'Politique de confidentialité', href: appPrivacyHref.fr }, { label: 'Politique relative aux cookies', href: footerPageHref['cookie-policy'] }, ], secondary: [ { label: 'Newsletter', href: footerPageHref.newsletter }, { label: 'Contacter les ventes', href: footerPageHref['contact-sales'] }, { label: 'Conditions générales', href: footerPageHref['general-conditions'] }, ], localeLabel: 'Europe (French)', rightsLabel: 'Tous droits réservés.', } case 'ar': return { primary: [ { label: 'من نحن', href: footerPageHref['about-us'] }, { label: 'شروط الاستخدام', href: footerPageHref['terms-of-service'] }, { label: 'الأمان', href: footerPageHref.security }, { label: 'الامتثال', href: footerPageHref.compliance }, { label: 'سياسة الخصوصية', href: appPrivacyHref.ar }, { label: 'سياسة ملفات تعريف الارتباط', href: footerPageHref['cookie-policy'] }, ], secondary: [ { label: 'النشرة الإخبارية', href: footerPageHref.newsletter }, { label: 'تواصل مع المبيعات', href: footerPageHref['contact-sales'] }, { label: 'الشروط العامة', href: footerPageHref['general-conditions'] }, ], localeLabel: 'North Africa (Arabic)', rightsLabel: 'جميع الحقوق محفوظة.', } case 'en': default: return { primary: [ { label: 'About Us', href: footerPageHref['about-us'] }, { label: 'Terms of Service', href: footerPageHref['terms-of-service'] }, { label: 'Security', href: footerPageHref.security }, { label: 'Compliance', href: footerPageHref.compliance }, { label: 'Privacy Policy', href: appPrivacyHref.en }, { label: 'Cookie Policy', href: footerPageHref['cookie-policy'] }, ], secondary: [ { label: 'Newsletter', href: footerPageHref.newsletter }, { label: 'Contact Sales', href: footerPageHref['contact-sales'] }, { label: 'General Conditions', href: footerPageHref['general-conditions'] }, ], localeLabel: 'Global (English)', rightsLabel: 'All rights reserved.', } } } export function hasCachedEmployeeProfile(): boolean { if (typeof window === 'undefined') return false try { return Boolean(window.localStorage.getItem(EMPLOYEE_PROFILE_KEY)) } catch { return false } } export function clearCachedEmployeeProfile() { if (typeof window === 'undefined') return try { window.localStorage.removeItem(EMPLOYEE_PROFILE_KEY) } catch {} } function detectBrowserLanguage(): string | null { if (typeof navigator === 'undefined') return null const langs = Array.from(navigator.languages ?? [navigator.language]) for (const lang of langs) { const code = lang.split('-')[0].toLowerCase() if (code === 'fr' || code === 'ar' || code === 'en') return code } return null } type PreferencesContextValue = { language: StorefrontLanguage theme: Theme dict: Dictionary companyName: string | null setLanguage: (language: StorefrontLanguage) => void setTheme: (theme: Theme) => void } const PreferencesContext = createContext(null) export function useStorefrontPreferences() { const context = useContext(PreferencesContext) if (!context) throw new Error('useStorefrontPreferences must be used within StorefrontShell') return context } export default function StorefrontShell({ children, initialLanguage = 'en', initialTheme = 'light', }: { children: React.ReactNode initialLanguage?: StorefrontLanguage initialTheme?: Theme }) { const router = useRouter() const pathname = usePathname() const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1' const [language, setLanguageState] = useState(initialLanguage) const [theme, setThemeState] = useState(initialTheme) const [hydrated, setHydrated] = useState(false) const previousLanguage = useRef(initialLanguage) const [companyName, setCompanyName] = useState(null) function applyLanguage(nextLanguage: StorefrontLanguage) { if (nextLanguage === language) return if (typeof window !== 'undefined') { document.documentElement.lang = nextLanguage document.documentElement.dir = nextLanguage === 'ar' ? 'rtl' : 'ltr' try { sessionStorage.setItem('storefront-language', nextLanguage) } catch {} writeScopedPreference(SHARED_LANGUAGE_KEY, nextLanguage, ['storefront-language']) } setLanguageState(nextLanguage) } function applyTheme(nextTheme: Theme) { if (nextTheme === theme) return if (typeof window !== 'undefined') { document.documentElement.classList.toggle('dark', nextTheme === 'dark') document.documentElement.style.colorScheme = nextTheme === 'light' ? 'light' : 'dark' document.body.dataset.theme = nextTheme writeScopedPreference(SHARED_THEME_KEY, nextTheme, ['storefront-theme']) } setThemeState(nextTheme) } async function syncCompanyBrand() { if (!hasCachedEmployeeProfile()) { setCompanyName(null) return } try { const response = await fetch(`${apiUrl}/companies/me/brand`, { credentials: 'include', }) if (!response.ok) { if (response.status === 401) { clearCachedEmployeeProfile() } setCompanyName(null) return } const json = await response.json() const name = json?.data?.displayName ?? json?.displayName ?? null setCompanyName(name) } catch { setCompanyName(null) } } function readSessionLanguage(): StorefrontLanguage | null { try { const val = sessionStorage.getItem('storefront-language') return isStorefrontLanguage(val) ? val : null } catch { return null } } function syncScopedPreferencesForSession() { const sessionLang = readSessionLanguage() if (sessionLang) { if (sessionLang !== language) setLanguageState(sessionLang) } else { const scopedLanguage = readCurrentUserScopedPreference(SHARED_LANGUAGE_KEY) if (isStorefrontLanguage(scopedLanguage) && scopedLanguage !== language) { setLanguageState(scopedLanguage) } } const scopedTheme = readCurrentUserScopedPreference(SHARED_THEME_KEY) if ((scopedTheme === 'light' || scopedTheme === 'dark') && scopedTheme !== theme) { setThemeState(scopedTheme) } else { writeScopedPreference(SHARED_THEME_KEY, theme, ['storefront-theme']) } } useEffect(() => { const sessionLang = readSessionLanguage() if (sessionLang) { setLanguageState(sessionLang) writeScopedPreference(SHARED_LANGUAGE_KEY, sessionLang, ['storefront-language']) } else { const storedLanguage = readScopedPreference(SHARED_LANGUAGE_KEY, ['storefront-language']) if (isStorefrontLanguage(storedLanguage)) { setLanguageState(storedLanguage) writeScopedPreference(SHARED_LANGUAGE_KEY, storedLanguage, ['storefront-language']) } else { const detected = detectBrowserLanguage() if (isStorefrontLanguage(detected)) { setLanguageState(detected) writeScopedPreference(SHARED_LANGUAGE_KEY, detected, ['storefront-language']) } } } const storedTheme = readScopedPreference(SHARED_THEME_KEY, ['storefront-theme']) as Theme | null if (storedTheme === 'light' || storedTheme === 'dark') { setThemeState(storedTheme) } setHydrated(true) void syncCompanyBrand() }, []) useEffect(() => { function handleWindowFocus() { syncScopedPreferencesForSession() void syncCompanyBrand() } function handleAuthMessage(event: MessageEvent) { if (!event.data || typeof event.data !== 'object') return const message = event.data as { type?: string } if (message.type === 'rentaldrivego:employee-login' || message.type === 'rentaldrivego:employee-logout') { syncScopedPreferencesForSession() void syncCompanyBrand() } } window.addEventListener('focus', handleWindowFocus) window.addEventListener('message', handleAuthMessage) document.addEventListener('visibilitychange', handleWindowFocus) return () => { window.removeEventListener('focus', handleWindowFocus) window.removeEventListener('message', handleAuthMessage) document.removeEventListener('visibilitychange', handleWindowFocus) } }, [language, theme]) useEffect(() => { document.documentElement.lang = language document.documentElement.dir = language === 'ar' ? 'rtl' : 'ltr' if (!hydrated) return try { sessionStorage.setItem('storefront-language', language) } catch {} writeScopedPreference(SHARED_LANGUAGE_KEY, language, ['storefront-language']) if (previousLanguage.current !== language && pathname !== '/sign-in') { router.refresh() } previousLanguage.current = language }, [hydrated, language, pathname, router]) useEffect(() => { document.documentElement.classList.toggle('dark', theme === 'dark') document.documentElement.style.colorScheme = theme === 'dark' ? 'dark' : 'light' document.body.dataset.theme = theme if (hydrated) { writeScopedPreference(SHARED_THEME_KEY, theme, ['storefront-theme']) } }, [theme, hydrated]) const value = useMemo( () => ({ language, theme, dict: dictionaries[language], companyName, setLanguage: applyLanguage, setTheme: applyTheme }), [language, theme, companyName], ) return {children} }