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)}
Change photo
JPG, PNG or GIF. Max 2MB.
{/* Name */}
Full name
{isEditing ? (
setName(e.target.value)}
disabled={isSaving}
/>
{isSaving && }
Save
Cancel
) : (
{user.full_name || "Not set"}
setIsEditing(true)}>
Edit
)}
{/* Email */}
Email Address
Your email is used for login and notifications
{user.email}
{user.email_verified ? (
Verified
) : (
Unverified
)}
{!user.email_verified && (
{isResending && }
Send verification email
)}
{/* 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.
setDeleteDialogOpen(true)}
>
Delete account
{/* 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 */}
Type your email address{" "}
{user.email}
{" "}
to confirm:
setConfirmEmail(e.target.value)}
disabled={isDeleting}
autoComplete="off"
/>
{
setDeleteDialogOpen(false);
setConfirmEmail("");
}}
disabled={isDeleting}
>
Cancel
{isDeleting && }
Yes, permanently delete my account
);
}