diff --git a/apps/dashboard/src/components/layout/Sidebar.tsx b/apps/dashboard/src/components/layout/Sidebar.tsx index 908b3de..58dbea3 100644 --- a/apps/dashboard/src/components/layout/Sidebar.tsx +++ b/apps/dashboard/src/components/layout/Sidebar.tsx @@ -2,7 +2,7 @@ import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' -import { useEffect, useState } from 'react' +import { useEffect, useLayoutEffect, useState } from 'react' import { LayoutDashboard, Car, @@ -33,6 +33,14 @@ interface BrandSettings { logoUrl: string | null } +interface CompanySummary { + name: string + brand?: { + displayName?: string | null + logoUrl?: string | null + } | null +} + interface SidebarUser { displayName: string subtitle: string @@ -81,6 +89,8 @@ const NAV_ITEMS = [ { href: '/settings', key: 'settings', icon: Settings, minRole: 'OWNER' }, ] as const +const SIDEBAR_BRAND_KEY = 'dashboard_brand' + const ROLE_RANK: Record = { OWNER: 3, MANAGER: 2, AGENT: 1 } function hasMinRole(employeeRole: string, minRole: string): boolean { @@ -93,7 +103,7 @@ function notifyParent(message: Record) { } export default function Sidebar() { - const { dict, language, setLanguage } = useDashboardI18n() + const { dict, language, setLanguage, theme } = useDashboardI18n() const isRtl = language === 'ar' const pathname = usePathname() const appPath = toDashboardAppPath(pathname) @@ -110,8 +120,54 @@ export default function Sidebar() { useEffect(() => { setMounted(true) }, []) + useLayoutEffect(() => { + try { + const cached = window.localStorage.getItem(SIDEBAR_BRAND_KEY) + if (!cached) return + const parsed = JSON.parse(cached) as Partial + if (typeof parsed.displayName === 'string' && parsed.displayName.trim()) { + setBrand({ + displayName: parsed.displayName, + logoUrl: typeof parsed.logoUrl === 'string' ? parsed.logoUrl : null, + }) + } + } catch {} + }, []) + useEffect(() => { - apiFetch('/companies/me/brand').then(setBrand).catch(() => {}) + const token = window.localStorage.getItem(EMPLOYEE_TOKEN_KEY) + if (!token) return + + let cancelled = false + + async function loadBrand() { + try { + const brandData = await apiFetch('/companies/me/brand') + if (cancelled) return + if (brandData?.displayName?.trim()) { + setBrand(brandData) + window.localStorage.setItem(SIDEBAR_BRAND_KEY, JSON.stringify(brandData)) + return + } + } catch {} + + try { + const company = await apiFetch('/companies/me') + if (cancelled) return + + const resolvedBrand: BrandSettings = { + displayName: company.brand?.displayName?.trim() || company.name, + logoUrl: company.brand?.logoUrl ?? null, + } + + setBrand(resolvedBrand) + window.localStorage.setItem(SIDEBAR_BRAND_KEY, JSON.stringify(resolvedBrand)) + } catch {} + } + + loadBrand() + + return () => { cancelled = true } }, []) useEffect(() => { @@ -218,7 +274,7 @@ export default function Sidebar() { )} - + {brand?.displayName ?? 'RentalDriveGo'} diff --git a/apps/dashboard/src/components/layout/TopBar.tsx b/apps/dashboard/src/components/layout/TopBar.tsx index c3cf8da..815526a 100644 --- a/apps/dashboard/src/components/layout/TopBar.tsx +++ b/apps/dashboard/src/components/layout/TopBar.tsx @@ -15,6 +15,23 @@ function computeInitials(name: string): string { return `${parts[0].slice(0, 1)}${parts[1].slice(0, 1)}`.toUpperCase() } +function resolveSocketOrigin(): string | null { + if (typeof window === 'undefined') return null + + const configuredApiUrl = process.env.NEXT_PUBLIC_API_URL + if (configuredApiUrl) { + try { + return new URL(configuredApiUrl, window.location.origin).origin + } catch {} + } + + if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { + return 'http://localhost:4000' + } + + return window.location.origin +} + export default function TopBar() { const { dict } = useDashboardI18n() const pathname = usePathname() @@ -75,15 +92,31 @@ export default function TopBar() { const token = window.localStorage.getItem(EMPLOYEE_TOKEN_KEY) if (!token) return - const socketUrl = (process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000/api/v1').replace('/api/v1', '') - const socket = io(socketUrl, { auth: { token }, transports: ['websocket'] }) + const socketOrigin = resolveSocketOrigin() + if (!socketOrigin) return - socket.on('notification', () => { - setUnreadCount((prev) => prev + 1) - window.dispatchEvent(new Event('notifications:updated')) + const socket = io(socketOrigin, { + autoConnect: false, + auth: { token }, + transports: ['polling', 'websocket'], }) - return () => { socket.disconnect() } + const handleNotification = () => { + setUnreadCount((prev) => prev + 1) + window.dispatchEvent(new Event('notifications:updated')) + } + + socket.on('notification', handleNotification) + + const connectTimer = window.setTimeout(() => { + socket.connect() + }, 0) + + return () => { + window.clearTimeout(connectTimer) + socket.off('notification', handleNotification) + socket.disconnect() + } }, []) useEffect(() => { @@ -177,7 +210,7 @@ export default function TopBar() { } return ( -
+

{title}

@@ -195,7 +228,7 @@ export default function TopBar() { {showNotifs ? ( -
+

{dict.notifications}