mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
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
81 lines
2.6 KiB
TypeScript
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>
|
|
)
|
|
}
|