import { useState, useEffect } from "react"; import { Mail, Upload, CheckCircle, AlertCircle, Loader2, Bell, AlertTriangle, Trash2, Building2, TriangleAlert } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { useAuth } from "@/contexts/AuthContext"; import { api, ApiError, PendingInvite } from "@/lib/api"; import { toast } from "@/hooks/use-toast"; import { useNavigate } from "react-router-dom"; function ProfileSkeleton() { return (
{/* Personal Information Skeleton */}
{/* Email Skeleton */}
); } export default function ProfilePage() { const { user, isLoading: authLoading, refreshUser, logout } = useAuth(); const navigate = useNavigate(); const [name, setName] = useState(""); const [isEditing, setIsEditing] = useState(false); const [isSaving, setIsSaving] = useState(false); const [pendingInvites, setPendingInvites] = useState([]); const [isResending, setIsResending] = useState(false); // Delete account dialog state const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [confirmEmail, setConfirmEmail] = useState(""); // Sync local name state with user data useEffect(() => { if (user?.full_name) { setName(user.full_name); } }, [user?.full_name]); // Fetch pending invitations for this user useEffect(() => { if (!user) return; api.users.getMyInvites() .then((res) => setPendingInvites(res.invites ?? [])) .catch(() => { /* silently ignore */ }); }, [user]); const getInitials = (fullName: string | null) => { if (!fullName) return "?"; return fullName .split(" ") .map((n) => n[0]) .join("") .toUpperCase() .slice(0, 2); }; const handleDeleteAccount = async () => { setIsDeleting(true); try { await api.users.deleteMe(); toast({ title: "Account deleted", description: "Your account has been deleted." }); setDeleteDialogOpen(false); setConfirmEmail(""); await logout(); navigate("/login"); } catch (err) { if (err instanceof ApiError && err.type === "USER_IS_SOLE_OWNER") { const details = err.details as { transfer_ownership?: string[]; } | undefined; const transferOrgs = details?.transfer_ownership ?? []; toast({ title: "Cannot delete account", description: transferOrgs.length > 0 ? `You are the owner of ${transferOrgs.join(", ")} and other members exist. Transfer ownership to another member before deleting your account.` : "You own organizations with other members. Transfer ownership first.", variant: "destructive", }); } else { toast({ title: "Deletion failed", description: err instanceof ApiError ? err.message : "An unexpected error occurred.", variant: "destructive", }); } setDeleteDialogOpen(false); } finally { setIsDeleting(false); } }; const handleSave = async () => { if (!name.trim()) { toast({ title: "Name required", description: "Please enter your full name", variant: "destructive", }); return; } setIsSaving(true); try { await api.users.updateMe({ full_name: name.trim() }); await refreshUser(); setIsEditing(false); toast({ title: "Profile updated", description: "Your name has been updated successfully", }); } catch (error) { if (error instanceof ApiError) { toast({ title: "Update failed", description: error.message, variant: "destructive", }); } } finally { setIsSaving(false); } }; const handleCancel = () => { setName(user?.full_name || ""); setIsEditing(false); }; const handleResendVerification = async () => { setIsResending(true); try { await api.auth.resendVerification(user.email); toast({ title: "Verification email sent", description: "Check your inbox.", }); } catch (err) { toast({ variant: "destructive", title: "Failed to send", description: err instanceof ApiError ? err.message : "An error occurred.", }); } finally { setIsResending(false); } }; if (authLoading || !user) { return ; } return (

Profile

Manage your personal information and account settings

{/* Account Suspended Banner */} {(user.status === "suspended" || user.status === "compliance_suspended") && (

Account suspended

Your account has been suspended. You cannot perform most actions. Please contact your administrator to resolve this.

)} {/* Pending Invitations Banner */} {pendingInvites.length > 0 && (
You have {pendingInvites.length} pending invitation{pendingInvites.length > 1 ? "s" : ""}
{pendingInvites.map((invite) => (

{invite.organization.name}

Invited as {invite.role}

Accept →
))}
)} {/* Profile Photo & Name */} Personal Information Update your photo and personal details {/* Avatar */}
{getInitials(user.full_name)}

JPG, PNG or GIF. Max 2MB.

{/* Name */}
{isEditing ? (
setName(e.target.value)} disabled={isSaving} />
) : (
{user.full_name || "Not set"}
)}
{/* Email */} Email Address Your email is used for login and notifications
{user.email} {user.email_verified ? ( Verified ) : ( Unverified )}
{!user.email_verified && ( )}
{/* Danger Zone */} Danger Zone Irreversible actions for your account. Proceed with caution.

Delete Account

Permanently deletes your profile and all associated data. If you own organizations with other members, transfer ownership first. Sole-member organizations are deleted automatically.

{/* Delete account confirmation dialog */} { setDeleteDialogOpen(open); if (!open) setConfirmEmail(""); }} > Delete your account? Your profile, SSH keys, linked accounts, and session data will be permanently deleted. This action cannot be undone. {/* Org ownership warning */}

Organization ownership check

If you own organizations with other members, you must{" "} transfer ownership to another member first.

Organizations where you are the sole member will be automatically deleted along with your account.

{/* What will be deleted */}

The following will be permanently deleted:

  • Your profile and account data
  • All SSH keys and active certificates
  • All linked accounts (Google, GitHub, etc.)
  • All active sessions
  • All passkeys and MFA methods
{/* Email confirmation input */}
setConfirmEmail(e.target.value)} disabled={isDeleting} autoComplete="off" />
); }