'use client' import { useState, useMemo } from 'react' import { useTeam, TeamMember } from '../hooks/useTeam' import InviteModal from '../components/team/InviteModal' import EditMemberModal from '../components/team/EditMemberModal' import PermissionsMatrix from '../components/team/PermissionsMatrix' import { useEmployee } from '@/hooks/useEmployee' // your existing auth hook // ─── Helpers ───────────────────────────────────────────────── function initials(m: TeamMember) { return (m.firstName[0] ?? '') + (m.lastName[0] ?? '') } function timeAgo(dateStr: string | null) { if (!dateStr) return 'Never' const diff = Date.now() - new Date(dateStr).getTime() const mins = Math.floor(diff / 60000) if (mins < 2) return 'Just now' if (mins < 60) return `${mins}m ago` const hrs = Math.floor(mins / 60) if (hrs < 24) return `${hrs}h ago` const days = Math.floor(hrs / 24) if (days < 30) return `${days}d ago` return new Date(dateStr).toLocaleDateString('en', { month: 'short', day: 'numeric' }) } const AVATAR_BG: string[] = [ 'bg-violet-100 dark:bg-violet-900/40 text-violet-700 dark:text-violet-300', 'bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300', 'bg-teal-100 dark:bg-teal-900/40 text-teal-700 dark:text-teal-300', 'bg-amber-100 dark:bg-amber-900/40 text-amber-700 dark:text-amber-300', 'bg-pink-100 dark:bg-pink-900/40 text-pink-700 dark:text-pink-300', ] function avatarColor(id: string) { const hash = id.split('').reduce((acc, c) => acc + c.charCodeAt(0), 0) return AVATAR_BG[hash % AVATAR_BG.length] } function RoleBadge({ role }: { role: string }) { const styles: Record = { OWNER: 'bg-violet-50 dark:bg-violet-950/40 text-violet-700 dark:text-violet-300 border-violet-100 dark:border-violet-900/50', MANAGER: 'bg-blue-50 dark:bg-blue-950/40 text-blue-700 dark:text-blue-300 border-blue-100 dark:border-blue-900/50', AGENT: 'bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 border-zinc-200 dark:border-zinc-700', } return ( {role.charAt(0) + role.slice(1).toLowerCase()} ) } function StatusBadge({ member }: { member: TeamMember }) { if (member.invitationStatus === 'pending') { return ( Pending invite ) } if (!member.isActive) { return ( Deactivated ) } return ( Active ) } // ─── Main page ──────────────────────────────────────────────── export default function TeamPage() { const { employee: currentEmployee } = useEmployee() const { members, stats, loading, error, invite, updateRole, deactivate, reactivate, remove } = useTeam() const [search, setSearch] = useState('') const [roleFilter, setRoleFilter] = useState('') const [statusFilter, setStatusFilter] = useState('') const [inviteOpen, setInviteOpen] = useState(false) const [editTarget, setEditTarget] = useState(null) const [toast, setToast] = useState(null) const isOwner = currentEmployee?.role === 'OWNER' function showToast(msg: string) { setToast(msg) setTimeout(() => setToast(null), 3000) } const filtered = useMemo(() => { return members.filter((m) => { const q = search.toLowerCase() const matchQ = !q || `${m.firstName} ${m.lastName}`.toLowerCase().includes(q) || m.email.toLowerCase().includes(q) const matchRole = !roleFilter || m.role === roleFilter const matchStatus = !statusFilter || (statusFilter === 'active' && m.isActive && m.invitationStatus === 'accepted') || (statusFilter === 'pending' && m.invitationStatus === 'pending') || (statusFilter === 'inactive' && !m.isActive && m.invitationStatus === 'accepted') return matchQ && matchRole && matchStatus }) }, [members, search, roleFilter, statusFilter]) // ── Handlers with toasts ─────────────────────────────────── const handleInvite: typeof invite = async (payload) => { await invite(payload) showToast(`Invite sent to ${payload.email}`) } const handleUpdateRole = async (id: string, role: 'MANAGER' | 'AGENT') => { await updateRole(id, role) showToast('Role updated') } const handleDeactivate = async (id: string) => { await deactivate(id) showToast('Member deactivated') } const handleReactivate = async (id: string) => { await reactivate(id) showToast('Member reactivated') } const handleRemove = async (id: string) => { await remove(id) showToast('Member removed') } // ── Render ───────────────────────────────────────────────── return (
{/* Page header */}

Team

Manage your employees and their access levels

{isOwner && ( )}
{/* Stat cards */}
{[ { label: 'Total members', value: stats.total }, { label: 'Active', value: stats.active }, { label: 'Pending invite', value: stats.pending }, { label: 'Deactivated', value: stats.inactive }, ].map((s) => (
{s.label} {s.value}
))}
{/* Filters */}
setSearch(e.target.value)} className="w-full pl-8 pr-3 py-2 text-sm border border-zinc-200 dark:border-zinc-700 rounded-lg bg-white dark:bg-zinc-900 text-zinc-900 dark:text-white placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-zinc-900 dark:focus:ring-white focus:border-transparent" />
{/* Table */}
{loading ? (
Loading team…
) : error ? (
{error}
) : filtered.length === 0 ? (

No members match your filters

) : ( {filtered.map((member, i) => ( {/* Member */} {/* Role */} {/* Status */} {/* Last active */} {/* Actions */} ))}
Member Role Status Last active
{initials(member)}
{member.firstName} {member.lastName} {member.id === currentEmployee?.id && ( (you) )}
{member.email}
{member.invitationStatus === 'pending' ? '—' : timeAgo(member.lastActiveAt)} {isOwner && member.role !== 'OWNER' && ( )}
)}
{/* Permissions matrix */} {/* Modals */} setInviteOpen(false)} onInvite={handleInvite} /> setEditTarget(null)} onUpdateRole={handleUpdateRole} onDeactivate={handleDeactivate} onReactivate={handleReactivate} onRemove={handleRemove} /> {/* Toast */} {toast && (
{toast}
)}
) }