/opt/canhelp/apps/web/src/components
Edit: /opt/canhelp/apps/web/src/components/Header.tsx (30479B)
'use client'
import Link from 'next/link'
import { useRouter, usePathname } from 'next/navigation'
import { useSession, signOut } from '@/lib/auth'
import { useLocale } from '@/context/locale'
import { formatShortName } from '@/lib/formatName'
import { LanguageSwitcher } from './LanguageSwitcher'
import { useState, useRef, useEffect } from 'react'
import { getUnreadCount, getMyDashboard, getPublicSettings } from '@/lib/api'
import type { PlanTier } from '@/lib/api'
import { io } from 'socket.io-client'
import { useEffectiveRole } from '@/hooks/useEffectiveRole'
import dynamic from 'next/dynamic'
const SupportModal = dynamic(() => import('./SupportModal'), { ssr: false })
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000'
export function Header() {
const router = useRouter()
const pathname = usePathname()
const { data: session } = useSession()
const { t, locale } = useLocale()
const [menuOpen, setMenuOpen] = useState(false)
const [profileOpen, setProfileOpen] = useState(false)
const [supportOpen, setSupportOpen] = useState(false)
const [unreadNotif, setUnreadNotif] = useState(0)
const [unreadChat, setUnreadChat] = useState(0)
const [msgToast, setMsgToast] = useState<{ roomId: string; body: string } | null>(null)
const [planTier, setPlanTier] = useState
(null)
const [showPricing, setShowPricing] = useState(true)
const profileRef = useRef(null)
const headerRef = useRef(null)
const toastTimer = useRef | null>(null)
const pathnameRef = useRef(pathname)
useEffect(() => { pathnameRef.current = pathname }, [pathname])
useEffect(() => {
getPublicSettings().then((settings) => setShowPricing(settings.showPricing)).catch(() => {})
}, [])
// Close dropdowns on outside click
useEffect(() => {
function handleClick(e: MouseEvent) {
if (profileRef.current && !profileRef.current.contains(e.target as Node)) {
setProfileOpen(false)
}
}
document.addEventListener('mousedown', handleClick)
return () => document.removeEventListener('mousedown', handleClick)
}, [])
const isAdmin = (session?.user as any)?.role === 'admin'
const effectiveRole = useEffectiveRole()
const isSpecialist = effectiveRole === 'specialist'
const isActive = (href: string, exact = false) =>
exact ? pathname === href : pathname === href || pathname.startsWith(href + '/')
const navCls = (href: string, exact = false) =>
`font-medium text-sm transition ${isActive(href, exact) ? 'text-green-600' : 'text-gray-600 hover:text-green-600'}`
const mobileNavCls = (href: string, exact = false) =>
`flex items-center gap-2 font-medium py-1 ${isActive(href, exact) ? 'text-green-600' : 'text-gray-700'}`
// Reset chat badge when entering chat
useEffect(() => {
if (pathname === '/chat') setUnreadChat(0)
}, [pathname])
useEffect(() => {
if (!session) return
const socket = io(API_URL, { withCredentials: true, transports: ['websocket', 'polling'] })
socket.on('new_message', (msg: { roomId: string; body: string }) => {
if (pathnameRef.current === '/chat') return
setUnreadChat((c) => c + 1)
if (toastTimer.current) clearTimeout(toastTimer.current)
setMsgToast(msg)
toastTimer.current = setTimeout(() => setMsgToast(null), 5000)
})
return () => {
socket.disconnect()
if (toastTimer.current) clearTimeout(toastTimer.current)
}
}, [session])
useEffect(() => {
if (!session) { setUnreadNotif(0); return }
const fetch = () => getUnreadCount().then((r) => setUnreadNotif(r.count)).catch(() => {})
fetch()
const timer = setInterval(fetch, 30_000)
return () => clearInterval(timer)
}, [session])
useEffect(() => {
if (!session) { setPlanTier(null); return }
getMyDashboard().then((d) => setPlanTier((d.plan?.tier ?? null) as PlanTier | null)).catch(() => {})
}, [session])
useEffect(() => {
const onScroll = () => {
if (!headerRef.current) return
if (window.scrollY > 40) {
headerRef.current.classList.add('header-shrink')
} else {
headerRef.current.classList.remove('header-shrink')
}
}
window.addEventListener('scroll', onScroll)
return () => window.removeEventListener('scroll', onScroll)
}, [])
return (
<>
{/* New message toast */}
{msgToast && (
💬
{t('chat.toast_title')}
{msgToast.body}
setMsgToast(null)}
className="text-xs text-green-600 hover:underline font-medium"
>{t('chat.toast_open')}
)}
{supportOpen && setSupportOpen(false)} />}
>
)
}