fix notification and add billing page and contract
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import {
|
||||
LayoutDashboard,
|
||||
User,
|
||||
Bookmark,
|
||||
Bell,
|
||||
LogOut,
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
Car,
|
||||
} from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useMarketplacePreferences } from '@/components/MarketplaceShell'
|
||||
import { clearRenterSession, loadRenterProfile } from '@/lib/renter'
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ href: '/renter/dashboard', key: 'dashboard', icon: LayoutDashboard, exact: true },
|
||||
{ href: '/renter/profile', key: 'profile', icon: User },
|
||||
{ href: '/renter/saved-companies', key: 'savedCompanies', icon: Bookmark },
|
||||
{ href: '/renter/notifications', key: 'notifications', icon: Bell },
|
||||
] as const
|
||||
|
||||
const dicts = {
|
||||
en: {
|
||||
dashboard: 'Dashboard',
|
||||
profile: 'Profile',
|
||||
savedCompanies: 'Saved companies',
|
||||
notifications: 'Notifications',
|
||||
signOut: 'Sign out',
|
||||
renterPortal: 'Renter Portal',
|
||||
rights: 'All rights reserved.',
|
||||
},
|
||||
fr: {
|
||||
dashboard: 'Tableau de bord',
|
||||
profile: 'Profil',
|
||||
savedCompanies: 'Entreprises sauvegardées',
|
||||
notifications: 'Notifications',
|
||||
signOut: 'Déconnexion',
|
||||
renterPortal: 'Espace client',
|
||||
rights: 'Tous droits réservés.',
|
||||
},
|
||||
ar: {
|
||||
dashboard: 'لوحة التحكم',
|
||||
profile: 'الملف الشخصي',
|
||||
savedCompanies: 'الشركات المحفوظة',
|
||||
notifications: 'الإشعارات',
|
||||
signOut: 'تسجيل الخروج',
|
||||
renterPortal: 'بوابة المستأجر',
|
||||
rights: 'جميع الحقوق محفوظة.',
|
||||
},
|
||||
} as const
|
||||
|
||||
function computeInitials(name: string): string {
|
||||
const parts = name.trim().split(/\s+/).filter(Boolean)
|
||||
if (parts.length === 0) return 'R'
|
||||
if (parts.length === 1) return parts[0].slice(0, 1).toUpperCase()
|
||||
return `${parts[0].slice(0, 1)}${parts[1].slice(0, 1)}`.toUpperCase()
|
||||
}
|
||||
|
||||
export default function RenterShell({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
const { language } = useMarketplacePreferences()
|
||||
const dict = dicts[language]
|
||||
const isRtl = language === 'ar'
|
||||
const [open, setOpen] = useState(false)
|
||||
const [userInitials, setUserInitials] = useState('R')
|
||||
const [userName, setUserName] = useState<string | null>(null)
|
||||
const [userEmail, setUserEmail] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
loadRenterProfile()
|
||||
.then((profile) => {
|
||||
const fullName = `${profile.firstName ?? ''} ${profile.lastName ?? ''}`.trim()
|
||||
setUserName(fullName || profile.email.split('@')[0])
|
||||
setUserEmail(profile.email)
|
||||
setUserInitials(computeInitials(fullName || profile.email))
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(false)
|
||||
}, [pathname])
|
||||
|
||||
function signOut() {
|
||||
clearRenterSession()
|
||||
router.push('/renter/sign-in')
|
||||
}
|
||||
|
||||
const isActive = (item: (typeof NAV_ITEMS)[number]) => {
|
||||
if ('exact' in item && item.exact) return pathname === item.href
|
||||
return pathname.startsWith(item.href)
|
||||
}
|
||||
|
||||
const activeNav = NAV_ITEMS.find((item) => isActive(item))
|
||||
const pageTitle = activeNav ? (dict[activeNav.key as keyof typeof dict] as string) : dict.dashboard
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-stone-50 dark:bg-stone-950">
|
||||
{open && (
|
||||
<div
|
||||
className="fixed inset-0 z-30 bg-black/50 lg:hidden"
|
||||
onClick={() => setOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!open && (
|
||||
<button
|
||||
onClick={() => setOpen(true)}
|
||||
aria-label="Open sidebar"
|
||||
className={[
|
||||
'fixed top-1/2 z-50 -translate-y-1/2 flex items-center justify-center w-6 h-14 bg-stone-900 text-white shadow-lg lg:hidden',
|
||||
isRtl ? 'right-0 rounded-l-lg' : 'left-0 rounded-r-lg',
|
||||
].join(' ')}
|
||||
>
|
||||
{isRtl ? <ChevronLeft className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<aside
|
||||
className={[
|
||||
'fixed inset-y-0 z-40 flex w-64 flex-col bg-stone-900 transition-transform duration-300',
|
||||
isRtl ? 'right-0' : 'left-0',
|
||||
'lg:translate-x-0',
|
||||
open ? 'translate-x-0' : isRtl ? 'translate-x-full' : '-translate-x-full',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex items-center gap-3 border-b border-stone-800 px-6 py-5">
|
||||
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-amber-500 overflow-hidden">
|
||||
<Car className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<span className="truncate text-sm font-bold tracking-wide text-white">
|
||||
{dict.renterPortal}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 space-y-0.5 overflow-y-auto px-3 py-4">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const Icon = item.icon
|
||||
const active = isActive(item)
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={[
|
||||
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
|
||||
active
|
||||
? 'bg-amber-500 text-white'
|
||||
: 'text-stone-400 hover:bg-stone-800 hover:text-white',
|
||||
].join(' ')}
|
||||
>
|
||||
<Icon className="h-4 w-4 flex-shrink-0" />
|
||||
{dict[item.key as keyof typeof dict] as string}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-stone-800 px-3 py-4">
|
||||
<div className="flex items-center gap-3 rounded-lg px-3 py-2">
|
||||
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-amber-500 text-xs font-semibold text-white">
|
||||
{userInitials}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
{userName && (
|
||||
<p className="truncate text-sm font-medium text-white">{userName}</p>
|
||||
)}
|
||||
{userEmail && (
|
||||
<p className="truncate text-xs text-stone-400">{userEmail}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={signOut}
|
||||
className="mt-2 flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm text-stone-400 transition-colors hover:bg-stone-800 hover:text-white"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
{dict.signOut}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
aria-label="Close sidebar"
|
||||
className={[
|
||||
'absolute top-1/2 z-10 -translate-y-1/2 flex items-center justify-center w-5 h-14 bg-stone-900 text-white shadow-lg lg:hidden',
|
||||
isRtl ? '-left-5 rounded-l-lg' : '-right-5 rounded-r-lg',
|
||||
].join(' ')}
|
||||
>
|
||||
{isRtl ? <ChevronRight className="h-3.5 w-3.5" /> : <ChevronLeft className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<div
|
||||
className={[
|
||||
'flex min-h-screen min-w-0 flex-1 flex-col',
|
||||
isRtl ? 'lg:mr-64' : 'lg:ml-64',
|
||||
].join(' ')}
|
||||
>
|
||||
<header className="flex h-16 items-center justify-between border-b border-stone-200 bg-white px-6 transition-colors dark:border-stone-800 dark:bg-stone-950">
|
||||
<h1 className="text-lg font-semibold text-stone-900 dark:text-stone-100">{pageTitle}</h1>
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-amber-500 text-xs font-semibold text-white">
|
||||
{userInitials}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="flex-1 overflow-y-auto py-8">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<footer className="border-t border-stone-200 bg-white/90 px-6 py-4 transition-colors dark:border-stone-800 dark:bg-stone-950/90">
|
||||
<p className="text-center text-xs text-stone-400 dark:text-stone-500">
|
||||
© {new Date().getFullYear()} RentalDriveGo. {dict.rights}
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user