import { Button, Input, Select } from "@cloudflare/kumo"; import { Dialog } from "@cloudflare/kumo/primitives"; import { useLingui } from "@lingui/react/macro"; import { X, Key, Prohibit, CheckCircle, ArrowSquareOut, FloppyDisk, Envelope, } from "@phosphor-icons/react"; import * as React from "react"; import type { UserDetail as UserDetailType, UpdateUserInput } from "../../lib/api"; import { useStableCallback } from "../../lib/hooks"; import { cn } from "../../lib/utils"; import { useRolesConfig } from "./useRolesConfig.js"; export interface UserDetailProps { user: UserDetailType | null; isLoading?: boolean; isOpen: boolean; isSaving?: boolean; isSendingRecovery?: boolean; recoverySent?: boolean; recoveryError?: string | null; currentUserId?: string; onClose: () => void; onSave: (data: UpdateUserInput) => void; onDisable: () => void; onEnable: () => void; onSendRecovery?: () => void; } /** * User detail slide-over panel with inline editing */ export function UserDetail({ user, isLoading, isOpen, isSaving, isSendingRecovery, recoverySent, recoveryError, currentUserId, onClose, onSave, onDisable, onEnable, onSendRecovery, }: UserDetailProps) { const { t } = useLingui(); const { roles, roleLabels, getRoleLabel } = useRolesConfig(); const [name, setName] = React.useState(user?.name ?? ""); const [email, setEmail] = React.useState(user?.email ?? ""); const [role, setRole] = React.useState(user?.role ?? 30); // Reset form when viewing a different user const userIdRef = React.useRef(user?.id); if (user?.id !== userIdRef.current) { userIdRef.current = user?.id; if (user) { setName(user.name ?? ""); setEmail(user.email ?? ""); setRole(user.role); } } const stableOnClose = useStableCallback(onClose); const isSelf = user && currentUserId && user.id === currentUserId; const isDirty = user && (name !== (user.name ?? "") || email !== user.email || role !== user.role); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!user) return; const data: UpdateUserInput = {}; if (name !== (user.name ?? "")) { data.name = name || undefined; } if (email !== user.email) { data.email = email; } if (role !== user.role && !isSelf) { data.role = role; } onSave(data); }; return ( !open && stableOnClose()}> {/* Header */}
{t`User Details`}
{/* Content */}
{isLoading ? ( ) : user ? (
{/* Avatar + editable fields */}
{user.avatarUrl ? ( ) : (
{(name || email)?.[0]?.toUpperCase() ?? "?"}
)}
setName(e.target.value)} placeholder={t`Enter name`} /> setEmail(e.target.value)} placeholder={t`Enter email`} required />
{/* Role + status */}
{isSelf ? (

{t`You cannot change your own role`}

) : (
)}
{user.disabled ? ( ) : ( )}
{/* Info cards */}
{/* Timestamps */}

{t`Account Info`}

{t`Created`} {new Date(user.createdAt).toLocaleDateString()}
{t`Last updated`} {new Date(user.updatedAt).toLocaleDateString()}
{t`Last login`} {user.lastLogin ? new Date(user.lastLogin).toLocaleDateString() : t`Never`}
{t`Email verified`} {user.emailVerified ? t`Yes` : t`No`}
{/* Passkeys */}

{user.credentials.length === 0 ? (

{t`No passkeys registered`}

) : (
{user.credentials.map((cred) => (
{cred.name || t`Unnamed passkey`}
{cred.deviceType === "multiDevice" ? t`Synced` : t`Device-bound`}
{t`Created ${new Date(cred.createdAt).toLocaleDateString()}`}
{t`Last used ${new Date(cred.lastUsedAt).toLocaleDateString()}`}
))}
)}
{/* OAuth accounts */} {user.oauthAccounts.length > 0 && (

{user.oauthAccounts.map((account, i) => (
{account.provider} {t`Connected ${new Date(account.createdAt).toLocaleDateString()}`}
))}
)}
) : (
{t`User not found`}
)}
{/* Footer actions */} {user && (
{!isSelf && ( )}
{!isSelf && onSendRecovery && (
{recoverySent && (

{t`Recovery link sent to ${user.email}`}

)} {recoveryError && (

{recoveryError}

)}
)}
)}
); } /** Loading skeleton for user detail */ function UserDetailSkeleton() { return (
{/* Profile skeleton */}
{/* Cards skeleton */} {Array.from({ length: 2 }, (_, i) => (
))}
); }