make the booking in many steps
Build & Deploy / Build & Push Docker Image (push) Successful in 8m27s
Test / Type Check (all packages) (push) Successful in 3m34s
Build & Deploy / Deploy to VPS (push) Successful in 2s
Test / API Unit Tests (push) Failing after 3m16s
Test / Homepage Unit Tests (push) Failing after 2m40s
Test / Storefront Unit Tests (push) Failing after 24s
Test / Admin Unit Tests (push) Failing after 22s
Test / Dashboard Unit Tests (push) Failing after 24s
Test / API Integration Tests (push) Failing after 33s

This commit is contained in:
root
2026-06-29 08:54:41 -04:00
parent ae10f5272f
commit 3d6607e6c8
20 changed files with 3894 additions and 836 deletions
@@ -87,22 +87,16 @@ function toSidebarUser(profile: Partial<EmployeeProfile>, fallbackName: string,
const NAV_ITEMS = [
{ href: '/', key: 'dashboard', icon: LayoutDashboard, exact: true, minRole: 'AGENT' },
{ href: '/fleet', key: 'fleet', icon: Car, minRole: 'AGENT' },
{ href: '/reservations', key: 'reservations', icon: Calendar, minRole: 'AGENT' },
{ href: '/online-reservations', key: 'onlineReservations', icon: Globe, minRole: 'AGENT' },
{ href: '/fleet', key: 'fleet', icon: Car, minRole: 'AGENT' },
{ href: '/customers', key: 'customers', icon: Users, minRole: 'AGENT' },
{ href: '/offers', key: 'offers', icon: Tag, minRole: 'MANAGER' },
{ href: '/team', key: 'team', icon: UserPlus, minRole: 'AGENT' },
{ href: '/reports', key: 'reports', icon: BarChart2, minRole: 'MANAGER' },
{ href: '/subscription', key: 'subscription', icon: CreditCard, minRole: 'OWNER' },
{ href: '/billing', key: 'billing', icon: CreditCard, minRole: 'MANAGER' },
{ href: '/contracts', key: 'contracts', icon: FileText, minRole: 'AGENT' },
{ href: '/reviews', key: 'reviews', icon: Star, minRole: 'AGENT' },
{ href: '/complaints', key: 'complaints', icon: AlertTriangle, minRole: 'AGENT' },
{ href: '/notifications', key: 'notifications', icon: Bell, minRole: 'AGENT' },
{ href: '/settings', key: 'settings', icon: Settings, minRole: 'OWNER' },
] as const
export const APPROVED_BASELINE_MENU_KEYS = NAV_ITEMS.map((item) => item.key)
const ICON_MAP = {
LayoutDashboard,
Car,
@@ -156,6 +150,7 @@ export default function Sidebar() {
})
const [role, setRole] = useState<string>('AGENT')
const [menuItems, setMenuItems] = useState<GeneratedMenuItem[] | null>(null)
const [menuLoadState, setMenuLoadState] = useState<'loading' | 'loaded' | 'failed'>('loading')
const [open, setOpen] = useState(false)
const [mounted, setMounted] = useState(false)
@@ -229,7 +224,12 @@ export default function Sidebar() {
window.localStorage.setItem(EMPLOYEE_PROFILE_KEY, JSON.stringify(employee))
setUser(toSidebarUser(employee, dict.workspaceUser, dict.localAuth))
if (employee.role) setRole(employee.role)
if (menu?.items) setMenuItems(menu.items)
if (menu) {
setMenuItems(menu.items)
setMenuLoadState('loaded')
} else {
setMenuLoadState('failed')
}
if (employee.preferredLanguage === 'en' || employee.preferredLanguage === 'fr' || employee.preferredLanguage === 'ar') {
// Only apply the server-side preference when the employee has no local preference
// stored yet — avoids overriding a language the user manually switched to.
@@ -243,6 +243,7 @@ export default function Sidebar() {
.catch(() => {
if (cancelled) return
if (!cached) setUser(toSidebarUser({}, dict.workspaceUser, dict.localAuth))
setMenuLoadState('failed')
})
return () => { cancelled = true }
@@ -278,8 +279,8 @@ export default function Sidebar() {
children: [],
}))
const useGeneratedMenu = hasRenderableMenuItems(menuItems)
const resolvedMenuItems = useGeneratedMenu && menuItems ? menuItems : fallbackMenuItems
const useGeneratedMenu = menuLoadState === 'loaded'
const resolvedMenuItems = useGeneratedMenu ? (menuItems ?? []) : fallbackMenuItems
function renderGeneratedMenu(items: GeneratedMenuItem[], depth = 0): ReactNode {
return items.map((item) => {
@@ -406,33 +407,7 @@ export default function Sidebar() {
</a>
<nav className="flex-1 space-y-1.5 overflow-y-auto px-3 py-5">
{useGeneratedMenu ? renderGeneratedMenu(resolvedMenuItems) : NAV_ITEMS.filter((item) => !mounted || hasMinRole(role, item.minRole)).map((item) => {
const Icon = item.icon
const active = mounted && isActive(item)
return (
<Link
key={item.href}
href={item.href}
className={[
'relative flex items-center gap-3 rounded-lg px-3.5 py-3 text-sm font-medium transition-all duration-200',
active
? 'bg-blue-500/10 text-blue-950 shadow-[inset_2px_0_0_#3b82f6,0_0_18px_rgba(59,130,246,0.10)] dark:text-slate-50'
: 'text-slate-500 hover:bg-blue-500/10 hover:text-blue-950 dark:text-slate-400 dark:hover:text-slate-50',
].join(' ')}
>
<Icon className={['h-[18px] w-[18px] flex-shrink-0', active ? 'text-blue-700 dark:text-blue-300' : ''].join(' ')} />
{dict.nav[item.key]}
{item.key === 'dashboard' ? (
<span className="ml-auto rounded-full border border-blue-400/30 bg-blue-500/15 px-2 py-0.5 text-[10px] font-medium text-blue-700 dark:text-blue-300">
Live
</span>
) : null}
{item.key === 'notifications' && active ? (
<span className="ml-auto h-1.5 w-1.5 rounded-full bg-orange-400 shadow-[0_0_10px_rgba(249,115,22,0.5)]" />
) : null}
</Link>
)
})}
{renderGeneratedMenu(resolvedMenuItems)}
</nav>
<div className="border-t border-blue-200/70 px-3 py-4 dark:border-blue-400/10">