@burdenoff/microfe-auth 2026.916.1 → 2026.917.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"UserProfilePage.js","names":[],"sources":["../../../src/auth/pages/UserProfilePage.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useId } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useAuth as useShellAuth } from \"@burdenoff/fe-libs/shared/providers/shell/AuthProvider\";\nimport { useNavigate } from \"react-router\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport {\n SpinnerIcon,\n AlertIcon,\n UserIcon,\n EmailIcon,\n CheckIcon,\n} from \"../components/AuthIcons\";\nimport {\n useInitiateAvatarUploadMutation,\n useCompleteAvatarUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\n\ninterface ProfileFormData {\n username: string;\n firstName: string;\n lastName: string;\n name: string;\n phoneNumber: string;\n}\n\n/**\n * User Profile page for viewing and editing own profile\n */\nexport function UserProfilePage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const shellAuth = useShellAuth();\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const setStoreUser = useAuthStore((state) => state.setUser);\n const { updateCurrentUserProfile, isLoading } = useUserOperations();\n\n const usernameId = useId();\n const displayNameId = useId();\n const firstNameId = useId();\n const lastNameId = useId();\n const phoneNumberId = useId();\n\n const currentWorkspaceId = useAuthStore((state) => state.currentWorkspaceId);\n const wspaceClient = useGatewayClient(\"workspace\");\n const [initiateUpload] = useInitiateAvatarUploadMutation({\n client: wspaceClient,\n });\n const [completeUpload] = useCompleteAvatarUploadMutation({\n client: wspaceClient,\n });\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);\n\n const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n if (!file.type.startsWith(\"image/\")) {\n setError(\n tr(\"auth.profile.invalidImageType\", \"Please select an image file\"),\n );\n return;\n }\n if (!currentWorkspaceId) {\n setError(\n tr(\n \"auth.profile.noWorkspace\",\n \"Please select a workspace before uploading a profile picture\",\n ),\n );\n return;\n }\n setIsUploadingAvatar(true);\n setError(null);\n const wsHeaders = { \"x-workspace-id\": currentWorkspaceId };\n try {\n const extension = file.name.split(\".\").pop()?.toLowerCase() ?? \"\";\n const initiateResult = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type,\n extension,\n },\n },\n context: { headers: wsHeaders },\n });\n const session = initiateResult.data?.initiateUpload.session;\n const uploadUrl = initiateResult.data?.initiateUpload.uploadUrl;\n if (!session || !uploadUrl) throw new Error(\"Failed to initiate upload\");\n\n const uploadResp = await fetch(uploadUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": file.type },\n body: file,\n });\n if (!uploadResp.ok) throw new Error(\"Failed to upload file\");\n\n const completeResult = await completeUpload({\n variables: { input: { sessionId: session.id } },\n context: { headers: wsHeaders },\n });\n const avatarUrl = completeResult.data?.completeUpload.file?.downloadUrl;\n if (!avatarUrl) throw new Error(\"Upload succeeded but no URL returned\");\n\n await updateCurrentUserProfile({ avatar: avatarUrl });\n // Update both the local authStore and the shell AuthProvider so the nav\n // bar avatar reflects the change immediately without a page reload.\n if (currentUser) {\n setStoreUser({ ...currentUser, avatar: avatarUrl });\n }\n shellAuth.updateUser({ avatar: avatarUrl });\n setSuccessMessage(\n tr(\"auth.profile.avatarUpdated\", \"Profile picture updated!\"),\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.avatarFailed\", \"Failed to upload picture\"),\n );\n } finally {\n setIsUploadingAvatar(false);\n if (fileInputRef.current) fileInputRef.current.value = \"\";\n }\n };\n\n const [isEditing, setIsEditing] = useState(false);\n const [formData, setFormData] = useState<ProfileFormData>({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n });\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // Populate form with current user data\n useEffect(() => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n }, [currentUser]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccessMessage(null);\n\n try {\n await updateCurrentUserProfile({\n username: formData.username || undefined,\n firstName: formData.firstName || undefined,\n lastName: formData.lastName || undefined,\n name: formData.name || undefined,\n phoneNumber: formData.phoneNumber || undefined,\n });\n setSuccessMessage(\n tr(\"auth.profile.updateSuccess\", \"Profile updated successfully!\"),\n );\n setIsEditing(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.updateFailed\", \"Failed to update profile\"),\n );\n }\n };\n\n const cancelEdit = () => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n setIsEditing(false);\n setError(null);\n };\n\n // Not authenticated\n if (!isAuthenticated || !currentUser) {\n return (\n <AuthLayout\n title={tr(\"auth.profile.title\", \"Profile\")}\n subtitle={tr(\"auth.profile.subtitle\", \"View and manage your profile\")}\n >\n <div className=\"text-center py-12\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-[var(--color-status-warning-bg-subtle)] flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-[var(--color-status-warning-text)]\" />\n </div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.profile.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.profile.signInToView\",\n \"Please sign in to view your profile\",\n )}\n </p>\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {tr(\"auth.profile.signIn\", \"Sign In\")}\n </Button>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.myProfile\", \"My Profile\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.profile.viewAndManage\",\n \"View and manage your account information\",\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n \"auth.profile.purpose\",\n \"This is how you appear across the platform. Update your name, photo, and contact details here — teammates see this information, and we use your email for sign-in and important notices.\",\n )}\n </PagePurpose>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div className=\"mb-6 flex items-center gap-3 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <CheckIcon className=\"size-5 text-[var(--color-status-success-text)] flex-shrink-0\" />\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"ml-auto text-sm text-[var(--color-status-success-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Profile Card */}\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm overflow-hidden\">\n {/* Profile Header — the one emphasis zone on this page */}\n <div className=\"bg-accent-wash border-b border-border-seam p-6 text-text-primary\">\n <div className=\"flex items-center gap-4\">\n <div className=\"relative group\">\n {currentUser.avatar ? (\n <img\n src={currentUser.avatar}\n alt={tr(\"auth.profile.avatarAlt\", \"Profile picture\")}\n className=\"size-20 rounded-full object-cover shadow-lg\"\n />\n ) : (\n <div className=\"size-20 rounded-full bg-accent-soft backdrop-blur-sm flex items-center justify-center text-[var(--color-action-primary-bg)] text-3xl font-semibold shadow-lg\">\n {(\n currentUser.name ||\n currentUser.username ||\n currentUser.email\n )\n .charAt(0)\n .toUpperCase()}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={isUploadingAvatar}\n aria-label={tr(\n \"auth.profile.changeAvatar\",\n \"Change profile picture\",\n )}\n className=\"absolute inset-0 rounded-full flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-pointer\"\n >\n {isUploadingAvatar ? (\n <SpinnerIcon className=\"size-6 text-white animate-spin\" />\n ) : (\n <svg\n className=\"size-6 text-white\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 13a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n )}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n onChange={(e) => void handleAvatarUpload(e)}\n tabIndex={-1}\n aria-label={tr(\n \"auth.profile.avatarUpload\",\n \"Upload avatar image\",\n )}\n />\n </div>\n <div className=\"text-text-primary\">\n <h2 className=\"text-2xl font-semibold\">\n {currentUser.name ||\n `${currentUser.firstName || \"\"} ${currentUser.lastName || \"\"}`.trim() ||\n currentUser.username}\n </h2>\n <p className=\"text-text-secondary flex items-center gap-2\">\n <EmailIcon className=\"size-4\" />\n {currentUser.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-bg-surface/60 text-text-secondary backdrop-blur-sm\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </span>\n {currentUser.emailVerified && (\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-status-success-bg-subtle text-status-success-text flex items-center gap-1\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* Profile Body */}\n <div className=\"p-6\">\n {isEditing ? (\n /* Edit Form */\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label\n htmlFor={usernameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <UserIcon\n className=\"size-5 text-[var(--color-text-tertiary)]\"\n aria-hidden=\"true\"\n />\n </div>\n <input\n id={usernameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.username\", \"Username\")}\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.usernamePlaceholder\",\n \"Enter username\",\n )}\n />\n </div>\n </div>\n\n <div>\n <label\n htmlFor={displayNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <input\n id={displayNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.displayName\", \"Display Name\")}\n value={formData.name}\n onChange={(e) =>\n setFormData({ ...formData, name: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.displayNamePlaceholder\",\n \"Enter display name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={firstNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <input\n id={firstNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={lastNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <input\n id={lastNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n\n <div className=\"md:col-span-2\">\n <label\n htmlFor={phoneNumberId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <input\n id={phoneNumberId}\n type=\"tel\"\n aria-label={tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n value={formData.phoneNumber}\n onChange={(e) =>\n setFormData({ ...formData, phoneNumber: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.phonePlaceholder\",\n \"Enter phone number\",\n )}\n />\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-4 border-t border-[var(--color-border-default)]\">\n <Button\n type=\"submit\"\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.profile.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.profile.saveChanges\", \"Save Changes\")\n )}\n </Button>\n <button\n type=\"button\"\n onClick={cancelEdit}\n className=\"px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.profile.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n ) : (\n /* View Mode */\n <>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.username || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.name || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.firstName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.email\", \"Email\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium flex items-center gap-2\">\n {currentUser.email}\n {currentUser.emailVerified && (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-success-text)] text-xs\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.phoneNumber || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.role\", \"Role\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.mfaStatus\", \"MFA Status\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.mfaEnabled ? (\n <span className=\"text-[var(--color-status-success-text)] flex items-center gap-1\">\n <CheckIcon className=\"size-4\" />\n {tr(\"auth.profile.enabled\", \"Enabled\")}\n </span>\n ) : (\n <span className=\"text-[var(--color-status-warning-text)]\">\n {tr(\"auth.profile.notEnabled\", \"Not Enabled\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastLogin\", \"Last Login\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastLoginAt\n ? new Date(currentUser.lastLoginAt).toLocaleString()\n : tr(\"auth.profile.never\", \"Never\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.accountCreated\", \"Account Created\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.createdAt\n ? new Date(currentUser.createdAt).toLocaleString()\n : \"-\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-6 mt-6 border-t border-[var(--color-border-default)]\">\n <Button\n type=\"button\"\n onClick={() => setIsEditing(true)}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {tr(\"auth.profile.editProfile\", \"Edit Profile\")}\n </Button>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/security\")}\n className=\"px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.profile.securitySettings\", \"Security Settings\")}\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-profile\"\n steps={[\n {\n id: \"edit-profile\",\n label: tr(\"auth.profile.nextEdit\", \"Complete your profile\"),\n description: tr(\n \"auth.profile.nextEditDesc\",\n \"Add your display name and photo so teammates recognise you.\",\n ),\n onClick: () => setIsEditing(true),\n done: Boolean(currentUser.name && currentUser.avatar),\n },\n {\n id: \"enable-mfa\",\n label: tr(\"auth.profile.nextMfa\", \"Secure your account with 2FA\"),\n description: tr(\n \"auth.profile.nextMfaDesc\",\n \"Turn on two-factor authentication for stronger protection.\",\n ),\n onClick: () => navigate(\"/auth/mfa\"),\n done: Boolean(currentUser.mfaEnabled),\n },\n {\n id: \"security-center\",\n label: tr(\"auth.profile.nextSecurity\", \"Visit the Security Center\"),\n description: tr(\n \"auth.profile.nextSecurityDesc\",\n \"Review sessions, password, and account options.\",\n ),\n onClick: () => navigate(\"/auth/security\"),\n },\n ]}\n />\n\n {/* Quick Links */}\n <div className=\"mt-8 grid grid-cols-1 md:grid-cols-3 gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa\")}\n className=\"p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-primary-border)] transition-colors duration-200 text-left group\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-bg-accent)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-primary-bg-subtle)] transition-colors duration-200\">\n <svg\n className=\"size-5 text-[var(--color-text-link)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.twoFactorAuth\", \"Two-Factor Auth\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {currentUser.mfaEnabled\n ? tr(\"auth.profile.manageMfa\", \"Manage MFA settings\")\n : tr(\"auth.profile.enableMfa\", \"Enable MFA for extra security\")}\n </p>\n </button>\n\n <button\n type=\"button\"\n onClick={() => navigate(\"/devportal/api-keys\")}\n className=\"p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-secondary-border)] transition-colors duration-200 text-left group\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-action-secondary-bg-subtle)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-secondary-bg-subtle)] transition-colors duration-200\">\n <svg\n className=\"size-5 text-[var(--color-action-secondary-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\"\n />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.apiKeys\", \"API Keys\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {tr(\n \"auth.profile.manageApiKeys\",\n \"Manage your API access credentials\",\n )}\n </p>\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"UserProfilePage.js","names":[],"sources":["../../../src/auth/pages/UserProfilePage.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useId } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useAuth as useShellAuth } from \"@burdenoff/fe-libs/shared/providers/shell/AuthProvider\";\nimport { useNavigate } from \"react-router\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { validateUserFields } from \"../utils/validation\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport {\n SpinnerIcon,\n AlertIcon,\n UserIcon,\n EmailIcon,\n CheckIcon,\n} from \"../components/AuthIcons\";\nimport {\n useInitiateAvatarUploadMutation,\n useCompleteAvatarUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\n\ninterface ProfileFormData {\n username: string;\n firstName: string;\n lastName: string;\n name: string;\n phoneNumber: string;\n}\n\n/**\n * User Profile page for viewing and editing own profile\n */\nexport function UserProfilePage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const shellAuth = useShellAuth();\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const setStoreUser = useAuthStore((state) => state.setUser);\n const { updateCurrentUserProfile, isLoading } = useUserOperations();\n\n const usernameId = useId();\n const displayNameId = useId();\n const firstNameId = useId();\n const lastNameId = useId();\n const phoneNumberId = useId();\n\n const currentWorkspaceId = useAuthStore((state) => state.currentWorkspaceId);\n const wspaceClient = useGatewayClient(\"workspace\");\n const [initiateUpload] = useInitiateAvatarUploadMutation({\n client: wspaceClient,\n });\n const [completeUpload] = useCompleteAvatarUploadMutation({\n client: wspaceClient,\n });\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);\n\n const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n if (!file.type.startsWith(\"image/\")) {\n setError(\n tr(\"auth.profile.invalidImageType\", \"Please select an image file\"),\n );\n return;\n }\n if (!currentWorkspaceId) {\n setError(\n tr(\n \"auth.profile.noWorkspace\",\n \"Please select a workspace before uploading a profile picture\",\n ),\n );\n return;\n }\n setIsUploadingAvatar(true);\n setError(null);\n const wsHeaders = { \"x-workspace-id\": currentWorkspaceId };\n try {\n const extension = file.name.split(\".\").pop()?.toLowerCase() ?? \"\";\n const initiateResult = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type,\n extension,\n },\n },\n context: { headers: wsHeaders },\n });\n const session = initiateResult.data?.initiateUpload.session;\n const uploadUrl = initiateResult.data?.initiateUpload.uploadUrl;\n if (!session || !uploadUrl) throw new Error(\"Failed to initiate upload\");\n\n const uploadResp = await fetch(uploadUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": file.type },\n body: file,\n });\n if (!uploadResp.ok) throw new Error(\"Failed to upload file\");\n\n const completeResult = await completeUpload({\n variables: { input: { sessionId: session.id } },\n context: { headers: wsHeaders },\n });\n const avatarUrl = completeResult.data?.completeUpload.file?.downloadUrl;\n if (!avatarUrl) throw new Error(\"Upload succeeded but no URL returned\");\n\n await updateCurrentUserProfile({ avatar: avatarUrl });\n // Update both the local authStore and the shell AuthProvider so the nav\n // bar avatar reflects the change immediately without a page reload.\n if (currentUser) {\n setStoreUser({ ...currentUser, avatar: avatarUrl });\n }\n shellAuth.updateUser({ avatar: avatarUrl });\n setSuccessMessage(\n tr(\"auth.profile.avatarUpdated\", \"Profile picture updated!\"),\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.avatarFailed\", \"Failed to upload picture\"),\n );\n } finally {\n setIsUploadingAvatar(false);\n if (fileInputRef.current) fileInputRef.current.value = \"\";\n }\n };\n\n const [isEditing, setIsEditing] = useState(false);\n const [formData, setFormData] = useState<ProfileFormData>({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n });\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // Populate form with current user data\n useEffect(() => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n }, [currentUser]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccessMessage(null);\n\n // BOFF-7214: enforce the same name/username invariants the signup form\n // uses — without this, a user editing their own profile could replace\n // their name with a pure-numeric value and re-open the same bypass.\n // Pass `requireAll: false` so a user whose optional `firstName` /\n // `lastName` is unset (e.g. provisioned via IdP) can still update\n // phone or display name without being forced to invent first/last\n // (Codex P2 review).\n const validation = validateUserFields(formData, { requireAll: false });\n if (!validation.valid) {\n setError(\n validation.errors\n .map((e) => {\n const key = `auth.profile.${e.field}_${e.code}`;\n return tr(key, e.message);\n })\n .join(\" · \"),\n );\n return;\n }\n\n try {\n await updateCurrentUserProfile({\n username: validation.values.username || undefined,\n firstName: validation.values.firstName || undefined,\n lastName: validation.values.lastName || undefined,\n name: formData.name || undefined,\n phoneNumber: formData.phoneNumber || undefined,\n });\n setSuccessMessage(\n tr(\"auth.profile.updateSuccess\", \"Profile updated successfully!\"),\n );\n setIsEditing(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.updateFailed\", \"Failed to update profile\"),\n );\n }\n };\n\n const cancelEdit = () => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n setIsEditing(false);\n setError(null);\n };\n\n // Not authenticated\n if (!isAuthenticated || !currentUser) {\n return (\n <AuthLayout\n title={tr(\"auth.profile.title\", \"Profile\")}\n subtitle={tr(\"auth.profile.subtitle\", \"View and manage your profile\")}\n >\n <div className=\"text-center py-12\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-[var(--color-status-warning-bg-subtle)] flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-[var(--color-status-warning-text)]\" />\n </div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.profile.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.profile.signInToView\",\n \"Please sign in to view your profile\",\n )}\n </p>\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {tr(\"auth.profile.signIn\", \"Sign In\")}\n </Button>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.myProfile\", \"My Profile\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.profile.viewAndManage\",\n \"View and manage your account information\",\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n \"auth.profile.purpose\",\n \"This is how you appear across the platform. Update your name, photo, and contact details here — teammates see this information, and we use your email for sign-in and important notices.\",\n )}\n </PagePurpose>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div className=\"mb-6 flex items-center gap-3 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <CheckIcon className=\"size-5 text-[var(--color-status-success-text)] flex-shrink-0\" />\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"ml-auto text-sm text-[var(--color-status-success-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Profile Card */}\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm overflow-hidden\">\n {/* Profile Header — the one emphasis zone on this page */}\n <div className=\"bg-accent-wash border-b border-border-seam p-6 text-text-primary\">\n <div className=\"flex items-center gap-4\">\n <div className=\"relative group\">\n {currentUser.avatar ? (\n <img\n src={currentUser.avatar}\n alt={tr(\"auth.profile.avatarAlt\", \"Profile picture\")}\n className=\"size-20 rounded-full object-cover shadow-lg\"\n />\n ) : (\n <div className=\"size-20 rounded-full bg-accent-soft backdrop-blur-sm flex items-center justify-center text-[var(--color-action-primary-bg)] text-3xl font-semibold shadow-lg\">\n {(\n currentUser.name ||\n currentUser.username ||\n currentUser.email\n )\n .charAt(0)\n .toUpperCase()}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={isUploadingAvatar}\n aria-label={tr(\n \"auth.profile.changeAvatar\",\n \"Change profile picture\",\n )}\n className=\"absolute inset-0 rounded-full flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-pointer\"\n >\n {isUploadingAvatar ? (\n <SpinnerIcon className=\"size-6 text-white animate-spin\" />\n ) : (\n <svg\n className=\"size-6 text-white\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 13a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n )}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n onChange={(e) => void handleAvatarUpload(e)}\n tabIndex={-1}\n aria-label={tr(\n \"auth.profile.avatarUpload\",\n \"Upload avatar image\",\n )}\n />\n </div>\n <div className=\"text-text-primary\">\n <h2 className=\"text-2xl font-semibold\">\n {currentUser.name ||\n `${currentUser.firstName || \"\"} ${currentUser.lastName || \"\"}`.trim() ||\n currentUser.username}\n </h2>\n <p className=\"text-text-secondary flex items-center gap-2\">\n <EmailIcon className=\"size-4\" />\n {currentUser.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-bg-surface/60 text-text-secondary backdrop-blur-sm\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </span>\n {currentUser.emailVerified && (\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-status-success-bg-subtle text-status-success-text flex items-center gap-1\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* Profile Body */}\n <div className=\"p-6\">\n {isEditing ? (\n /* Edit Form */\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label\n htmlFor={usernameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <UserIcon\n className=\"size-5 text-[var(--color-text-tertiary)]\"\n aria-hidden=\"true\"\n />\n </div>\n <input\n id={usernameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.username\", \"Username\")}\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.usernamePlaceholder\",\n \"Enter username\",\n )}\n />\n </div>\n </div>\n\n <div>\n <label\n htmlFor={displayNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <input\n id={displayNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.displayName\", \"Display Name\")}\n value={formData.name}\n onChange={(e) =>\n setFormData({ ...formData, name: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.displayNamePlaceholder\",\n \"Enter display name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={firstNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <input\n id={firstNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={lastNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <input\n id={lastNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n\n <div className=\"md:col-span-2\">\n <label\n htmlFor={phoneNumberId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <input\n id={phoneNumberId}\n type=\"tel\"\n aria-label={tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n value={formData.phoneNumber}\n onChange={(e) =>\n setFormData({ ...formData, phoneNumber: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.phonePlaceholder\",\n \"Enter phone number\",\n )}\n />\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-4 border-t border-[var(--color-border-default)]\">\n <Button\n type=\"submit\"\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.profile.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.profile.saveChanges\", \"Save Changes\")\n )}\n </Button>\n <button\n type=\"button\"\n onClick={cancelEdit}\n className=\"px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.profile.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n ) : (\n /* View Mode */\n <>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.username || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.name || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.firstName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.email\", \"Email\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium flex items-center gap-2\">\n {currentUser.email}\n {currentUser.emailVerified && (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-success-text)] text-xs\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.phoneNumber || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.role\", \"Role\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.mfaStatus\", \"MFA Status\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.mfaEnabled ? (\n <span className=\"text-[var(--color-status-success-text)] flex items-center gap-1\">\n <CheckIcon className=\"size-4\" />\n {tr(\"auth.profile.enabled\", \"Enabled\")}\n </span>\n ) : (\n <span className=\"text-[var(--color-status-warning-text)]\">\n {tr(\"auth.profile.notEnabled\", \"Not Enabled\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastLogin\", \"Last Login\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastLoginAt\n ? new Date(currentUser.lastLoginAt).toLocaleString()\n : tr(\"auth.profile.never\", \"Never\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.accountCreated\", \"Account Created\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.createdAt\n ? new Date(currentUser.createdAt).toLocaleString()\n : \"-\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-6 mt-6 border-t border-[var(--color-border-default)]\">\n <Button\n type=\"button\"\n onClick={() => setIsEditing(true)}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {tr(\"auth.profile.editProfile\", \"Edit Profile\")}\n </Button>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/security\")}\n className=\"px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.profile.securitySettings\", \"Security Settings\")}\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-profile\"\n steps={[\n {\n id: \"edit-profile\",\n label: tr(\"auth.profile.nextEdit\", \"Complete your profile\"),\n description: tr(\n \"auth.profile.nextEditDesc\",\n \"Add your display name and photo so teammates recognise you.\",\n ),\n onClick: () => setIsEditing(true),\n done: Boolean(currentUser.name && currentUser.avatar),\n },\n {\n id: \"enable-mfa\",\n label: tr(\"auth.profile.nextMfa\", \"Secure your account with 2FA\"),\n description: tr(\n \"auth.profile.nextMfaDesc\",\n \"Turn on two-factor authentication for stronger protection.\",\n ),\n onClick: () => navigate(\"/auth/mfa\"),\n done: Boolean(currentUser.mfaEnabled),\n },\n {\n id: \"security-center\",\n label: tr(\"auth.profile.nextSecurity\", \"Visit the Security Center\"),\n description: tr(\n \"auth.profile.nextSecurityDesc\",\n \"Review sessions, password, and account options.\",\n ),\n onClick: () => navigate(\"/auth/security\"),\n },\n ]}\n />\n\n {/* Quick Links */}\n <div className=\"mt-8 grid grid-cols-1 md:grid-cols-3 gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa\")}\n className=\"p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-primary-border)] transition-colors duration-200 text-left group\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-bg-accent)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-primary-bg-subtle)] transition-colors duration-200\">\n <svg\n className=\"size-5 text-[var(--color-text-link)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.twoFactorAuth\", \"Two-Factor Auth\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {currentUser.mfaEnabled\n ? tr(\"auth.profile.manageMfa\", \"Manage MFA settings\")\n : tr(\"auth.profile.enableMfa\", \"Enable MFA for extra security\")}\n </p>\n </button>\n\n <button\n type=\"button\"\n onClick={() => navigate(\"/devportal/api-keys\")}\n className=\"p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-secondary-border)] transition-colors duration-200 text-left group\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-action-secondary-bg-subtle)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-secondary-bg-subtle)] transition-colors duration-200\">\n <svg\n className=\"size-5 text-[var(--color-action-secondary-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\"\n />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.apiKeys\", \"API Keys\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {tr(\n \"auth.profile.manageApiKeys\",\n \"Manage your API access credentials\",\n )}\n </p>\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -5,11 +5,12 @@ import { useUserOperations as i } from "../hooks/useUserOperations.js";
5
5
  import { useAuthPermissions as a } from "../hooks/useAuthPermissions.js";
6
6
  import { ConfirmActionDialog as o } from "../components/ConfirmActionDialog.js";
7
7
  import { nativeImpact as s, nativeNotify as c } from "../../utils/nativeBridge.js";
8
- import { useCallback as ee, useEffect as te, useId as l, useState as u } from "react";
9
- import { useNavigate as ne } from "react-router";
8
+ import { validateUserFields as ee } from "../utils/validation.js";
9
+ import { useCallback as te, useEffect as ne, useId as l, useState as u } from "react";
10
+ import { useNavigate as re } from "react-router";
10
11
  import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
11
12
  import { Button as m } from "@burdenoff/fe-libs/button";
12
- import { EmphasisPanel as re, IllustratedEmptyState as h, NextSteps as ie, PagePurpose as ae } from "@burdenoff/fe-libs/ui";
13
+ import { EmphasisPanel as ie, IllustratedEmptyState as h, NextSteps as ae, PagePurpose as oe } from "@burdenoff/fe-libs/ui";
13
14
  //#region src/auth/pages/UsersPage.tsx
14
15
  var g = [
15
16
  "TENANT_ADMIN",
@@ -24,7 +25,7 @@ var g = [
24
25
  "DELETED"
25
26
  ];
26
27
  function v() {
27
- let v = t(), oe = ne(), y = e((e) => e.isAuthenticated), se = e((e) => e.user), { canViewUsers: ce, canUpdateUsers: le, canDeleteUsers: b, isLoading: ue } = a(), { listUsers: x, updateUser: de, deleteUser: fe, hardDeleteUser: pe, isLoading: S } = i(), C = l(), w = l(), T = l(), E = l(), me = l(), [D, O] = u(!1), k = async () => {
28
+ let v = t(), se = re(), y = e((e) => e.isAuthenticated), ce = e((e) => e.user), { canViewUsers: le, canUpdateUsers: ue, canDeleteUsers: b, isLoading: de } = a(), { listUsers: x, updateUser: fe, deleteUser: pe, hardDeleteUser: me, isLoading: S } = i(), C = l(), w = l(), T = l(), E = l(), he = l(), [D, O] = u(!1), k = async () => {
28
29
  O(!0);
29
30
  try {
30
31
  let e = {};
@@ -69,7 +70,7 @@ function v() {
69
70
  } finally {
70
71
  O(!1);
71
72
  }
72
- }, [A, he] = u([]), [j, ge] = u(0), [_e, ve] = u(!1), [M, N] = u("list"), [P, F] = u(null), [I, L] = u({
73
+ }, [A, ge] = u([]), [j, _e] = u(0), [ve, ye] = u(!1), [M, N] = u("list"), [P, F] = u(null), [I, L] = u({
73
74
  username: "",
74
75
  firstName: "",
75
76
  lastName: "",
@@ -78,7 +79,7 @@ function v() {
78
79
  role: "MEMBER",
79
80
  status: "ACTIVE",
80
81
  emailVerified: !1
81
- }), [R, z] = u(null), [ye, B] = u(!1), [V, H] = u(null), [U, W] = u(null), [G, be] = u(""), [K, xe] = u(""), [q, Se] = u(""), [J, Y] = u(0), X = ee(async () => {
82
+ }), [R, z] = u(null), [be, B] = u(!1), [V, H] = u(null), [U, W] = u(null), [G, xe] = u(""), [K, Se] = u(""), [q, Ce] = u(""), [J, Y] = u(0), X = te(async () => {
82
83
  if (y) try {
83
84
  B(!1);
84
85
  let e = {};
@@ -87,7 +88,7 @@ function v() {
87
88
  offset: J * 10,
88
89
  limit: 10
89
90
  });
90
- he(t.users), ge(t.totalCount), ve(t.hasMore), z(null);
91
+ ge(t.users), _e(t.totalCount), ye(t.hasMore), z(null);
91
92
  } catch (e) {
92
93
  if (e instanceof Error && (e.name === "AbortError" || e.message.includes("aborted"))) return;
93
94
  let t = e instanceof Error ? e.message : "Failed to fetch users";
@@ -101,7 +102,7 @@ function v() {
101
102
  J,
102
103
  y
103
104
  ]);
104
- te(() => {
105
+ ne(() => {
105
106
  X();
106
107
  }, [X]);
107
108
  let Z = () => {
@@ -115,40 +116,44 @@ function v() {
115
116
  status: "ACTIVE",
116
117
  emailVerified: !1
117
118
  }), F(null), z(null), H(null);
118
- }, Ce = async (e) => {
119
- if (e.preventDefault(), P) {
120
- z(null);
121
- try {
122
- await de(P.id, {
123
- username: I.username || void 0,
124
- firstName: I.firstName || void 0,
125
- lastName: I.lastName || void 0,
126
- name: I.name || void 0,
127
- phoneNumber: I.phoneNumber || void 0,
128
- role: I.role,
129
- status: I.status,
130
- emailVerified: I.emailVerified
131
- }), H(v("auth.users.updateSuccess", "User updated successfully!")), N("list"), Z(), X();
132
- } catch (e) {
133
- z(e instanceof Error ? e.message : "Failed to update user");
134
- }
135
- }
136
119
  }, we = async (e) => {
120
+ if (e.preventDefault(), !P) return;
121
+ z(null);
122
+ let t = ee(I, { requireAll: !1 });
123
+ if (!t.valid) {
124
+ z(t.errors.map((e) => v(`auth.users.${e.field}_${e.code}`, e.message)).join(" · "));
125
+ return;
126
+ }
127
+ try {
128
+ await fe(P.id, {
129
+ username: t.values.username || void 0,
130
+ firstName: t.values.firstName || void 0,
131
+ lastName: t.values.lastName || void 0,
132
+ name: I.name || void 0,
133
+ phoneNumber: I.phoneNumber || void 0,
134
+ role: I.role,
135
+ status: I.status,
136
+ emailVerified: I.emailVerified
137
+ }), H(v("auth.users.updateSuccess", "User updated successfully!")), N("list"), Z(), X();
138
+ } catch (e) {
139
+ z(e instanceof Error ? e.message : "Failed to update user");
140
+ }
141
+ }, Te = async (e) => {
137
142
  s("medium");
138
143
  try {
139
- let t = await fe(e.id);
144
+ let t = await pe(e.id);
140
145
  t.success ? (c("success"), H(v("auth.users.userDeactivated", "User \"{email}\" has been deactivated.").replace("{email}", e.email)), W(null), X()) : (c("error"), z(t.message));
141
146
  } catch (e) {
142
147
  c("error"), z(e instanceof Error ? e.message : "Failed to delete user");
143
148
  }
144
- }, Te = async (e) => {
149
+ }, Ee = async (e) => {
145
150
  try {
146
- let t = await pe(e.id);
151
+ let t = await me(e.id);
147
152
  t.success ? (H(v("auth.users.userPermanentlyDeleted", "User \"{email}\" has been permanently deleted.").replace("{email}", e.email)), W(null), X()) : z(t.message);
148
153
  } catch (e) {
149
154
  z(e instanceof Error ? e.message : "Failed to hard delete user");
150
155
  }
151
- }, Q = (e) => {
156
+ }, De = (e) => {
152
157
  F(e), L({
153
158
  username: e.username || "",
154
159
  firstName: e.firstName || "",
@@ -159,7 +164,7 @@ function v() {
159
164
  status: e.status,
160
165
  emailVerified: e.emailVerified
161
166
  }), N("edit"), z(null);
162
- }, $ = /* @__PURE__ */ f(o, {
167
+ }, Q = /* @__PURE__ */ f(o, {
163
168
  open: U !== null,
164
169
  onOpenChange: (e) => {
165
170
  e || W(null);
@@ -171,13 +176,13 @@ function v() {
171
176
  onConfirm: async () => {
172
177
  if (U) {
173
178
  if (U.type === "hard-delete") {
174
- await Te(U.user);
179
+ await Ee(U.user);
175
180
  return;
176
181
  }
177
- await we(U.user);
182
+ await Te(U.user);
178
183
  }
179
184
  }
180
- }), Ee = (e) => {
185
+ }), Oe = (e) => {
181
186
  switch (e) {
182
187
  case "SUPER_ADMIN": return "bg-accent-purple-subtle text-accent-purple";
183
188
  case "TENANT_ADMIN": return "bg-status-info-bg-subtle text-status-info-text";
@@ -187,7 +192,7 @@ function v() {
187
192
  case "GUEST": return "bg-bg-sunken text-text-primary";
188
193
  default: return "bg-bg-sunken text-text-primary";
189
194
  }
190
- }, De = (e) => {
195
+ }, $ = (e) => {
191
196
  switch (e) {
192
197
  case "ACTIVE": return "bg-status-success-bg-subtle text-status-success-text";
193
198
  case "INACTIVE": return "bg-status-warning-bg-subtle text-status-warning-text";
@@ -196,7 +201,7 @@ function v() {
196
201
  default: return "bg-bg-sunken text-text-primary";
197
202
  }
198
203
  };
199
- return y ? ue ? /* @__PURE__ */ f("div", {
204
+ return y ? de ? /* @__PURE__ */ f("div", {
200
205
  className: "max-w-6xl mx-auto p-6",
201
206
  children: /* @__PURE__ */ p("div", {
202
207
  className: "text-center py-16",
@@ -205,7 +210,7 @@ function v() {
205
210
  children: v("auth.users.loadingPermissions", "Loading permissions...")
206
211
  })]
207
212
  })
208
- }) : !ce || ye ? /* @__PURE__ */ f("div", {
213
+ }) : !le || be ? /* @__PURE__ */ f("div", {
209
214
  className: "max-w-6xl mx-auto p-6",
210
215
  children: /* @__PURE__ */ f(h, {
211
216
  illustration: "empty-generic",
@@ -219,7 +224,7 @@ function v() {
219
224
  }) : M === "list" ? /* @__PURE__ */ p("div", {
220
225
  className: "max-w-6xl mx-auto p-6",
221
226
  children: [
222
- $,
227
+ Q,
223
228
  /* @__PURE__ */ p("div", {
224
229
  className: "flex items-center justify-between mb-8",
225
230
  children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h1", {
@@ -259,11 +264,11 @@ function v() {
259
264
  }), v("auth.users.export", "Export")] })
260
265
  })]
261
266
  }),
262
- /* @__PURE__ */ f(ae, {
267
+ /* @__PURE__ */ f(oe, {
263
268
  className: "mb-6",
264
269
  children: v("auth.users.purpose", "Manage everyone with access to your organization. Search and filter accounts, review their role and status, edit details, or deactivate people who should no longer have access.")
265
270
  }),
266
- /* @__PURE__ */ p(re, {
271
+ /* @__PURE__ */ p(ie, {
267
272
  className: "mb-6",
268
273
  children: [
269
274
  /* @__PURE__ */ f("p", {
@@ -294,7 +299,7 @@ function v() {
294
299
  children: R
295
300
  }),
296
301
  V && /* @__PURE__ */ p("output", {
297
- id: me,
302
+ id: he,
298
303
  className: "block mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border",
299
304
  children: [/* @__PURE__ */ f("p", {
300
305
  className: "text-status-success-text",
@@ -344,7 +349,7 @@ function v() {
344
349
  placeholder: v("auth.users.searchPlaceholder", "Search by email, username, or name..."),
345
350
  value: G,
346
351
  onChange: (e) => {
347
- be(e.target.value), Y(0);
352
+ xe(e.target.value), Y(0);
348
353
  },
349
354
  className: "w-full px-4 py-2.5 border border-border-default rounded-xl bg-bg-surface text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all"
350
355
  })]
@@ -357,7 +362,7 @@ function v() {
357
362
  id: w,
358
363
  value: K,
359
364
  onChange: (e) => {
360
- xe(e.target.value), Y(0);
365
+ Se(e.target.value), Y(0);
361
366
  },
362
367
  className: "px-4 py-2.5 border border-border-default rounded-xl bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
363
368
  children: [/* @__PURE__ */ f("option", {
@@ -376,7 +381,7 @@ function v() {
376
381
  id: T,
377
382
  value: q,
378
383
  onChange: (e) => {
379
- Se(e.target.value), Y(0);
384
+ Ce(e.target.value), Y(0);
380
385
  },
381
386
  className: "px-4 py-2.5 border border-border-default rounded-xl bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
382
387
  children: [/* @__PURE__ */ f("option", {
@@ -472,14 +477,14 @@ function v() {
472
477
  /* @__PURE__ */ f("td", {
473
478
  className: "p-4",
474
479
  children: /* @__PURE__ */ f("span", {
475
- className: `inline-block px-2.5 py-1 rounded-full text-xs font-medium whitespace-nowrap ${Ee(e.role)}`,
480
+ className: `inline-block px-2.5 py-1 rounded-full text-xs font-medium whitespace-nowrap ${Oe(e.role)}`,
476
481
  children: e.role.replace("_", " ")
477
482
  })
478
483
  }),
479
484
  /* @__PURE__ */ f("td", {
480
485
  className: "p-4",
481
486
  children: /* @__PURE__ */ f("span", {
482
- className: `inline-block px-2.5 py-1 rounded-full text-xs font-medium whitespace-nowrap ${De(e.status)}`,
487
+ className: `inline-block px-2.5 py-1 rounded-full text-xs font-medium whitespace-nowrap ${$(e.status)}`,
483
488
  children: e.status
484
489
  })
485
490
  }),
@@ -528,14 +533,14 @@ function v() {
528
533
  children: [
529
534
  /* @__PURE__ */ f("button", {
530
535
  type: "button",
531
- onClick: () => oe(`/auth/users/${e.id}`),
536
+ onClick: () => se(`/auth/users/${e.id}`),
532
537
  "aria-label": `${v("auth.users.actionView", "View")} ${e.name || e.username || e.email}`,
533
538
  className: "px-2.5 py-1.5 rounded-lg text-xs font-medium border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken transition-colors whitespace-nowrap flex-shrink-0",
534
539
  children: v("auth.users.actionView", "View")
535
540
  }),
536
- le && /* @__PURE__ */ f("button", {
541
+ ue && /* @__PURE__ */ f("button", {
537
542
  type: "button",
538
- onClick: () => Q(e),
543
+ onClick: () => De(e),
539
544
  "aria-label": `${v("auth.users.actionEdit", "Edit")} ${e.name || e.username || e.email}`,
540
545
  className: "px-2.5 py-1.5 rounded-lg text-xs font-medium border border-status-info-border text-text-link bg-bg-surface hover:bg-status-info-bg-subtle transition-colors whitespace-nowrap flex-shrink-0",
541
546
  children: v("auth.users.actionEdit", "Edit")
@@ -591,7 +596,7 @@ function v() {
591
596
  }), /* @__PURE__ */ f("button", {
592
597
  type: "button",
593
598
  onClick: () => Y((e) => e + 1),
594
- disabled: !_e,
599
+ disabled: !ve,
595
600
  "aria-label": v("auth.users.nextPageAriaLabel", "Go to next page"),
596
601
  className: "px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors",
597
602
  children: v("auth.users.nextPage", "Next")
@@ -599,7 +604,7 @@ function v() {
599
604
  })
600
605
  })]
601
606
  }),
602
- /* @__PURE__ */ f(ie, {
607
+ /* @__PURE__ */ f(ae, {
603
608
  className: "mt-8",
604
609
  storageKey: "auth-users",
605
610
  steps: [{
@@ -622,7 +627,7 @@ function v() {
622
627
  }) : M === "view" && P ? /* @__PURE__ */ p("div", {
623
628
  className: "max-w-4xl mx-auto p-6",
624
629
  children: [
625
- $,
630
+ Q,
626
631
  /* @__PURE__ */ p("button", {
627
632
  type: "button",
628
633
  onClick: () => {
@@ -662,10 +667,10 @@ function v() {
662
667
  /* @__PURE__ */ p("div", {
663
668
  className: "flex items-center gap-2 mt-2",
664
669
  children: [/* @__PURE__ */ f("span", {
665
- className: `px-2.5 py-1 rounded-full text-xs font-medium ${Ee(P.role)}`,
670
+ className: `px-2.5 py-1 rounded-full text-xs font-medium ${Oe(P.role)}`,
666
671
  children: P.role.replace("_", " ")
667
672
  }), /* @__PURE__ */ f("span", {
668
- className: `px-2.5 py-1 rounded-full text-xs font-medium ${De(P.status)}`,
673
+ className: `px-2.5 py-1 rounded-full text-xs font-medium ${$(P.status)}`,
669
674
  children: P.status
670
675
  })]
671
676
  })
@@ -773,7 +778,7 @@ function v() {
773
778
  children: [
774
779
  /* @__PURE__ */ f(m, {
775
780
  type: "button",
776
- onClick: () => Q(P),
781
+ onClick: () => De(P),
777
782
  variant: "default",
778
783
  className: "font-semibold",
779
784
  children: v("auth.users.editUser", "Edit User")
@@ -787,7 +792,7 @@ function v() {
787
792
  className: "px-4 py-2.5 rounded-xl border border-status-error-border text-status-error-text font-medium hover:bg-status-error-bg-subtle transition-colors",
788
793
  children: v("auth.users.deleteUser", "Delete User")
789
794
  }),
790
- b && P.status === "DELETED" && se?.role === "SUPER_ADMIN" && /* @__PURE__ */ f("button", {
795
+ b && P.status === "DELETED" && ce?.role === "SUPER_ADMIN" && /* @__PURE__ */ f("button", {
791
796
  type: "button",
792
797
  onClick: () => W({
793
798
  type: "hard-delete",
@@ -804,7 +809,7 @@ function v() {
804
809
  }) : /* @__PURE__ */ p("div", {
805
810
  className: "max-w-2xl mx-auto p-6",
806
811
  children: [
807
- $,
812
+ Q,
808
813
  /* @__PURE__ */ p("button", {
809
814
  type: "button",
810
815
  onClick: () => {
@@ -850,7 +855,7 @@ function v() {
850
855
  })]
851
856
  }),
852
857
  /* @__PURE__ */ p("form", {
853
- onSubmit: Ce,
858
+ onSubmit: we,
854
859
  className: "space-y-6",
855
860
  "aria-describedby": R ? E : void 0,
856
861
  noValidate: !0,