fix and update language for company workspace

This commit is contained in:
root
2026-05-11 23:40:40 -04:00
committed by Administrator
parent 193aeae834
commit 1a39aa8433
43 changed files with 5034 additions and 802 deletions
+174 -45
View File
@@ -2,6 +2,7 @@
import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
import {
LayoutDashboard,
Car,
@@ -15,10 +16,30 @@ import {
Bell,
Settings,
LogOut,
ChevronLeft,
ChevronRight,
} from 'lucide-react'
import { useDashboardI18n } from '@/components/I18nProvider'
import { marketplaceUrl } from '@/lib/urls'
import { EMPLOYEE_TOKEN_KEY } from '@/lib/api'
import { EMPLOYEE_TOKEN_KEY, apiFetch } from '@/lib/api'
interface BrandSettings {
displayName: string
logoUrl: string | null
}
interface SidebarUser {
displayName: string
subtitle: string
initials: string
}
function computeInitials(name: string): string {
const parts = name.trim().split(/\s+/).filter(Boolean)
if (parts.length === 0) return 'U'
if (parts.length === 1) return parts[0].slice(0, 1).toUpperCase()
return `${parts[0].slice(0, 1)}${parts[1].slice(0, 1)}`.toUpperCase()
}
const NAV_ITEMS = [
{ href: '/dashboard', key: 'dashboard', icon: LayoutDashboard, exact: true },
@@ -35,9 +56,67 @@ const NAV_ITEMS = [
] as const
export default function Sidebar() {
const { dict } = useDashboardI18n()
const { dict, language } = useDashboardI18n()
const isRtl = language === 'ar'
const pathname = usePathname()
const router = useRouter()
const [brand, setBrand] = useState<BrandSettings | null>(null)
const [user, setUser] = useState<SidebarUser>({
displayName: dict.workspaceUser,
subtitle: dict.localAuth,
initials: 'W',
})
const [open, setOpen] = useState(false)
useEffect(() => {
apiFetch<BrandSettings>('/companies/me/brand').then(setBrand).catch(() => {})
}, [])
useEffect(() => {
const token = window.localStorage.getItem(EMPLOYEE_TOKEN_KEY)
if (!token) {
setUser({
displayName: dict.workspaceUser,
subtitle: dict.localAuth,
initials: 'W',
})
return
}
let cancelled = false
apiFetch<{
employee: {
email: string
firstName: string
lastName: string
role: string
}
}>('/auth/employee/me')
.then(({ employee }) => {
if (cancelled) return
const fullName = `${employee.firstName ?? ''} ${employee.lastName ?? ''}`.trim()
const email = employee.email ?? ''
const role = employee.role ?? ''
const displayName = fullName || (email ? email.split('@')[0] : dict.workspaceUser)
const subtitle = email || role || dict.localAuth
const initials = computeInitials(fullName || email || dict.workspaceUser)
setUser({ displayName, subtitle, initials })
})
.catch(() => {
if (cancelled) return
setUser({
displayName: dict.workspaceUser,
subtitle: dict.localAuth,
initials: 'W',
})
})
return () => { cancelled = true }
}, [dict.localAuth, dict.workspaceUser])
// Close sidebar when navigating on mobile
useEffect(() => { setOpen(false) }, [pathname])
const isActive = (item: typeof NAV_ITEMS[number]) => {
if ('exact' in item && item.exact) return pathname === item.href
@@ -51,52 +130,102 @@ export default function Sidebar() {
}
return (
<aside className="fixed inset-y-0 left-0 z-40 flex w-64 flex-col bg-slate-900">
<Link href={marketplaceUrl} className="flex items-center gap-3 border-b border-slate-800 px-6 py-5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-500">
<Car className="h-4 w-4 text-white" />
</div>
<span className="text-sm font-bold tracking-wide text-white">RentalDriveGo</span>
</Link>
<>
{/* Mobile backdrop */}
{open && (
<div
className="fixed inset-0 z-30 bg-black/50 lg:hidden"
onClick={() => setOpen(false)}
/>
)}
<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-blue-600 text-white' : 'text-slate-400 hover:bg-slate-800 hover:text-white',
].join(' ')}
>
<Icon className="h-4 w-4 flex-shrink-0" />
{dict.nav[item.key]}
</Link>
)
})}
</nav>
<div className="border-t border-slate-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-blue-500 text-xs font-semibold text-white">
W
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-white">{dict.workspaceUser}</p>
<p className="truncate text-xs text-slate-400">{dict.localAuth}</p>
</div>
</div>
{/* Tab to open sidebar — only visible when sidebar is closed on mobile */}
{!open && (
<button
onClick={signOut}
className="mt-2 flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm text-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
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-slate-900 text-white shadow-lg lg:hidden',
isRtl ? 'right-0 rounded-l-lg' : 'left-0 rounded-r-lg',
].join(' ')}
>
<LogOut className="h-4 w-4" />
{dict.signOut}
{isRtl ? <ChevronLeft className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
</button>
</div>
</aside>
)}
<aside
className={[
'fixed inset-y-0 z-40 flex w-64 flex-col bg-slate-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(' ')}
>
<Link href={marketplaceUrl} className="flex items-center gap-3 border-b border-slate-800 px-6 py-5">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 overflow-hidden">
{brand?.logoUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={brand.logoUrl} alt={brand.displayName} className="h-8 w-8 object-cover" />
) : (
<Car className="h-4 w-4 text-white" />
)}
</div>
<span className="truncate text-sm font-bold tracking-wide text-white">
{brand?.displayName ?? 'RentalDriveGo'}
</span>
</Link>
<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-blue-600 text-white' : 'text-slate-400 hover:bg-slate-800 hover:text-white',
].join(' ')}
>
<Icon className="h-4 w-4 flex-shrink-0" />
{dict.nav[item.key]}
</Link>
)
})}
</nav>
<div className="border-t border-slate-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-blue-500 text-xs font-semibold text-white">
{user.initials}
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-white">{user.displayName}</p>
<p className="truncate text-xs text-slate-400">{user.subtitle}</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-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
>
<LogOut className="h-4 w-4" />
{dict.signOut}
</button>
</div>
{/* Arrow tab to collapse sidebar (mobile only, sticks to outer edge) */}
<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-slate-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>
</>
)
}