fix and update language for company workspace
This commit is contained in:
@@ -1,25 +1,127 @@
|
||||
'use client'
|
||||
|
||||
import { Bell } from 'lucide-react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { apiFetch } from '@/lib/api'
|
||||
import { EMPLOYEE_TOKEN_KEY, apiFetch } from '@/lib/api'
|
||||
import { useDashboardI18n } from '@/components/I18nProvider'
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
export default function TopBar() {
|
||||
const { dict } = useDashboardI18n()
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
const [unreadCount, setUnreadCount] = useState(0)
|
||||
const [showNotifs, setShowNotifs] = useState(false)
|
||||
const [userInitials, setUserInitials] = useState('W')
|
||||
const [notifications, setNotifications] = useState<Array<{
|
||||
id: string
|
||||
title: string
|
||||
body: string
|
||||
readAt: string | null
|
||||
createdAt: string
|
||||
}>>([])
|
||||
const [loadingNotifs, setLoadingNotifs] = useState(false)
|
||||
|
||||
const title = dict.titles[pathname] ?? dict.titles['/dashboard']
|
||||
async function refreshUnreadCount() {
|
||||
try {
|
||||
const data = await apiFetch<{ unread: number }>('/notifications/unread-count')
|
||||
setUnreadCount(data.unread)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
apiFetch<{ unread: number }>('/notifications/unread-count')
|
||||
.then((data) => setUnreadCount(data.unread))
|
||||
.catch(() => {})
|
||||
refreshUnreadCount()
|
||||
}, [pathname])
|
||||
|
||||
useEffect(() => {
|
||||
function onNotificationsUpdated() {
|
||||
refreshUnreadCount()
|
||||
}
|
||||
window.addEventListener('notifications:updated', onNotificationsUpdated)
|
||||
return () => window.removeEventListener('notifications:updated', onNotificationsUpdated)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!showNotifs) return
|
||||
let cancelled = false
|
||||
setLoadingNotifs(true)
|
||||
apiFetch<Array<{
|
||||
id: string
|
||||
title: string
|
||||
body: string
|
||||
readAt: string | null
|
||||
createdAt: string
|
||||
}>>('/notifications/company')
|
||||
.then((data) => {
|
||||
if (cancelled) return
|
||||
setNotifications(data.slice(0, 8))
|
||||
})
|
||||
.catch(() => {
|
||||
if (cancelled) return
|
||||
setNotifications([])
|
||||
})
|
||||
.finally(() => {
|
||||
if (cancelled) return
|
||||
setLoadingNotifs(false)
|
||||
})
|
||||
|
||||
return () => { cancelled = true }
|
||||
}, [showNotifs])
|
||||
|
||||
useEffect(() => {
|
||||
setShowNotifs(false)
|
||||
}, [pathname])
|
||||
|
||||
useEffect(() => {
|
||||
const token = window.localStorage.getItem(EMPLOYEE_TOKEN_KEY)
|
||||
if (!token) {
|
||||
setUserInitials(computeInitials(dict.workspaceUser))
|
||||
return
|
||||
}
|
||||
|
||||
let cancelled = false
|
||||
|
||||
apiFetch<{
|
||||
employee: {
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
}
|
||||
}>('/auth/employee/me')
|
||||
.then(({ employee }) => {
|
||||
if (cancelled) return
|
||||
const fullName = `${employee.firstName ?? ''} ${employee.lastName ?? ''}`.trim()
|
||||
const email = employee.email ?? ''
|
||||
setUserInitials(computeInitials(fullName || email || dict.workspaceUser))
|
||||
})
|
||||
.catch(() => {
|
||||
if (cancelled) return
|
||||
setUserInitials(computeInitials(dict.workspaceUser))
|
||||
})
|
||||
|
||||
return () => { cancelled = true }
|
||||
}, [dict.workspaceUser])
|
||||
|
||||
async function openNotificationsInbox(notificationId?: string) {
|
||||
if (notificationId) {
|
||||
try {
|
||||
await apiFetch(`/notifications/company/${notificationId}/read`, { method: 'POST' })
|
||||
setUnreadCount((current) => Math.max(current - 1, 0))
|
||||
window.dispatchEvent(new Event('notifications:updated'))
|
||||
} catch {}
|
||||
}
|
||||
setShowNotifs(false)
|
||||
router.push('/dashboard/notifications')
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="flex h-16 items-center justify-between border-b border-slate-200 bg-white px-6 transition-colors dark:border-slate-800 dark:bg-slate-950">
|
||||
<h1 className="text-lg font-semibold text-slate-900 dark:text-slate-100">{title}</h1>
|
||||
@@ -40,16 +142,51 @@ export default function TopBar() {
|
||||
|
||||
{showNotifs ? (
|
||||
<div className="absolute right-0 top-full z-50 mt-2 w-80 rounded-xl border border-slate-200 bg-white p-4 shadow-lg dark:border-slate-700 dark:bg-slate-900">
|
||||
<p className="mb-2 text-sm font-medium text-slate-900 dark:text-slate-100">{dict.notifications}</p>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">
|
||||
{unreadCount === 0 ? dict.noNewNotifications : dict.unreadNotifications(unreadCount)}
|
||||
</p>
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">{dict.notifications}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openNotificationsInbox()}
|
||||
className="text-xs font-medium text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
View all
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loadingNotifs ? (
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">Loading…</p>
|
||||
) : notifications.length === 0 ? (
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">
|
||||
{unreadCount === 0 ? dict.noNewNotifications : dict.unreadNotifications(unreadCount)}
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{notifications.map((notification) => (
|
||||
<button
|
||||
key={notification.id}
|
||||
type="button"
|
||||
onClick={() => openNotificationsInbox(notification.id)}
|
||||
className={[
|
||||
'w-full rounded-lg px-3 py-2 text-left transition-colors hover:bg-slate-100 dark:hover:bg-slate-800',
|
||||
notification.readAt ? 'opacity-80' : '',
|
||||
].join(' ')}
|
||||
>
|
||||
<p className="truncate text-sm font-medium text-slate-900 dark:text-slate-100">
|
||||
{notification.title}
|
||||
</p>
|
||||
<p className="truncate text-xs text-slate-500 dark:text-slate-400">
|
||||
{notification.body}
|
||||
</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 text-xs font-semibold text-white">
|
||||
W
|
||||
{userInitials}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user