Files
mpc/dashboard/components/layout/user-menu.tsx
zeekay 351c0d253e feat: implement MFA TOTP, real KMS encryption, smart wallet DB ops, fix dashboard stubs
Backend:
- MFA TOTP via pquerna/otp: generate secret, verify code, enforce at login
- KMS client: real AES-256-GCM encryption with Argon2id key derivation
- Smart wallet handlers: propose/execute Safe tx, submit UserOp via DB
- 9 new KMS tests, all 27 API tests passing

Frontend:
- Dashboard home fetches real vault/wallet counts, transactions, cluster status
- User menu shows actual logged-in email instead of hardcoded placeholder
- Reshare page connected to real API
- Smart wallet detail connected to real API (propose, execute, user-op)
- Loading/error states throughout
2025-12-27 14:17:51 -08:00

81 lines
2.6 KiB
TypeScript

'use client'
import { useState, useRef, useEffect } from 'react'
import Link from 'next/link'
import { clearTokens, getUserEmail } from '@/lib/auth'
export function UserMenu() {
const [open, setOpen] = useState(false)
const [email, setEmail] = useState<string | null>(null)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
setEmail(getUserEmail())
}, [])
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (ref.current && !ref.current.contains(event.target as Node)) {
setOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
const handleLogout = () => {
clearTokens()
window.location.href = '/login'
}
const initial = email ? email.charAt(0).toUpperCase() : 'U'
return (
<div ref={ref} className="relative">
<button
onClick={() => setOpen(!open)}
className="flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
>
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-muted text-xs font-semibold text-muted-foreground">
{initial}
</div>
<span className="hidden sm:inline">{email ?? 'Account'}</span>
<svg
className={`h-4 w-4 transition-transform ${open ? 'rotate-180' : ''}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</button>
{open && (
<div className="absolute right-0 mt-1 w-48 rounded-md border border-border bg-card p-1 shadow-lg">
<Link
href="/settings"
onClick={() => setOpen(false)}
className="block rounded-sm px-3 py-2 text-sm text-card-foreground hover:bg-accent"
>
Profile
</Link>
<Link
href="/settings"
onClick={() => setOpen(false)}
className="block rounded-sm px-3 py-2 text-sm text-card-foreground hover:bg-accent"
>
Settings
</Link>
<hr className="my-1 border-border" />
<button
onClick={handleLogout}
className="block w-full rounded-sm px-3 py-2 text-left text-sm text-destructive hover:bg-accent"
>
Logout
</button>
</div>
)}
</div>
)
}