fix notification and add billing page and contract
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
|
||||
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'
|
||||
|
||||
export type DashboardLanguage = 'en' | 'fr' | 'ar'
|
||||
export type DashboardTheme = 'light' | 'dark'
|
||||
export type DashboardTheme = 'light' | 'medium' | 'dark'
|
||||
|
||||
type FleetDict = {
|
||||
statusLabels: Record<'AVAILABLE' | 'RENTED' | 'MAINTENANCE' | 'OUT_OF_SERVICE', string>
|
||||
@@ -294,6 +295,7 @@ type DashboardDictionary = {
|
||||
language: string
|
||||
theme: string
|
||||
light: string
|
||||
medium: string
|
||||
dark: string
|
||||
fleet: FleetDict
|
||||
vehicleDetail: VehicleDetailDict
|
||||
@@ -307,26 +309,31 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
nav: {
|
||||
dashboard: 'Dashboard',
|
||||
fleet: 'Fleet',
|
||||
reservations: 'Reservations',
|
||||
reservations: 'Booking',
|
||||
onlineReservations: 'Online Reservations',
|
||||
customers: 'Customers',
|
||||
offers: 'Offers',
|
||||
team: 'Team',
|
||||
reports: 'Reports',
|
||||
billing: 'Billing',
|
||||
subscription: 'Subscription',
|
||||
billing: 'Rent Car Billing',
|
||||
contracts: 'Contracts',
|
||||
notifications: 'Notifications',
|
||||
settings: 'Settings',
|
||||
},
|
||||
titles: {
|
||||
'/dashboard': 'Dashboard',
|
||||
'/dashboard/fleet': 'Fleet Management',
|
||||
'/dashboard/reservations': 'Reservations',
|
||||
'/dashboard/reservations': 'Booking',
|
||||
'/dashboard/reservations/new': 'Book Car',
|
||||
'/dashboard/online-reservations': 'Online Reservations',
|
||||
'/dashboard/customers': 'Customers',
|
||||
'/dashboard/offers': 'Offers',
|
||||
'/dashboard/team': 'Team',
|
||||
'/dashboard/reports': 'Reports',
|
||||
'/dashboard/billing': 'Billing',
|
||||
'/dashboard/subscription': 'Subscription',
|
||||
'/dashboard/billing': 'Rent Car Billing',
|
||||
'/dashboard/contracts': 'Contracts',
|
||||
'/dashboard/notifications': 'Notifications',
|
||||
'/dashboard/settings': 'Settings',
|
||||
},
|
||||
@@ -339,6 +346,7 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
language: 'Language',
|
||||
theme: 'Theme',
|
||||
light: 'Light',
|
||||
medium: 'Medium',
|
||||
dark: 'Dark',
|
||||
fleet: {
|
||||
statusLabels: { AVAILABLE: 'Available', RENTED: 'Rented', MAINTENANCE: 'Maintenance', OUT_OF_SERVICE: 'Out of Service' },
|
||||
@@ -572,11 +580,11 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
trialActive: "You're on a free trial.",
|
||||
pastDueMsg: 'Your payment is past due. Please update your billing information.',
|
||||
suspendedMsg: 'Your account has been suspended. Please contact support or renew your subscription.',
|
||||
manageBilling: 'Manage billing →',
|
||||
manageBilling: 'Manage subscription →',
|
||||
statusLabels: { CONFIRMED: 'Confirmed', PENDING: 'Pending', ACTIVE: 'Active', COMPLETED: 'Completed', CANCELLED: 'Cancelled' },
|
||||
},
|
||||
reservations: {
|
||||
heading: 'Reservations',
|
||||
heading: 'Booking',
|
||||
subtitle: 'All booking sources, including dashboard, public site, and marketplace.',
|
||||
colCustomer: 'Customer',
|
||||
colVehicle: 'Vehicle',
|
||||
@@ -650,7 +658,9 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
offers: 'Offres',
|
||||
team: 'Équipe',
|
||||
reports: 'Rapports',
|
||||
billing: 'Facturation',
|
||||
subscription: 'Abonnement',
|
||||
billing: 'Facturation location',
|
||||
contracts: 'Contrats',
|
||||
notifications: 'Notifications',
|
||||
settings: 'Paramètres',
|
||||
},
|
||||
@@ -658,12 +668,15 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
'/dashboard': 'Tableau de bord',
|
||||
'/dashboard/fleet': 'Gestion de flotte',
|
||||
'/dashboard/reservations': 'Réservations',
|
||||
'/dashboard/reservations/new': 'Réserver une voiture',
|
||||
'/dashboard/online-reservations': 'Réservations en ligne',
|
||||
'/dashboard/customers': 'Clients',
|
||||
'/dashboard/offers': 'Offres',
|
||||
'/dashboard/team': 'Équipe',
|
||||
'/dashboard/reports': 'Rapports',
|
||||
'/dashboard/billing': 'Facturation',
|
||||
'/dashboard/subscription': 'Abonnement',
|
||||
'/dashboard/billing': 'Facturation location',
|
||||
'/dashboard/contracts': 'Contrats',
|
||||
'/dashboard/notifications': 'Notifications',
|
||||
'/dashboard/settings': 'Paramètres',
|
||||
},
|
||||
@@ -676,6 +689,7 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
language: 'Langue',
|
||||
theme: 'Mode',
|
||||
light: 'Clair',
|
||||
medium: 'Moyen',
|
||||
dark: 'Sombre',
|
||||
fleet: {
|
||||
statusLabels: { AVAILABLE: 'Disponible', RENTED: 'Loué', MAINTENANCE: 'Maintenance', OUT_OF_SERVICE: 'Hors service' },
|
||||
@@ -909,7 +923,7 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
trialActive: "Vous êtes en période d'essai.",
|
||||
pastDueMsg: 'Votre paiement est en retard. Veuillez mettre à jour vos informations de facturation.',
|
||||
suspendedMsg: 'Votre compte a été suspendu. Veuillez contacter le support ou renouveler votre abonnement.',
|
||||
manageBilling: 'Gérer la facturation →',
|
||||
manageBilling: 'Gérer l’abonnement →',
|
||||
statusLabels: { CONFIRMED: 'Confirmé', PENDING: 'En attente', ACTIVE: 'Actif', COMPLETED: 'Terminé', CANCELLED: 'Annulé' },
|
||||
},
|
||||
reservations: {
|
||||
@@ -987,7 +1001,9 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
offers: 'العروض',
|
||||
team: 'الفريق',
|
||||
reports: 'التقارير',
|
||||
billing: 'الفوترة',
|
||||
subscription: 'الاشتراك',
|
||||
billing: 'فوترة تأجير السيارات',
|
||||
contracts: 'العقود',
|
||||
notifications: 'الإشعارات',
|
||||
settings: 'الإعدادات',
|
||||
},
|
||||
@@ -995,12 +1011,15 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
'/dashboard': 'لوحة التحكم',
|
||||
'/dashboard/fleet': 'إدارة الأسطول',
|
||||
'/dashboard/reservations': 'الحجوزات',
|
||||
'/dashboard/reservations/new': 'حجز سيارة',
|
||||
'/dashboard/online-reservations': 'الحجوزات الإلكترونية',
|
||||
'/dashboard/customers': 'العملاء',
|
||||
'/dashboard/offers': 'العروض',
|
||||
'/dashboard/team': 'الفريق',
|
||||
'/dashboard/reports': 'التقارير',
|
||||
'/dashboard/billing': 'الفوترة',
|
||||
'/dashboard/subscription': 'الاشتراك',
|
||||
'/dashboard/billing': 'فوترة تأجير السيارات',
|
||||
'/dashboard/contracts': 'العقود',
|
||||
'/dashboard/notifications': 'الإشعارات',
|
||||
'/dashboard/settings': 'الإعدادات',
|
||||
},
|
||||
@@ -1013,6 +1032,7 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
language: 'اللغة',
|
||||
theme: 'الوضع',
|
||||
light: 'فاتح',
|
||||
medium: 'متوسط',
|
||||
dark: 'داكن',
|
||||
fleet: {
|
||||
statusLabels: { AVAILABLE: 'متاح', RENTED: 'مؤجر', MAINTENANCE: 'صيانة', OUT_OF_SERVICE: 'خارج الخدمة' },
|
||||
@@ -1246,7 +1266,7 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
|
||||
trialActive: 'أنت في فترة تجريبية مجانية.',
|
||||
pastDueMsg: 'دفعتك متأخرة. يرجى تحديث معلومات الفوترة.',
|
||||
suspendedMsg: 'تم تعليق حسابك. يرجى التواصل مع الدعم أو تجديد اشتراكك.',
|
||||
manageBilling: 'إدارة الفوترة ←',
|
||||
manageBilling: 'إدارة الاشتراك ←',
|
||||
statusLabels: { CONFIRMED: 'مؤكد', PENDING: 'قيد الانتظار', ACTIVE: 'نشط', COMPLETED: 'مكتمل', CANCELLED: 'ملغى' },
|
||||
},
|
||||
reservations: {
|
||||
@@ -1325,25 +1345,38 @@ type I18nContextValue = {
|
||||
}
|
||||
|
||||
const I18nContext = createContext<I18nContextValue | null>(null)
|
||||
const DASHBOARD_LANGUAGE_KEY = 'dashboard-language'
|
||||
|
||||
export function DashboardI18nProvider({ children }: { children: React.ReactNode }) {
|
||||
const [language, setLanguage] = useState<DashboardLanguage>('en')
|
||||
function normalizeLanguage(value: string | null | undefined): DashboardLanguage {
|
||||
return value === 'fr' || value === 'ar' || value === 'en' ? value : 'en'
|
||||
}
|
||||
|
||||
export function DashboardI18nProvider({
|
||||
children,
|
||||
initialLanguage = 'en',
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
initialLanguage?: DashboardLanguage
|
||||
}) {
|
||||
const [language, setLanguage] = useState<DashboardLanguage>(initialLanguage)
|
||||
const [theme, setTheme] = useState<DashboardTheme>('light')
|
||||
const themeInitialized = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const stored = window.localStorage.getItem('dashboard-language')
|
||||
const storedTheme = window.localStorage.getItem('dashboard-theme')
|
||||
const stored = readScopedPreference(SHARED_LANGUAGE_KEY, [DASHBOARD_LANGUAGE_KEY])
|
||||
if (stored === null) return
|
||||
const next = normalizeLanguage(stored)
|
||||
if (next !== language) setLanguage(next)
|
||||
}, [])
|
||||
|
||||
if (stored === 'en' || stored === 'fr' || stored === 'ar') {
|
||||
setLanguage(stored)
|
||||
}
|
||||
|
||||
if (storedTheme === 'light' || storedTheme === 'dark') {
|
||||
setTheme(storedTheme)
|
||||
useEffect(() => {
|
||||
const storedTheme = readScopedPreference(SHARED_THEME_KEY, ['dashboard-theme'])
|
||||
if (storedTheme === 'light' || storedTheme === 'medium' || storedTheme === 'dark') {
|
||||
if (storedTheme !== theme) setTheme(storedTheme)
|
||||
return
|
||||
}
|
||||
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches && theme !== 'dark') {
|
||||
setTheme('dark')
|
||||
}
|
||||
}, [])
|
||||
@@ -1351,17 +1384,46 @@ export function DashboardI18nProvider({ children }: { children: React.ReactNode
|
||||
useEffect(() => {
|
||||
document.documentElement.lang = language
|
||||
document.documentElement.dir = language === 'ar' ? 'rtl' : 'ltr'
|
||||
window.localStorage.setItem('dashboard-language', language)
|
||||
writeScopedPreference(SHARED_LANGUAGE_KEY, language, [DASHBOARD_LANGUAGE_KEY])
|
||||
document.cookie = `${SHARED_LANGUAGE_COOKIE}=${language}; path=/; max-age=31536000; samesite=lax`
|
||||
document.cookie = `${DASHBOARD_LANGUAGE_KEY}=${language}; path=/; max-age=31536000; samesite=lax`
|
||||
}, [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)
|
||||
document.documentElement.style.colorScheme = theme
|
||||
document.documentElement.classList.add(theme === 'light' ? 'light' : 'dark')
|
||||
document.documentElement.style.colorScheme = theme === 'light' ? 'light' : 'dark'
|
||||
document.body.dataset.theme = theme
|
||||
window.localStorage.setItem('dashboard-theme', theme)
|
||||
writeScopedPreference(SHARED_THEME_KEY, theme, ['dashboard-theme'])
|
||||
}, [theme])
|
||||
|
||||
useEffect(() => {
|
||||
function handleAuthChanged() {
|
||||
const scopedLanguage = readCurrentUserScopedPreference(SHARED_LANGUAGE_KEY)
|
||||
const scopedTheme = readCurrentUserScopedPreference(SHARED_THEME_KEY)
|
||||
|
||||
if (scopedLanguage && normalizeLanguage(scopedLanguage) !== language) {
|
||||
setLanguage(normalizeLanguage(scopedLanguage))
|
||||
} else {
|
||||
writeScopedPreference(SHARED_LANGUAGE_KEY, language, [DASHBOARD_LANGUAGE_KEY])
|
||||
}
|
||||
|
||||
if (scopedTheme === 'light' || scopedTheme === 'medium' || scopedTheme === 'dark') {
|
||||
if (scopedTheme !== theme) setTheme(scopedTheme)
|
||||
} else {
|
||||
writeScopedPreference(SHARED_THEME_KEY, theme, ['dashboard-theme'])
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('rentaldrivego:auth-changed', handleAuthChanged as EventListener)
|
||||
return () => window.removeEventListener('rentaldrivego:auth-changed', handleAuthChanged as EventListener)
|
||||
}, [language, theme])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({ language, setLanguage, theme, setTheme, dict: dictionaries[language] }),
|
||||
[language, theme],
|
||||
@@ -1426,7 +1488,7 @@ export function DashboardThemeSwitcher() {
|
||||
<span className="px-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500 dark:text-slate-400">
|
||||
{dict.theme}
|
||||
</span>
|
||||
{(['light', 'dark'] as DashboardTheme[]).map((value) => {
|
||||
{(['light', 'medium', 'dark'] as DashboardTheme[]).map((value) => {
|
||||
const active = value === theme
|
||||
return (
|
||||
<button
|
||||
@@ -1439,7 +1501,7 @@ export function DashboardThemeSwitcher() {
|
||||
: 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800'
|
||||
}`}
|
||||
>
|
||||
{value === 'light' ? dict.light : dict.dark}
|
||||
{value === 'light' ? dict.light : value === 'medium' ? dict.medium : dict.dark}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user