'use client' import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react' import { useRouter } from 'next/navigation' import { footerPageHref } from '@/lib/footerContent' import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isMarketplaceLanguage, type MarketplaceLanguage } from '@/lib/i18n' import { SHARED_LANGUAGE_KEY, SHARED_THEME_KEY, readCurrentUserScopedPreference, readScopedPreference, writeScopedPreference } from '@/lib/preferences' type Theme = 'light' | 'dark' type Dictionary = { marketplace: string explore: string signIn: string ownerSignIn: string language: string theme: string light: string dark: string preferences: string } const dictionaries: Record = { en: { marketplace: 'Home', explore: 'Marketplace', signIn: 'Sign in', ownerSignIn: 'Create Agency Space', language: 'Language', theme: 'Theme', light: 'Light', dark: 'Dark', preferences: 'Marketplace preferences', }, fr: { marketplace: 'Accueil', explore: 'Marketplace', signIn: 'Connexion', ownerSignIn: "Créer un espace d'agence", language: 'Langue', theme: 'Mode', light: 'Clair', dark: 'Sombre', preferences: 'Préférences marketplace', }, ar: { marketplace: 'الرئيسية', explore: 'السوق', signIn: 'تسجيل الدخول', ownerSignIn: 'إنشاء مساحة الوكالة', language: 'اللغة', theme: 'الوضع', light: 'فاتح', dark: 'داكن', preferences: 'تفضيلات السوق', }, } export const localeOptions: Array<{ value: MarketplaceLanguage; 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: MarketplaceLanguage): { 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: footerPageHref['privacy-policy'] }, { label: 'Politique relative aux cookies', href: footerPageHref['cookie-policy'] }, ], secondary: [ { label: 'Newsletter', href: footerPageHref.newsletter }, { label: 'Contacter les ventes', href: footerPageHref['contact-sales'] }, { label: 'Nos bureaux', href: footerPageHref['our-offices'] }, ], 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: footerPageHref['privacy-policy'] }, { label: 'سياسة ملفات تعريف الارتباط', href: footerPageHref['cookie-policy'] }, ], secondary: [ { label: 'النشرة الإخبارية', href: footerPageHref.newsletter }, { label: 'تواصل مع المبيعات', href: footerPageHref['contact-sales'] }, { label: 'مكاتبنا', href: footerPageHref['our-offices'] }, ], 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: footerPageHref['privacy-policy'] }, { label: 'Cookie Policy', href: footerPageHref['cookie-policy'] }, ], secondary: [ { label: 'Newsletter', href: footerPageHref.newsletter }, { label: 'Contact Sales', href: footerPageHref['contact-sales'] }, { label: 'Our Offices', href: footerPageHref['our-offices'] }, ], localeLabel: 'Global (English)', rightsLabel: 'All rights reserved.', } } } 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: MarketplaceLanguage theme: Theme dict: Dictionary companyName: string | null setLanguage: (language: MarketplaceLanguage) => void setTheme: (theme: Theme) => void } const PreferencesContext = createContext(null) export function useMarketplacePreferences() { const context = useContext(PreferencesContext) if (!context) throw new Error('useMarketplacePreferences must be used within MarketplaceShell') return context } export default function MarketplaceShell({ children, initialLanguage = 'en', }: { children: React.ReactNode initialLanguage?: MarketplaceLanguage }) { const router = useRouter() const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1' const [language, setLanguageState] = useState(initialLanguage) const [theme, setThemeState] = useState('light') const [hydrated, setHydrated] = useState(false) const previousLanguage = useRef(initialLanguage) const [companyName, setCompanyName] = useState(null) function applyLanguage(nextLanguage: MarketplaceLanguage) { if (nextLanguage === language) return if (typeof window !== 'undefined') { document.documentElement.lang = nextLanguage document.documentElement.dir = nextLanguage === 'ar' ? 'rtl' : 'ltr' try { sessionStorage.setItem('marketplace-language', nextLanguage) } catch {} writeScopedPreference(SHARED_LANGUAGE_KEY, nextLanguage, ['marketplace-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, ['marketplace-theme']) } setThemeState(nextTheme) } async function syncCompanyBrand() { const token = document.cookie .split(';') .map((c) => c.trim()) .find((c) => c.startsWith('employee_token=')) ?.split('=')[1] if (!token) { setCompanyName(null) return } try { const response = await fetch(`${apiUrl}/companies/me/brand`, { headers: { Authorization: `Bearer ${token}` }, }) if (!response.ok) { setCompanyName(null) return } const json = await response.json() const name = json?.data?.displayName ?? json?.displayName ?? null setCompanyName(name) } catch { setCompanyName(null) } } function readSessionLanguage(): MarketplaceLanguage | null { try { const val = sessionStorage.getItem('marketplace-language') return isMarketplaceLanguage(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 (isMarketplaceLanguage(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, ['marketplace-theme']) } } useEffect(() => { const sessionLang = readSessionLanguage() if (sessionLang) { setLanguageState(sessionLang) writeScopedPreference(SHARED_LANGUAGE_KEY, sessionLang, ['marketplace-language']) } else { const storedLanguage = readScopedPreference(SHARED_LANGUAGE_KEY, ['marketplace-language']) if (isMarketplaceLanguage(storedLanguage)) { setLanguageState(storedLanguage) writeScopedPreference(SHARED_LANGUAGE_KEY, storedLanguage, ['marketplace-language']) } else { const detected = detectBrowserLanguage() if (isMarketplaceLanguage(detected)) { setLanguageState(detected) writeScopedPreference(SHARED_LANGUAGE_KEY, detected, ['marketplace-language']) } } } const storedTheme = readScopedPreference(SHARED_THEME_KEY, ['marketplace-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('marketplace-language', language) } catch {} writeScopedPreference(SHARED_LANGUAGE_KEY, language, ['marketplace-language']) if (previousLanguage.current !== language) { router.refresh() } previousLanguage.current = language }, [hydrated, language, router]) useEffect(() => { document.documentElement.classList.toggle('dark', theme === 'dark') document.body.dataset.theme = theme writeScopedPreference(SHARED_THEME_KEY, theme, ['marketplace-theme']) }, [theme]) const value = useMemo( () => ({ language, theme, dict: dictionaries[language], companyName, setLanguage: applyLanguage, setTheme: applyTheme }), [language, theme, companyName], ) return {children} }