'use client' import { ChevronDown, Globe } from 'lucide-react' import Image from 'next/image' import Link from 'next/link' import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react' import { usePathname, useRouter } from 'next/navigation' import { footerPageHref } from '@/lib/footerContent' import { MARKETPLACE_LANGUAGE_COOKIE, SHARED_LANGUAGE_COOKIE, isMarketplaceLanguage, type MarketplaceLanguage } from '@/lib/i18n' import { resolveBrowserAppUrl } from '@/lib/appUrls' import { SHARED_LANGUAGE_KEY, SHARED_THEME_KEY, readCurrentUserScopedPreference, readScopedPreference, writeScopedPreference } from '@/lib/preferences' type Theme = 'light' | 'medium' | 'dark' type Dictionary = { marketplace: string explore: string signIn: string ownerSignIn: string language: string theme: string light: string medium: string dark: string preferences: string } const dictionaries: Record = { en: { marketplace: 'Home', explore: 'Marketplace', signIn: 'Sign in', ownerSignIn: 'Create account', language: 'Language', theme: 'Theme', light: 'Light', medium: 'Medium', dark: 'Dark', preferences: 'Marketplace preferences', }, fr: { marketplace: 'Accueil', explore: 'Marketplace', signIn: 'Connexion', ownerSignIn: 'Créer un compte', language: 'Langue', theme: 'Mode', light: 'Clair', medium: 'Moyen', dark: 'Sombre', preferences: 'Préférences marketplace', }, ar: { marketplace: 'الرئيسية', explore: 'السوق', signIn: 'تسجيل الدخول', ownerSignIn: 'إنشاء حساب', language: 'اللغة', theme: 'الوضع', light: 'فاتح', medium: 'متوسط', dark: 'داكن', preferences: 'تفضيلات السوق', }, } type PreferencesContextValue = { language: MarketplaceLanguage theme: Theme dict: Dictionary setLanguage: (language: MarketplaceLanguage) => void setTheme: (theme: Theme) => void } const PreferencesContext = createContext(null) type FooterItem = { label: string href?: string } const localeOptions: Array<{ value: MarketplaceLanguage; label: string }> = [ { value: 'en', label: 'Global (English)' }, { value: 'ar', label: 'NorthAfrica (Arabic)' }, { value: 'fr', label: 'Europ (French)' }, ] function getFooterContent(language: MarketplaceLanguage): { primary: FooterItem[] secondary: FooterItem[] 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: 'Europ (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: 'NorthAfrica (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 SegmentedControl({ label, value, options, onChange, }: { label: string value: T options: { value: T; label: string }[] onChange: (value: T) => void }) { return (
{label}
{options.map((option) => { const active = option.value === value return ( ) })}
) } 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 }: { children: React.ReactNode }) { const pathname = usePathname() const router = useRouter() const dashboardUrl = resolveBrowserAppUrl(process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3001') const adminUrl = resolveBrowserAppUrl(process.env.NEXT_PUBLIC_ADMIN_URL ?? 'http://localhost:3002') const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1' const [language, setLanguage] = useState('en') const [theme, setTheme] = useState('light') const [hydrated, setHydrated] = useState(false) const previousLanguage = useRef('en') const localeMenuRef = useRef(null) const [companyName, setCompanyName] = useState(null) const [localeMenuOpen, setLocaleMenuOpen] = useState(false) 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 syncScopedPreferencesForSession() { const scopedLanguage = readCurrentUserScopedPreference(SHARED_LANGUAGE_KEY) const scopedTheme = readCurrentUserScopedPreference(SHARED_THEME_KEY) if (isMarketplaceLanguage(scopedLanguage) && scopedLanguage !== language) { setLanguage(scopedLanguage) } else { writeScopedPreference(SHARED_LANGUAGE_KEY, language, ['marketplace-language']) } if ((scopedTheme === 'light' || scopedTheme === 'medium' || scopedTheme === 'dark') && scopedTheme !== theme) { setTheme(scopedTheme) } else { writeScopedPreference(SHARED_THEME_KEY, theme, ['marketplace-theme']) } } useEffect(() => { const storedLanguage = readScopedPreference(SHARED_LANGUAGE_KEY, ['marketplace-language']) const storedTheme = readScopedPreference(SHARED_THEME_KEY, ['marketplace-theme']) as Theme | null if (isMarketplaceLanguage(storedLanguage)) { setLanguage(storedLanguage) } if (storedTheme === 'light' || storedTheme === 'medium' || storedTheme === 'dark') { setTheme(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' document.documentElement.classList.toggle('dark', theme === 'dark' || theme === 'medium') document.body.dataset.theme = theme writeScopedPreference(SHARED_LANGUAGE_KEY, language, ['marketplace-language']) writeScopedPreference(SHARED_THEME_KEY, theme, ['marketplace-theme']) document.cookie = `${MARKETPLACE_LANGUAGE_COOKIE}=${language}; path=/; max-age=31536000; SameSite=Lax` document.cookie = `${SHARED_LANGUAGE_COOKIE}=${language}; path=/; max-age=31536000; SameSite=Lax` if (hydrated && previousLanguage.current !== language) { router.refresh() } previousLanguage.current = language }, [hydrated, language, router, theme]) useEffect(() => { function handlePointerDown(event: MouseEvent) { if (!localeMenuRef.current?.contains(event.target as Node)) { setLocaleMenuOpen(false) } } document.addEventListener('mousedown', handlePointerDown) return () => document.removeEventListener('mousedown', handlePointerDown) }, []) const dict = dictionaries[language] const footerContent = getFooterContent(language) const availableLocaleOptions = localeOptions.filter((option) => option.value !== language) const isRenterApp = pathname.startsWith('/renter/') const isEmbeddedWorkspace = pathname === '/branded-website' || isRenterApp const hideHeader = isRenterApp const value = useMemo( () => ({ language, theme, dict, setLanguage, setTheme }), [dict, language, theme], ) return (
{!hideHeader ? (
RentalDriveGo RentalDriveGo
) : null}
{children}
{!isEmbeddedWorkspace ? (
{footerContent.secondary.map((item) => (
))}
{localeMenuOpen ? (
{availableLocaleOptions.map((option) => ( ))}
) : null}

© {new Date().getFullYear()} RentalDriveGo. {footerContent.rightsLabel}

) : null}
) } function FooterNavItem({ item }: { item: FooterItem }) { const className = 'px-3 text-stone-600 transition hover:text-stone-950 dark:text-stone-300 dark:hover:text-white' if (item.href) { return ( {item.label} ) } return {item.label} }