157 lines
5.4 KiB
TypeScript
157 lines
5.4 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import {
|
|
Settings,
|
|
AlertTriangle,
|
|
Building2,
|
|
CreditCard,
|
|
User,
|
|
LogOut,
|
|
ChevronDown,
|
|
} from 'lucide-react';
|
|
import { useAuth } from '@/lib/hooks/useAuth';
|
|
import { authApi } from '@/lib/api/auth';
|
|
import type { SubscriptionAlertData } from '@/types/subscription';
|
|
|
|
function warningTooltip(data: SubscriptionAlertData | null): string {
|
|
if (!data?.showWarning) return '';
|
|
if (data.trialExpired) return 'Trial ended — review Subscriptions';
|
|
if (data.trialEndingSoon) return 'Trial ending soon — review Subscriptions';
|
|
if (data.seatsLow) return 'Seats running low — review Subscriptions';
|
|
return 'Review Subscriptions';
|
|
}
|
|
|
|
export function DashboardAccountMenu() {
|
|
const { user, currentOrganization, logout } = useAuth();
|
|
const [open, setOpen] = useState(false);
|
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
const [alert, setAlert] = useState<SubscriptionAlertData | null>(null);
|
|
const isOwner = currentOrganization?.isOwner ?? false;
|
|
|
|
useEffect(() => {
|
|
const onDocClick = (e: MouseEvent) => {
|
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
setOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', onDocClick);
|
|
return () => document.removeEventListener('mousedown', onDocClick);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!isOwner || !currentOrganization) {
|
|
setAlert(null);
|
|
return;
|
|
}
|
|
let cancelled = false;
|
|
void (async () => {
|
|
try {
|
|
const res = await authApi.getSubscriptionAlert();
|
|
if (!cancelled && res.success) setAlert(res.data);
|
|
} catch {
|
|
if (!cancelled) setAlert(null);
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [isOwner, currentOrganization?.id]);
|
|
|
|
const showWarning = Boolean(isOwner && alert?.showWarning);
|
|
const tooltip = useMemo(() => warningTooltip(alert), [alert]);
|
|
|
|
const handleLogout = useCallback(() => {
|
|
setOpen(false);
|
|
void logout();
|
|
}, [logout]);
|
|
|
|
return (
|
|
<div className="relative z-[120]" ref={menuRef}>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen((v) => !v)}
|
|
className="inline-flex items-center gap-2 rounded-[var(--radius-md)] border border-border/70 px-3 py-2 text-sm text-text-primary hover:bg-background-card/80 transition-colors"
|
|
aria-expanded={open}
|
|
aria-haspopup="menu"
|
|
>
|
|
<span className="relative inline-flex shrink-0" title={showWarning ? tooltip : undefined}>
|
|
<Settings className="h-5 w-5 icon-flat" aria-hidden />
|
|
{showWarning && (
|
|
<span
|
|
className="absolute -right-1 -top-1 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-amber-500 ring-2 ring-background-secondary"
|
|
aria-label={tooltip}
|
|
>
|
|
<AlertTriangle className="h-2.5 w-2.5 text-amber-950" strokeWidth={2.5} />
|
|
</span>
|
|
)}
|
|
</span>
|
|
<span className="hidden sm:inline max-w-[160px] truncate">{user?.name}</span>
|
|
<ChevronDown className="h-4 w-4 text-text-muted shrink-0" aria-hidden />
|
|
</button>
|
|
|
|
{open && (
|
|
<div
|
|
role="menu"
|
|
className="absolute right-0 mt-2 w-72 rounded-[var(--radius-md)] border border-border bg-background-secondary/95 py-2 shadow-lg z-[200] backdrop-blur-sm"
|
|
>
|
|
<div className="px-3 py-2 border-b border-border/60">
|
|
<p className="text-xs text-text-muted">Signed in</p>
|
|
<p className="text-sm font-medium truncate">{user?.email}</p>
|
|
<p className="text-xs text-text-secondary mt-1 truncate">
|
|
{currentOrganization?.name}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="py-1">
|
|
<Link
|
|
href="/settings/organizations"
|
|
role="menuitem"
|
|
className="flex items-center gap-3 px-3 py-2.5 text-sm text-text-primary hover:bg-background-card/70"
|
|
onClick={() => setOpen(false)}
|
|
>
|
|
<Building2 className="h-4 w-4 icon-flat shrink-0" />
|
|
Switch organization
|
|
</Link>
|
|
|
|
{isOwner && (
|
|
<Link
|
|
href="/settings/subscriptions"
|
|
role="menuitem"
|
|
className="flex items-center gap-3 px-3 py-2.5 text-sm text-text-primary hover:bg-background-card/70"
|
|
onClick={() => setOpen(false)}
|
|
>
|
|
<CreditCard className="h-4 w-4 icon-flat shrink-0" />
|
|
Subscriptions
|
|
</Link>
|
|
)}
|
|
|
|
<Link
|
|
href="/settings/account"
|
|
role="menuitem"
|
|
className="flex items-center gap-3 px-3 py-2.5 text-sm text-text-primary hover:bg-background-card/70"
|
|
onClick={() => setOpen(false)}
|
|
>
|
|
<User className="h-4 w-4 icon-flat shrink-0" />
|
|
Account
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="border-t border-border/60 pt-1">
|
|
<button
|
|
type="button"
|
|
role="menuitem"
|
|
className="flex w-full items-center gap-3 px-3 py-2.5 text-sm text-text-secondary hover:bg-background-card/70 hover:text-text-primary"
|
|
onClick={handleLogout}
|
|
>
|
|
<LogOut className="h-4 w-4 icon-flat shrink-0" />
|
|
Log out
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|