@burdenoff/microfe-auth 2026.1013.1 → 2026.1014.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":"UsersPage.js","names":[],"sources":["../../../src/auth/pages/UsersPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useId } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useAuthPermissions } from \"../hooks/useAuthPermissions\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { validatePhoneNumber } from \"../utils/validation\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { validateUserFields } from \"../utils/validation\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport type {\n User,\n UserRole,\n UserStatus,\n UserFilterInput,\n PaginationInput,\n} from \"../types\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\ntype ViewMode = \"list\" | \"edit\" | \"view\";\n\ninterface FormData {\n username: string;\n firstName: string;\n lastName: string;\n name: string;\n phoneNumber: string;\n role: UserRole;\n status: UserStatus;\n emailVerified: boolean;\n}\n\n// SUPER_ADMIN is determined by email allowlist at runtime, not stored as a\n// DB role — filtering by it always returns 0 rows, so it is excluded here.\nconst USER_ROLES: UserRole[] = [\n \"TENANT_ADMIN\",\n \"ORG_ADMIN\",\n \"WORKSPACE_ADMIN\",\n \"MEMBER\",\n \"GUEST\",\n];\n\nconst USER_STATUSES: UserStatus[] = [\n \"ACTIVE\",\n \"INACTIVE\",\n \"SUSPENDED\",\n \"DELETED\",\n];\n\n/**\n * Users CRUDL page for managing users (admin only)\n */\nexport function UsersPage() {\n const tr = useTr();\n const navigate = useNavigate();\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const {\n canViewUsers,\n canUpdateUsers,\n canDeleteUsers,\n isLoading: permissionsLoading,\n } = useAuthPermissions();\n const { listUsers, updateUser, deleteUser, hardDeleteUser, isLoading } =\n useUserOperations();\n\n // Stable IDs for ARIA associations (accessibility)\n const searchId = useId();\n const roleFilterId = useId();\n const statusFilterId = useId();\n const errorId = useId();\n const successId = useId();\n\n const [isExporting, setIsExporting] = useState(false);\n\n const handleExport = async () => {\n setIsExporting(true);\n try {\n const filter: UserFilterInput = {};\n if (searchQuery) filter.search = searchQuery;\n if (filterRole) filter.role = filterRole;\n if (filterStatus) filter.status = filterStatus;\n\n const result = await listUsers(filter, { offset: 0, limit: 10000 });\n const rows = result.users;\n\n const headers = [\n \"ID\",\n \"Email\",\n \"Username\",\n \"First Name\",\n \"Last Name\",\n \"Phone\",\n \"Role\",\n \"Status\",\n \"Email Verified\",\n \"MFA Enabled\",\n \"Last Login\",\n \"Created At\",\n ];\n\n const escape = (v: string | undefined | null) => {\n if (v == null) return \"\";\n const s = String(v);\n return s.includes(\",\") || s.includes('\"') || s.includes(\"\\n\")\n ? `\"${s.replace(/\"/g, '\"\"')}\"`\n : s;\n };\n\n const lines = [\n headers.join(\",\"),\n ...rows.map((u) =>\n [\n escape(u.id),\n escape(u.email),\n escape(u.username),\n escape(u.firstName),\n escape(u.lastName),\n escape(u.phoneNumber),\n escape(u.role),\n escape(u.status),\n escape(String(u.emailVerified)),\n escape(String(u.mfaEnabled)),\n escape(u.lastLoginAt),\n escape(u.createdAt),\n ].join(\",\"),\n ),\n ];\n\n const blob = new Blob([lines.join(\"\\n\")], {\n type: \"text/csv;charset=utf-8;\",\n });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `users-${new Date().toISOString().slice(0, 10)}.csv`;\n a.click();\n URL.revokeObjectURL(url);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to export users\");\n } finally {\n setIsExporting(false);\n }\n };\n\n const [users, setUsers] = useState<User[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [hasMore, setHasMore] = useState(false);\n const [viewMode, setViewMode] = useState<ViewMode>(\"list\");\n const [selectedUser, setSelectedUser] = useState<User | null>(null);\n const [formData, setFormData] = useState<FormData>({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n role: \"MEMBER\",\n status: \"ACTIVE\",\n emailVerified: false,\n });\n const [error, setError] = useState<string | null>(null);\n const [phoneError, setPhoneError] = useState<string | null>(null);\n const [isUnauthorized, setIsUnauthorized] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingAction, setPendingAction] = useState<{\n type: \"deactivate\" | \"hard-delete\";\n user: User;\n } | null>(null);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [filterRole, setFilterRole] = useState<UserRole | \"\">(\"\");\n const [filterStatus, setFilterStatus] = useState<UserStatus | \"\">(\"\");\n const [currentPage, setCurrentPage] = useState(0);\n const pageSize = 10;\n\n const fetchUsers = useCallback(async () => {\n if (!isAuthenticated) return;\n\n try {\n setIsUnauthorized(false);\n const filter: UserFilterInput = {};\n if (searchQuery) filter.search = searchQuery;\n if (filterRole) filter.role = filterRole;\n if (filterStatus) filter.status = filterStatus;\n\n const pagination: PaginationInput = {\n offset: currentPage * pageSize,\n limit: pageSize,\n };\n\n const result = await listUsers(filter, pagination);\n setUsers(result.users);\n setTotalCount(result.totalCount);\n setHasMore(result.hasMore);\n setError(null);\n } catch (err) {\n // Ignore abort errors - they happen during component unmount/re-render\n if (\n err instanceof Error &&\n (err.name === \"AbortError\" || err.message.includes(\"aborted\"))\n ) {\n return;\n }\n const errorMessage =\n err instanceof Error ? err.message : \"Failed to fetch users\";\n // Check if it's an authorization error\n if (\n errorMessage.toLowerCase().includes(\"not authorized\") ||\n errorMessage.toLowerCase().includes(\"forbidden\")\n ) {\n setIsUnauthorized(true);\n setError(null);\n } else {\n setError(errorMessage);\n }\n }\n }, [\n listUsers,\n searchQuery,\n filterRole,\n filterStatus,\n currentPage,\n isAuthenticated,\n ]);\n\n useEffect(() => {\n fetchUsers();\n }, [fetchUsers]);\n\n const resetForm = () => {\n setFormData({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n role: \"MEMBER\",\n status: \"ACTIVE\",\n emailVerified: false,\n });\n setSelectedUser(null);\n setError(null);\n setPhoneError(null);\n setSuccessMessage(null);\n };\n\n const handleUpdate = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!selectedUser) return;\n setError(null);\n setPhoneError(null);\n\n const phoneValidation = validatePhoneNumber(formData.phoneNumber);\n if (!phoneValidation.valid) {\n const phoneMsg = tr(\n \"auth.users.phoneNumber_invalid\",\n \"Invalid phone number\",\n );\n setPhoneError(phoneMsg);\n setError(phoneMsg);\n return;\n }\n\n // BOFF-7214: same name/username invariants the signup form enforces\n // must hold on user edit too, otherwise an admin (or anyone with\n // users:update) can re-introduce a numbers-only account. Same\n // validator the register form uses, same canonicalised output.\n // Pass `requireAll: false` so an existing user whose optional\n // `firstName`/`lastName` is unset (e.g. provisioned via IdP) can\n // still update unrelated fields like phone or role (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.users.${e.field}_${e.code}`;\n return tr(key, e.message);\n })\n .join(\" · \"),\n );\n return;\n }\n\n try {\n await updateUser(selectedUser.id, {\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 role: formData.role,\n status: formData.status,\n emailVerified: formData.emailVerified,\n });\n setSuccessMessage(\n tr(\"auth.users.updateSuccess\", \"User updated successfully!\"),\n );\n setViewMode(\"list\");\n resetForm();\n fetchUsers();\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to update user\");\n }\n };\n\n const handleDelete = async (user: User) => {\n void nativeImpact(\"medium\");\n try {\n const result = await deleteUser(user.id);\n if (result.success) {\n void nativeNotify(\"success\");\n setSuccessMessage(\n tr(\n \"auth.users.userDeactivated\",\n 'User \"{email}\" has been deactivated.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n fetchUsers();\n } else {\n void nativeNotify(\"error\");\n setError(result.message);\n }\n } catch (err) {\n void nativeNotify(\"error\");\n setError(err instanceof Error ? err.message : \"Failed to delete user\");\n }\n };\n\n const handleHardDelete = async (user: User) => {\n try {\n const result = await hardDeleteUser(user.id);\n if (result.success) {\n setSuccessMessage(\n tr(\n \"auth.users.userPermanentlyDeleted\",\n 'User \"{email}\" has been permanently deleted.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n fetchUsers();\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to hard delete user\",\n );\n }\n };\n\n const startEdit = (user: User) => {\n setSelectedUser(user);\n setFormData({\n username: user.username || \"\",\n firstName: user.firstName || \"\",\n lastName: user.lastName || \"\",\n name: user.name || \"\",\n phoneNumber: user.phoneNumber || \"\",\n role: user.role,\n status: user.status,\n emailVerified: user.emailVerified,\n });\n setViewMode(\"edit\");\n setError(null);\n };\n\n const pendingActionDialog = (\n <ConfirmActionDialog\n open={pendingAction !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPendingAction(null);\n }\n }}\n title={\n pendingAction?.type === \"hard-delete\"\n ? tr(\"auth.users.hardDeleteTitle\", \"Permanently delete user?\")\n : tr(\"auth.users.deactivateTitle\", \"Deactivate user?\")\n }\n description={\n pendingAction?.type === \"hard-delete\"\n ? pendingAction\n ? tr(\n \"auth.users.hardDeleteDesc\",\n 'This permanently deletes \"{email}\" and cannot be undone.',\n ).replace(\"{email}\", pendingAction.user.email)\n : \"\"\n : pendingAction\n ? tr(\n \"auth.users.deactivateDesc\",\n '\"{email}\" will be prevented from signing in until reactivated.',\n ).replace(\"{email}\", pendingAction.user.email)\n : \"\"\n }\n confirmText={\n pendingAction?.type === \"hard-delete\"\n ? tr(\"auth.users.hardDeleteConfirm\", \"Delete permanently\")\n : tr(\"auth.users.deactivateConfirm\", \"Deactivate user\")\n }\n variant={pendingAction?.type === \"hard-delete\" ? \"danger\" : \"warning\"}\n onConfirm={async () => {\n if (!pendingAction) return;\n if (pendingAction.type === \"hard-delete\") {\n await handleHardDelete(pendingAction.user);\n return;\n }\n await handleDelete(pendingAction.user);\n }}\n />\n );\n\n const getRoleColor = (role: UserRole) => {\n switch (role) {\n case \"SUPER_ADMIN\":\n return \"bg-accent-purple-subtle text-accent-purple\";\n case \"TENANT_ADMIN\":\n return \"bg-status-info-bg-subtle text-status-info-text\";\n case \"ORG_ADMIN\":\n return \"bg-accent-indigo-subtle text-accent-indigo\";\n case \"WORKSPACE_ADMIN\":\n return \"bg-accent-cyan-subtle text-accent-cyan\";\n case \"MEMBER\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"GUEST\":\n return \"bg-bg-sunken text-text-primary\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n };\n\n const getStatusColor = (status: UserStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"INACTIVE\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"SUSPENDED\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"DELETED\":\n return \"bg-status-error-bg-subtle text-status-error-text\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n };\n\n // Authentication check\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-6xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.users.authRequired\", \"Authentication Required\")}\n description={tr(\n \"auth.users.signInToManage\",\n \"Please sign in to manage users\",\n )}\n />\n </div>\n );\n }\n\n // Loading permissions\n if (permissionsLoading) {\n return (\n <div className=\"max-w-6xl mx-auto p-6\">\n <div className=\"text-center py-16\">\n <SpinnerIcon className=\"size-8 mx-auto text-text-secondary animate-spin\" />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.users.loadingPermissions\", \"Loading permissions...\")}\n </p>\n </div>\n </div>\n );\n }\n\n // Authorization check - user needs user:read permission\n if (!canViewUsers || isUnauthorized) {\n return (\n <div className=\"max-w-6xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.users.adminAccessRequired\", \"Admin Access Required\")}\n description={\n <>\n {tr(\n \"auth.users.adminAccessDesc\",\n \"You need administrator privileges to view and manage users.\",\n )}{\" \"}\n {tr(\n \"auth.users.requiredRoles\",\n \"Required roles: Super Admin, Tenant Admin, or Org Admin\",\n )}\n </>\n }\n />\n </div>\n );\n }\n\n // List view\n if (viewMode === \"list\") {\n return (\n <div className=\"max-w-6xl mx-auto p-6\">\n {pendingActionDialog}\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <div>\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.users.title\", \"Users\")}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\"auth.users.subtitle\", \"Manage users in your organization\")} (\n {totalCount} {tr(\"auth.users.paginationUsers\", \"total\")})\n </p>\n </div>\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\"auth.users.exportAriaLabel\", \"Export user list\")}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon\n className=\"size-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"auth.users.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.users.export\", \"Export\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.users.purpose\",\n \"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.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: directory summary — the one highlighted zone */}\n <EmphasisPanel className=\"mb-6\">\n <p className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.users.summaryLabel\", \"People in this organization\")}\n </p>\n <p className=\"mt-1 text-3xl font-semibold text-text-primary\">\n {totalCount}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.users.summaryHint\",\n \"Use the filters below to find a specific person or role.\",\n )}\n </p>\n </EmphasisPanel>\n\n {/* Live region for status announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {successMessage}\n </div>\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n >\n {error}\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <output\n id={successId}\n className=\"block mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\"\n >\n <p className=\"text-status-success-text\">{successMessage}</p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline\"\n >\n {tr(\"auth.users.dismiss\", \"Dismiss\")}\n </button>\n </output>\n )}\n\n {/* Error Message */}\n {error && (\n <div\n id={errorId}\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <div className=\"flex-1\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-status-error-text hover:underline\"\n >\n {tr(\"auth.users.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"mb-6 p-4 rounded-xl border border-border-subtle bg-bg-surface\">\n <div className=\"flex flex-wrap gap-4 items-end\">\n {/* Visually hidden labels satisfy WCAG — placeholders are not labels */}\n <div className=\"flex-1 min-w-[200px]\">\n <label htmlFor={searchId} className=\"sr-only\">\n {tr(\"auth.users.searchLabel\", \"Search users\")}\n </label>\n <input\n id={searchId}\n type=\"search\"\n aria-label={tr(\"auth.users.searchLabel\", \"Search users\")}\n placeholder={tr(\n \"auth.users.searchPlaceholder\",\n \"Search by email, username, or name...\",\n )}\n value={searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setCurrentPage(0);\n }}\n 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\"\n />\n </div>\n <div>\n <label htmlFor={roleFilterId} className=\"sr-only\">\n {tr(\"auth.users.filterRoleLabel\", \"Filter by role\")}\n </label>\n <select\n id={roleFilterId}\n value={filterRole}\n onChange={(e) => {\n setFilterRole(e.target.value as UserRole | \"\");\n setCurrentPage(0);\n }}\n 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\"\n >\n <option value=\"\">\n {tr(\"auth.users.allRoles\", \"All Roles\")}\n </option>\n {USER_ROLES.map((role) => (\n <option key={role} value={role}>\n {role.replace(\"_\", \" \")}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor={statusFilterId} className=\"sr-only\">\n {tr(\"auth.users.filterStatusLabel\", \"Filter by status\")}\n </label>\n <select\n id={statusFilterId}\n value={filterStatus}\n onChange={(e) => {\n setFilterStatus(e.target.value as UserStatus | \"\");\n setCurrentPage(0);\n }}\n 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\"\n >\n <option value=\"\">\n {tr(\"auth.users.allStatuses\", \"All Statuses\")}\n </option>\n {USER_STATUSES.map((status) => (\n <option key={status} value={status}>\n {status}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n {isLoading ? (\n <div\n aria-live=\"polite\"\n aria-busy=\"true\"\n className=\"flex items-center justify-center py-12\"\n >\n <SpinnerIcon\n className=\"size-8 text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <span className=\"ml-3 text-text-secondary\">\n {tr(\"auth.users.loadingUsers\", \"Loading users...\")}\n </span>\n </div>\n ) : users.length === 0 ? (\n <IllustratedEmptyState\n illustration={\n searchQuery || filterRole || filterStatus\n ? \"empty-search\"\n : \"empty-people\"\n }\n title={tr(\"auth.users.noUsersFound\", \"No users found\")}\n description={\n searchQuery || filterRole || filterStatus\n ? tr(\n \"auth.users.noUsersFilteredDesc\",\n \"No users match your current search or filters. Try clearing them to see everyone.\",\n )\n : tr(\n \"auth.users.noUsersDesc\",\n \"Once people join your organization they'll show up here for you to manage.\",\n )\n }\n />\n ) : (\n <>\n {/* Users Table */}\n <div className=\"rounded-xl border border-border-subtle bg-bg-surface overflow-hidden shadow-sm\">\n <div className=\"overflow-x-auto\">\n <table\n className=\"w-full min-w-[800px]\"\n aria-label={tr(\"auth.users.tableAriaLabel\", \"Users table\")}\n >\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-canvas\">\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[280px]\"\n >\n {tr(\"auth.users.columnUser\", \"User\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[130px]\"\n >\n {tr(\"auth.users.columnRole\", \"Role\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[100px]\"\n >\n {tr(\"auth.users.columnStatus\", \"Status\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[80px]\"\n >\n {tr(\"auth.users.columnVerified\", \"Verified\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[100px]\"\n >\n {tr(\"auth.users.columnLastLogin\", \"Last Login\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-right p-4 text-sm font-medium text-text-secondary w-[180px]\"\n >\n {tr(\"auth.users.columnActions\", \"Actions\")}\n </th>\n </tr>\n </thead>\n <tbody>\n {users.map((user) => (\n <tr\n key={user.id}\n className=\"border-b border-border-subtle last:border-b-0 hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"p-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"auth-brand-badge flex size-10 shrink-0 items-center justify-center rounded-full font-medium\">\n {(user.name || user.username || user.email)\n .charAt(0)\n .toUpperCase()}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-text-primary truncate\">\n {user.name || user.username}\n </p>\n <p className=\"text-sm text-text-secondary truncate\">\n {user.email}\n </p>\n </div>\n </div>\n </td>\n <td className=\"p-4\">\n <span\n className={`inline-block px-2.5 py-1 rounded-full text-xs font-medium whitespace-nowrap ${getRoleColor(user.role)}`}\n >\n {user.role.replace(\"_\", \" \")}\n </span>\n </td>\n <td className=\"p-4\">\n <span\n className={`inline-block px-2.5 py-1 rounded-full text-xs font-medium whitespace-nowrap ${getStatusColor(user.status)}`}\n >\n {user.status}\n </span>\n </td>\n <td className=\"p-4\">\n {user.emailVerified ? (\n <span className=\"inline-flex items-center gap-1 text-status-success-text whitespace-nowrap\">\n <svg\n className=\"size-4 flex-shrink-0\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\n clipRule=\"evenodd\"\n />\n </svg>\n <span className=\"text-sm\">\n {tr(\"auth.users.verifiedYes\", \"Yes\")}\n </span>\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 text-status-warning-text whitespace-nowrap\">\n <svg\n className=\"size-4 flex-shrink-0\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z\"\n clipRule=\"evenodd\"\n />\n </svg>\n <span className=\"text-sm\">\n {tr(\"auth.users.verifiedNo\", \"No\")}\n </span>\n </span>\n )}\n </td>\n <td className=\"p-4 text-sm text-text-secondary whitespace-nowrap\">\n {user.lastLoginAt\n ? new Date(user.lastLoginAt).toLocaleDateString()\n : tr(\"auth.users.lastLoginNever\", \"Never\")}\n </td>\n <td className=\"p-4\">\n <div className=\"flex justify-end gap-1.5 flex-nowrap\">\n <button\n type=\"button\"\n onClick={() => navigate(`/auth/users/${user.id}`)}\n aria-label={`${tr(\"auth.users.actionView\", \"View\")} ${user.name || user.username || user.email}`}\n 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\"\n >\n {tr(\"auth.users.actionView\", \"View\")}\n </button>\n {canUpdateUsers && (\n <button\n type=\"button\"\n onClick={() => startEdit(user)}\n aria-label={`${tr(\"auth.users.actionEdit\", \"Edit\")} ${user.name || user.username || user.email}`}\n 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\"\n >\n {tr(\"auth.users.actionEdit\", \"Edit\")}\n </button>\n )}\n {canDeleteUsers && user.status !== \"DELETED\" && (\n <button\n type=\"button\"\n onClick={() =>\n setPendingAction({ type: \"deactivate\", user })\n }\n aria-label={`${tr(\"auth.users.actionDelete\", \"Delete\")} ${user.name || user.username || user.email}`}\n className=\"px-2.5 py-1.5 rounded-lg text-xs font-medium border border-status-error-border text-status-error-text bg-bg-surface hover:bg-status-error-bg-subtle transition-colors whitespace-nowrap flex-shrink-0\"\n >\n {tr(\"auth.users.actionDelete\", \"Delete\")}\n </button>\n )}\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n\n {/* Pagination */}\n <div className=\"mt-6 flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"auth.users.showing\", \"Showing\")}{\" \"}\n {currentPage * pageSize + 1} {tr(\"auth.users.to\", \"to\")}{\" \"}\n {Math.min((currentPage + 1) * pageSize, totalCount)}{\" \"}\n {tr(\"auth.users.of\", \"of\")} {totalCount}{\" \"}\n {tr(\"auth.users.paginationUsers\", \"users\")}\n </p>\n <nav\n aria-label={tr(\n \"auth.users.paginationAriaLabel\",\n \"User list pagination\",\n )}\n >\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() =>\n setCurrentPage((prev) => Math.max(0, prev - 1))\n }\n disabled={currentPage === 0}\n aria-label={tr(\n \"auth.users.previousPageAriaLabel\",\n \"Go to previous page\",\n )}\n 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\"\n >\n {tr(\"auth.users.previousPage\", \"Previous\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((prev) => prev + 1)}\n disabled={!hasMore}\n aria-label={tr(\n \"auth.users.nextPageAriaLabel\",\n \"Go to next page\",\n )}\n 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\"\n >\n {tr(\"auth.users.nextPage\", \"Next\")}\n </button>\n </div>\n </nav>\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-users\"\n steps={[\n {\n id: \"search-user\",\n label: tr(\"auth.users.nextSearch\", \"Find a specific person\"),\n description: tr(\n \"auth.users.nextSearchDesc\",\n \"Use the search and role / status filters above to narrow the list.\",\n ),\n onClick: () => {\n const el = document.getElementById(searchId);\n if (el instanceof HTMLInputElement) el.focus();\n },\n },\n {\n id: \"export-users\",\n label: tr(\"auth.users.nextExport\", \"Export the user list\"),\n description: tr(\n \"auth.users.nextExportDesc\",\n \"Download a CSV of users matching your current filters.\",\n ),\n onClick: () => void handleExport(),\n },\n ]}\n />\n </>\n )}\n </div>\n );\n }\n\n // View details\n if (viewMode === \"view\" && selectedUser) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {pendingActionDialog}\n <button\n type=\"button\"\n onClick={() => {\n setViewMode(\"list\");\n resetForm();\n }}\n className=\"mb-6 flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\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=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.users.backToUsers\", \"Back to Users\")}\n </button>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n <div className=\"flex items-center gap-4 mb-8\">\n <div className=\"auth-brand-badge flex size-20 items-center justify-center rounded-full text-3xl font-semibold\">\n {(\n selectedUser.name ||\n selectedUser.username ||\n selectedUser.email\n )\n .charAt(0)\n .toUpperCase()}\n </div>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {selectedUser.name || selectedUser.username}\n </h1>\n <p className=\"text-text-secondary\">{selectedUser.email}</p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getRoleColor(selectedUser.role)}`}\n >\n {selectedUser.role.replace(\"_\", \" \")}\n </span>\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getStatusColor(selectedUser.status)}`}\n >\n {selectedUser.status}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsUsername\", \"Username\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.username}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsEmail\", \"Email\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.email}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsEmailVerified\", \"Email Verified\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.emailVerified ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.users.verifiedYes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-status-warning-text\">\n {tr(\"auth.users.verifiedNo\", \"No\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsMfaEnabled\", \"MFA Enabled\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.mfaEnabled ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.users.verifiedYes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr(\"auth.users.verifiedNo\", \"No\")}\n </span>\n )}\n </p>\n </div>\n\n {selectedUser.phoneNumber && (\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsPhoneNumber\", \"Phone Number\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.phoneNumber}\n </p>\n </div>\n )}\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsLastLogin\", \"Last Login\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.lastLoginAt\n ? new Date(selectedUser.lastLoginAt).toLocaleString()\n : tr(\"auth.users.lastLoginNever\", \"Never\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsCreated\", \"Created\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {new Date(selectedUser.createdAt).toLocaleString()}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsLastUpdated\", \"Last Updated\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {new Date(selectedUser.updatedAt).toLocaleString()}\n </p>\n </div>\n </div>\n\n <div className=\"mt-8 pt-6 border-t border-border-subtle flex gap-3\">\n <Button\n type=\"button\"\n onClick={() => startEdit(selectedUser)}\n variant=\"default\"\n className=\"font-semibold\"\n >\n {tr(\"auth.users.editUser\", \"Edit User\")}\n </Button>\n {selectedUser.status !== \"DELETED\" && (\n <button\n type=\"button\"\n onClick={() =>\n setPendingAction({ type: \"deactivate\", user: selectedUser })\n }\n 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\"\n >\n {tr(\"auth.users.deleteUser\", \"Delete User\")}\n </button>\n )}\n {canDeleteUsers &&\n selectedUser.status === \"DELETED\" &&\n currentUser?.role === \"SUPER_ADMIN\" && (\n <button\n type=\"button\"\n onClick={() =>\n setPendingAction({\n type: \"hard-delete\",\n user: selectedUser,\n })\n }\n className=\"px-4 py-2.5 rounded-xl bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover transition-colors\"\n >\n {tr(\"auth.users.permanentlyDelete\", \"Permanently Delete\")}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n }\n\n // Edit form\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n {pendingActionDialog}\n <button\n type=\"button\"\n onClick={() => {\n setViewMode(\"list\");\n resetForm();\n }}\n className=\"mb-6 flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\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=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.users.backToUsers\", \"Back to Users\")}\n </button>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n <h1 className=\"text-2xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.users.editUser\", \"Edit User\")}\n </h1>\n\n {selectedUser && (\n <p className=\"text-text-secondary mb-6\">\n {tr(\"auth.users.editingUser\", \"Editing:\")}{\" \"}\n <strong>{selectedUser.email}</strong>\n </p>\n )}\n\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\">\n <AlertIcon className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n </div>\n )}\n\n <form\n onSubmit={handleUpdate}\n className=\"space-y-6\"\n aria-describedby={error ? errorId : undefined}\n noValidate\n >\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"edit-username\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldUsername\", \"Username\")}\n </label>\n <input\n id=\"edit-username\"\n type=\"text\"\n aria-label={tr(\"auth.users.fieldUsername\", \"Username\")}\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full px-4 py-3 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\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-name\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldName\", \"Name\")}\n </label>\n <input\n id=\"edit-name\"\n type=\"text\"\n aria-label={tr(\"auth.users.fieldName\", \"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-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\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"edit-firstName\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldFirstName\", \"First Name\")}\n </label>\n <input\n id=\"edit-firstName\"\n type=\"text\"\n aria-label={tr(\"auth.users.fieldFirstName\", \"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-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\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-lastName\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldLastName\", \"Last Name\")}\n </label>\n <input\n id=\"edit-lastName\"\n type=\"text\"\n aria-label={tr(\"auth.users.fieldLastName\", \"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-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\"\n />\n </div>\n </div>\n\n <div>\n <label\n htmlFor=\"edit-phoneNumber\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldPhoneNumber\", \"Phone Number\")}\n </label>\n <input\n id=\"edit-phoneNumber\"\n type=\"tel\"\n aria-label={tr(\"auth.users.fieldPhoneNumber\", \"Phone Number\")}\n aria-invalid={phoneError ? true : undefined}\n aria-describedby={\n phoneError ? \"edit-phoneNumber-error\" : undefined\n }\n value={formData.phoneNumber}\n onChange={(e) => {\n setFormData({ ...formData, phoneNumber: e.target.value });\n if (phoneError) {\n setPhoneError(null);\n setError(null);\n }\n }}\n className=\"block w-full px-4 py-3 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\"\n />\n {phoneError && (\n <p\n id=\"edit-phoneNumber-error\"\n role=\"alert\"\n className=\"mt-2 text-sm text-[var(--color-status-error-text)]\"\n >\n {phoneError}\n </p>\n )}\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"edit-role\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldRole\", \"Role\")}\n </label>\n <select\n id=\"edit-role\"\n aria-label={tr(\"auth.users.fieldRole\", \"Role\")}\n value={formData.role}\n onChange={(e) =>\n setFormData({ ...formData, role: e.target.value as UserRole })\n }\n className=\"block w-full px-4 py-3 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\"\n >\n {USER_ROLES.map((role) => (\n <option key={role} value={role}>\n {role.replace(\"_\", \" \")}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label\n htmlFor=\"edit-status\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldStatus\", \"Status\")}\n </label>\n <select\n id=\"edit-status\"\n aria-label={tr(\"auth.users.fieldStatus\", \"Status\")}\n value={formData.status}\n onChange={(e) =>\n setFormData({\n ...formData,\n status: e.target.value as UserStatus,\n })\n }\n className=\"block w-full px-4 py-3 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\"\n >\n {USER_STATUSES.map((status) => (\n <option key={status} value={status}>\n {status}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-bg-canvas/50\">\n <input\n type=\"checkbox\"\n id=\"emailVerified\"\n checked={formData.emailVerified}\n onChange={(e) =>\n setFormData({ ...formData, emailVerified: e.target.checked })\n }\n className=\"size-5 rounded border-border-default text-text-link focus:ring-action-primary-bg\"\n />\n <label\n htmlFor=\"emailVerified\"\n className=\"text-sm font-medium text-text-primary\"\n >\n {tr(\"auth.users.fieldEmailVerified\", \"Email Verified\")}\n </label>\n </div>\n\n <div className=\"flex gap-3 pt-6 mt-6 border-t border-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.users.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.users.saveChanges\", \"Save Changes\")\n )}\n </Button>\n <button\n type=\"button\"\n onClick={() => {\n setViewMode(\"list\");\n resetForm();\n }}\n className=\"inline-flex items-center h-10 px-8 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.users.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAyCA,IAAM,IAAyB;CAC7B;CACA;CACA;CACA;CACA;AACF,GAEM,IAA8B;CAClC;CACA;CACA;CACA;AACF;AAKA,SAAgB,IAAY;CAC1B,IAAM,IAAK,EAAM,GACX,KAAW,GAAY,GAEvB,IAAkB,GAAc,MAAU,EAAM,eAAe,GAC/D,KAAc,GAAc,MAAU,EAAM,IAAI,GAChD,EACJ,kBACA,oBACA,mBACA,WAAW,OACT,EAAmB,GACjB,EAAE,cAAW,gBAAY,gBAAY,oBAAgB,iBACzD,EAAkB,GAGd,IAAW,EAAM,GACjB,KAAe,EAAM,GACrB,IAAiB,EAAM,GACvB,IAAU,EAAM,GAChB,KAAY,EAAM,GAElB,CAAC,GAAa,KAAkB,EAAS,EAAK,GAE9C,IAAe,YAAY;EAC/B,EAAe,EAAI;EACnB,IAAI;GACF,IAAM,IAA0B,CAAC;GAGjC,AAFI,MAAa,EAAO,SAAS,IAC7B,MAAY,EAAO,OAAO,IAC1B,MAAc,EAAO,SAAS;GAGlC,IAAM,KAAO,MADQ,EAAU,GAAQ;IAAE,QAAQ;IAAG,OAAO;GAAM,CAAC,GAC9C,OAEd,IAAU;IACd;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,GAEM,KAAU,MAAiC;IAC/C,IAAI,KAAK,MAAM,OAAO;IACtB,IAAM,IAAI,OAAO,CAAC;IAClB,OAAO,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,IAAG,KAAK,EAAE,SAAS,IAAI,IACxD,IAAI,EAAE,QAAQ,MAAM,MAAI,EAAE,KAC1B;GACN,GAEM,IAAQ,CACZ,EAAQ,KAAK,GAAG,GAChB,GAAG,EAAK,KAAK,MACX;IACE,EAAO,EAAE,EAAE;IACX,EAAO,EAAE,KAAK;IACd,EAAO,EAAE,QAAQ;IACjB,EAAO,EAAE,SAAS;IAClB,EAAO,EAAE,QAAQ;IACjB,EAAO,EAAE,WAAW;IACpB,EAAO,EAAE,IAAI;IACb,EAAO,EAAE,MAAM;IACf,EAAO,OAAO,EAAE,aAAa,CAAC;IAC9B,EAAO,OAAO,EAAE,UAAU,CAAC;IAC3B,EAAO,EAAE,WAAW;IACpB,EAAO,EAAE,SAAS;GACpB,EAAE,KAAK,GAAG,CACZ,CACF,GAEM,IAAO,IAAI,KAAK,CAAC,EAAM,KAAK,IAAI,CAAC,GAAG,EACxC,MAAM,0BACR,CAAC,GACK,IAAM,IAAI,gBAAgB,CAAI,GAC9B,IAAI,SAAS,cAAc,GAAG;GAIpC,AAHA,EAAE,OAAO,GACT,EAAE,WAAW,0BAAS,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,EAAE,OAC5D,EAAE,MAAM,GACR,IAAI,gBAAgB,CAAG;EACzB,SAAS,GAAK;GACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,wBAAwB;EACxE,UAAU;GACR,EAAe,EAAK;EACtB;CACF,GAEM,CAAC,GAAO,MAAY,EAAiB,CAAC,CAAC,GACvC,CAAC,GAAY,MAAiB,EAAS,CAAC,GACxC,CAAC,IAAS,MAAc,EAAS,EAAK,GACtC,CAAC,GAAU,KAAe,EAAmB,MAAM,GACnD,CAAC,GAAc,KAAmB,EAAsB,IAAI,GAC5D,CAAC,GAAU,KAAe,EAAmB;EACjD,UAAU;EACV,WAAW;EACX,UAAU;EACV,MAAM;EACN,aAAa;EACb,MAAM;EACN,QAAQ;EACR,eAAe;CACjB,CAAC,GACK,CAAC,GAAO,KAAY,EAAwB,IAAI,GAChD,CAAC,GAAY,KAAiB,EAAwB,IAAI,GAC1D,CAAC,IAAgB,KAAqB,EAAS,EAAK,GACpD,CAAC,GAAgB,KAAqB,EAAwB,IAAI,GAClE,CAAC,GAAe,KAAoB,EAGhC,IAAI,GACR,CAAC,GAAa,MAAkB,EAAS,EAAE,GAC3C,CAAC,GAAY,MAAiB,EAAwB,EAAE,GACxD,CAAC,GAAc,MAAmB,EAA0B,EAAE,GAC9D,CAAC,GAAa,KAAkB,EAAS,CAAC,GAG1C,IAAa,GAAY,YAAY;EACpC,OAEL,IAAI;GACF,EAAkB,EAAK;GACvB,IAAM,IAA0B,CAAC;GAGjC,AAFI,MAAa,EAAO,SAAS,IAC7B,MAAY,EAAO,OAAO,IAC1B,MAAc,EAAO,SAAS;GAOlC,IAAM,IAAS,MAAM,EAAU,GAAQ;IAJrC,QAAQ,IAAc;IACtB,OAAO;GAG8B,CAAU;GAIjD,AAHA,GAAS,EAAO,KAAK,GACrB,GAAc,EAAO,UAAU,GAC/B,GAAW,EAAO,OAAO,GACzB,EAAS,IAAI;EACf,SAAS,GAAK;GAEZ,IACE,aAAe,UACd,EAAI,SAAS,gBAAgB,EAAI,QAAQ,SAAS,SAAS,IAE5D;GAEF,IAAM,IACJ,aAAe,QAAQ,EAAI,UAAU;GAEvC,AACE,EAAa,YAAY,EAAE,SAAS,gBAAgB,KACpD,EAAa,YAAY,EAAE,SAAS,WAAW,KAE/C,EAAkB,EAAI,GACtB,EAAS,IAAI,KAEb,EAAS,CAAY;EAEzB;CACF,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,SAAgB;EACd,EAAW;CACb,GAAG,CAAC,CAAU,CAAC;CAEf,IAAM,UAAkB;EActB,AAbA,EAAY;GACV,UAAU;GACV,WAAW;GACX,UAAU;GACV,MAAM;GACN,aAAa;GACb,MAAM;GACN,QAAQ;GACR,eAAe;EACjB,CAAC,GACD,EAAgB,IAAI,GACpB,EAAS,IAAI,GACb,EAAc,IAAI,GAClB,EAAkB,IAAI;CACxB,GAEM,KAAe,OAAO,MAAuB;EAEjD,IADA,EAAE,eAAe,GACb,CAAC,GAAc;EAKnB,IAJA,EAAS,IAAI,GACb,EAAc,IAAI,GAGd,CADoB,GAAoB,EAAS,WAChD,EAAgB,OAAO;GAC1B,IAAM,IAAW,EACf,kCACA,sBACF;GAEA,AADA,EAAc,CAAQ,GACtB,EAAS,CAAQ;GACjB;EACF;EASA,IAAM,IAAa,GAAmB,GAAU,EAAE,YAAY,GAAM,CAAC;EACrE,IAAI,CAAC,EAAW,OAAO;GACrB,EACE,EAAW,OACR,KAAK,MAEG,EAAG,cADgB,EAAE,MAAM,GAAG,EAAE,QACxB,EAAE,OAAO,CACzB,EACA,KAAK,KAAK,CACf;GACA;EACF;EAEA,IAAI;GAgBF,AAfA,MAAM,GAAW,EAAa,IAAI;IAChC,UAAU,EAAW,OAAO,YAAY,KAAA;IACxC,WAAW,EAAW,OAAO,aAAa,KAAA;IAC1C,UAAU,EAAW,OAAO,YAAY,KAAA;IACxC,MAAM,EAAS,QAAQ,KAAA;IACvB,aAAa,EAAS,eAAe,KAAA;IACrC,MAAM,EAAS;IACf,QAAQ,EAAS;IACjB,eAAe,EAAS;GAC1B,CAAC,GACD,EACE,EAAG,4BAA4B,4BAA4B,CAC7D,GACA,EAAY,MAAM,GAClB,EAAU,GACV,EAAW;EACb,SAAS,GAAK;GACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,uBAAuB;EACvE;CACF,GAEM,KAAe,OAAO,MAAe;EACzC,EAAkB,QAAQ;EAC1B,IAAI;GACF,IAAM,IAAS,MAAM,GAAW,EAAK,EAAE;GACvC,AAAI,EAAO,WACT,EAAkB,SAAS,GAC3B,EACE,EACE,8BACA,wCACF,EAAE,QAAQ,WAAW,EAAK,KAAK,CACjC,GACA,EAAiB,IAAI,GACrB,EAAW,MAEX,EAAkB,OAAO,GACzB,EAAS,EAAO,OAAO;EAE3B,SAAS,GAAK;GAEZ,AADA,EAAkB,OAAO,GACzB,EAAS,aAAe,QAAQ,EAAI,UAAU,uBAAuB;EACvE;CACF,GAEM,KAAmB,OAAO,MAAe;EAC7C,IAAI;GACF,IAAM,IAAS,MAAM,GAAe,EAAK,EAAE;GAC3C,AAAI,EAAO,WACT,EACE,EACE,qCACA,gDACF,EAAE,QAAQ,WAAW,EAAK,KAAK,CACjC,GACA,EAAiB,IAAI,GACrB,EAAW,KAEX,EAAS,EAAO,OAAO;EAE3B,SAAS,GAAK;GACZ,EACE,aAAe,QAAQ,EAAI,UAAU,4BACvC;EACF;CACF,GAEM,MAAa,MAAe;EAahC,AAZA,EAAgB,CAAI,GACpB,EAAY;GACV,UAAU,EAAK,YAAY;GAC3B,WAAW,EAAK,aAAa;GAC7B,UAAU,EAAK,YAAY;GAC3B,MAAM,EAAK,QAAQ;GACnB,aAAa,EAAK,eAAe;GACjC,MAAM,EAAK;GACX,QAAQ,EAAK;GACb,eAAe,EAAK;EACtB,CAAC,GACD,EAAY,MAAM,GAClB,EAAS,IAAI;CACf,GAEM,IACJ,kBAAC,GAAD;EACE,MAAM,MAAkB;EACxB,eAAe,MAAS;GACtB,AAAK,KACH,EAAiB,IAAI;EAEzB;EACA,OACE,GAAe,SAAS,gBACpB,EAAG,8BAA8B,0BAA0B,IAC3D,EAAG,8BAA8B,kBAAkB;EAEzD,aACE,GAAe,SAAS,gBACpB,IACE,EACE,6BACA,4DACF,EAAE,QAAQ,WAAW,EAAc,KAAK,KAAK,IAC7C,KACF,IACE,EACE,6BACA,kEACF,EAAE,QAAQ,WAAW,EAAc,KAAK,KAAK,IAC7C;EAER,aACE,GAAe,SAAS,gBACpB,EAAG,gCAAgC,oBAAoB,IACvD,EAAG,gCAAgC,iBAAiB;EAE1D,SAAS,GAAe,SAAS,gBAAgB,WAAW;EAC5D,WAAW,YAAY;GAChB,OACL;QAAI,EAAc,SAAS,eAAe;KACxC,MAAM,GAAiB,EAAc,IAAI;KACzC;IACF;IACA,MAAM,GAAa,EAAc,IAAI;GADrC;EAEF;CACD,CAAA,GAGG,MAAgB,MAAmB;EACvC,QAAQ,GAAR;GACE,KAAK,eACH,OAAO;GACT,KAAK,gBACH,OAAO;GACT,KAAK,aACH,OAAO;GACT,KAAK,mBACH,OAAO;GACT,KAAK,UACH,OAAO;GACT,KAAK,SACH,OAAO;GACT,SACE,OAAO;EACX;CACF,GAEM,MAAkB,MAAuB;EAC7C,QAAQ,GAAR;GACE,KAAK,UACH,OAAO;GACT,KAAK,YACH,OAAO;GACT,KAAK,aACH,OAAO;GACT,KAAK,WACH,OAAO;GACT,SACE,OAAO;EACX;CACF;CAmuBA,OAhuBK,IAgBD,KAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAa,WAAU,kDAAmD,CAAA,GAC1E,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,iCAAiC,wBAAwB;GAC5D,CAAA,CACA;;CACF,CAAA,IAKL,CAAC,MAAgB,KAEjB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAG,kCAAkC,uBAAuB;GACnE,aACE,kBAAA,GAAA,EAAA,UAAA;IACG,EACC,8BACA,6DACF;IAAG;IACF,EACC,4BACA,yDACF;GACA,EAAA,CAAA;EAEL,CAAA;CACE,CAAA,IAKL,MAAa,SAEb,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG;GAED,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,oBAAoB,OAAO;IAC7B,CAAA,GACJ,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAG,uBAAuB,mCAAmC;MAAE;MAC/D;MAAW;MAAE,EAAG,8BAA8B,OAAO;MAAE;KACvD;MACA,EAAA,CAAA,GAEL,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,KAAK,EAAa;KACjC,UAAU;KACV,cAAY,EAAG,8BAA8B,kBAAkB;KAC/D,WAAU;eAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;MACE,WAAU;MACV,eAAY;KACb,CAAA,GACA,EAAG,wBAAwB,cAAc,CAC1C,EAAA,CAAA,IAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;MACP,eAAY;gBAEZ,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;MACH,CAAA;KACE,CAAA,GACJ,EAAG,qBAAqB,QAAQ,CACjC,EAAA,CAAA;IAEE,CAAA,CACL;;GAEL,kBAAC,IAAD;IAAa,WAAU;cACpB,EACC,sBACA,kLACF;GACW,CAAA;GAGb,kBAAC,IAAD;IAAe,WAAU;cAAzB;KACE,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,2BAA2B,6BAA6B;KAC3D,CAAA;KACH,kBAAC,KAAD;MAAG,WAAU;gBACV;KACA,CAAA;KACH,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,0BACA,0DACF;KACC,CAAA;IACU;;GAGf,kBAAC,OAAD;IAAK,aAAU;IAAS,eAAY;IAAO,WAAU;cAClD;GACE,CAAA;GACL,kBAAC,OAAD;IACE,MAAK;IACL,aAAU;IACV,eAAY;IACZ,WAAU;cAET;GACE,CAAA;GAGJ,KACC,kBAAC,UAAD;IACE,IAAI;IACJ,WAAU;cAFZ,CAIE,kBAAC,KAAD;KAAG,WAAU;eAA4B;IAAkB,CAAA,GAC3D,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAkB,IAAI;KACrC,WAAU;eAET,EAAG,sBAAsB,SAAS;IAC7B,CAAA,CACF;;GAIT,KACC,kBAAC,OAAD;IACE,IAAI;IACJ,MAAK;IACL,WAAU;cAHZ,CAKE,kBAAC,GAAD;KACE,WAAU;KACV,eAAY;IACb,CAAA,GACD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAkC;KAAS,CAAA,GACxD,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,IAAI;MAC5B,WAAU;gBAET,EAAG,sBAAsB,SAAS;KAC7B,CAAA,CACL;MACF;;GAIP,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QAAO,SAAS;QAAU,WAAU;kBACjC,EAAG,0BAA0B,cAAc;OACvC,CAAA,GACP,kBAAC,SAAD;QACE,IAAI;QACJ,MAAK;QACL,cAAY,EAAG,0BAA0B,cAAc;QACvD,aAAa,EACX,gCACA,uCACF;QACA,OAAO;QACP,WAAW,MAAM;SAEf,AADA,GAAe,EAAE,OAAO,KAAK,GAC7B,EAAe,CAAC;QAClB;QACA,WAAU;OACX,CAAA,CACE;;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAS;OAAc,WAAU;iBACrC,EAAG,8BAA8B,gBAAgB;MAC7C,CAAA,GACP,kBAAC,UAAD;OACE,IAAI;OACJ,OAAO;OACP,WAAW,MAAM;QAEf,AADA,GAAc,EAAE,OAAO,KAAsB,GAC7C,EAAe,CAAC;OAClB;OACA,WAAU;iBAPZ,CASE,kBAAC,UAAD;QAAQ,OAAM;kBACX,EAAG,uBAAuB,WAAW;OAChC,CAAA,GACP,EAAW,KAAK,MACf,kBAAC,UAAD;QAAmB,OAAO;kBACvB,EAAK,QAAQ,KAAK,GAAG;OAChB,GAFK,CAEL,CACT,CACK;QACL,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAS;OAAgB,WAAU;iBACvC,EAAG,gCAAgC,kBAAkB;MACjD,CAAA,GACP,kBAAC,UAAD;OACE,IAAI;OACJ,OAAO;OACP,WAAW,MAAM;QAEf,AADA,GAAgB,EAAE,OAAO,KAAwB,GACjD,EAAe,CAAC;OAClB;OACA,WAAU;iBAPZ,CASE,kBAAC,UAAD;QAAQ,OAAM;kBACX,EAAG,0BAA0B,cAAc;OACtC,CAAA,GACP,EAAc,KAAK,MAClB,kBAAC,UAAD;QAAqB,OAAO;kBACzB;OACK,GAFK,CAEL,CACT,CACK;QACL,EAAA,CAAA;KACF;;GACF,CAAA;GAEJ,IACC,kBAAC,OAAD;IACE,aAAU;IACV,aAAU;IACV,WAAU;cAHZ,CAKE,kBAAC,GAAD;KACE,WAAU;KACV,eAAY;IACb,CAAA,GACD,kBAAC,QAAD;KAAM,WAAU;eACb,EAAG,2BAA2B,kBAAkB;IAC7C,CAAA,CACH;QACH,EAAM,WAAW,IACnB,kBAAC,GAAD;IACE,cACE,KAAe,KAAc,IACzB,iBACA;IAEN,OAAO,EAAG,2BAA2B,gBAAgB;IACrD,aACE,KAAe,KAAc,IACzB,EACE,kCACA,mFACF,IACA,EACE,0BACA,4EACF;GAEP,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA;IAEE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,SAAD;OACE,WAAU;OACV,cAAY,EAAG,6BAA6B,aAAa;iBAF3D,CAIE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACE,kBAAC,MAAD;UACE,OAAM;UACN,WAAU;oBAET,EAAG,yBAAyB,MAAM;SACjC,CAAA;SACJ,kBAAC,MAAD;UACE,OAAM;UACN,WAAU;oBAET,EAAG,yBAAyB,MAAM;SACjC,CAAA;SACJ,kBAAC,MAAD;UACE,OAAM;UACN,WAAU;oBAET,EAAG,2BAA2B,QAAQ;SACrC,CAAA;SACJ,kBAAC,MAAD;UACE,OAAM;UACN,WAAU;oBAET,EAAG,6BAA6B,UAAU;SACzC,CAAA;SACJ,kBAAC,MAAD;UACE,OAAM;UACN,WAAU;oBAET,EAAG,8BAA8B,YAAY;SAC5C,CAAA;SACJ,kBAAC,MAAD;UACE,OAAM;UACN,WAAU;oBAET,EAAG,4BAA4B,SAAS;SACvC,CAAA;QACF;UACC,CAAA,GACP,kBAAC,SAAD,EAAA,UACG,EAAM,KAAK,MACV,kBAAC,MAAD;QAEE,WAAU;kBAFZ;SAIE,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,OAAD;YAAK,WAAU;uBACX,EAAK,QAAQ,EAAK,YAAY,EAAK,OAClC,OAAO,CAAC,EACR,YAAY;WACZ,CAAA,GACL,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,KAAD;aAAG,WAAU;uBACV,EAAK,QAAQ,EAAK;YAClB,CAAA,GACH,kBAAC,KAAD;aAAG,WAAU;uBACV,EAAK;YACL,CAAA,CACA;aACF;;SACH,CAAA;SACJ,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,QAAD;WACE,WAAW,+EAA+E,GAAa,EAAK,IAAI;qBAE/G,EAAK,KAAK,QAAQ,KAAK,GAAG;UACvB,CAAA;SACJ,CAAA;SACJ,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,QAAD;WACE,WAAW,+EAA+E,GAAe,EAAK,MAAM;qBAEnH,EAAK;UACF,CAAA;SACJ,CAAA;SACJ,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAK,gBACJ,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,OAAD;YACE,WAAU;YACV,MAAK;YACL,SAAQ;sBAER,kBAAC,QAAD;aACE,UAAS;aACT,GAAE;aACF,UAAS;YACV,CAAA;WACE,CAAA,GACL,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAG,0BAA0B,KAAK;WAC/B,CAAA,CACF;eAEN,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,OAAD;YACE,WAAU;YACV,MAAK;YACL,SAAQ;sBAER,kBAAC,QAAD;aACE,UAAS;aACT,GAAE;aACF,UAAS;YACV,CAAA;WACE,CAAA,GACL,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAG,yBAAyB,IAAI;WAC7B,CAAA,CACF;;SAEN,CAAA;SACJ,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAK,cACF,IAAI,KAAK,EAAK,WAAW,EAAE,mBAAmB,IAC9C,EAAG,6BAA6B,OAAO;SACzC,CAAA;SACJ,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,GAAS,eAAe,EAAK,IAAI;aAChD,cAAY,GAAG,EAAG,yBAAyB,MAAM,EAAE,GAAG,EAAK,QAAQ,EAAK,YAAY,EAAK;aACzF,WAAU;uBAET,EAAG,yBAAyB,MAAM;YAC7B,CAAA;YACP,MACC,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,GAAU,CAAI;aAC7B,cAAY,GAAG,EAAG,yBAAyB,MAAM,EAAE,GAAG,EAAK,QAAQ,EAAK,YAAY,EAAK;aACzF,WAAU;uBAET,EAAG,yBAAyB,MAAM;YAC7B,CAAA;YAET,KAAkB,EAAK,WAAW,aACjC,kBAAC,UAAD;aACE,MAAK;aACL,eACE,EAAiB;cAAE,MAAM;cAAc;aAAK,CAAC;aAE/C,cAAY,GAAG,EAAG,2BAA2B,QAAQ,EAAE,GAAG,EAAK,QAAQ,EAAK,YAAY,EAAK;aAC7F,WAAU;uBAET,EAAG,2BAA2B,QAAQ;YACjC,CAAA;WAEP;;SACH,CAAA;QACF;UA9GG,EAAK,EA8GR,CACL,EACI,CAAA,CACF;;KACJ,CAAA;IACF,CAAA;IAGL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACG,EAAG,sBAAsB,SAAS;OAAG;OACrC,IAAc,KAAW;OAAE;OAAE,EAAG,iBAAiB,IAAI;OAAG;OACxD,KAAK,KAAK,IAAc,KAAK,IAAU,CAAU;OAAG;OACpD,EAAG,iBAAiB,IAAI;OAAE;OAAE;OAAY;OACxC,EAAG,8BAA8B,OAAO;MACxC;SACH,kBAAC,OAAD;MACE,cAAY,EACV,kCACA,sBACF;gBAEA,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eACE,GAAgB,MAAS,KAAK,IAAI,GAAG,IAAO,CAAC,CAAC;QAEhD,UAAU,MAAgB;QAC1B,cAAY,EACV,oCACA,qBACF;QACA,WAAU;kBAET,EAAG,2BAA2B,UAAU;OACnC,CAAA,GACR,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,GAAgB,MAAS,IAAO,CAAC;QAChD,UAAU,CAAC;QACX,cAAY,EACV,gCACA,iBACF;QACA,WAAU;kBAET,EAAG,uBAAuB,MAAM;OAC3B,CAAA,CACL;;KACF,CAAA,CACF;;IAEL,kBAAC,IAAD;KACE,WAAU;KACV,YAAW;KACX,OAAO,CACL;MACE,IAAI;MACJ,OAAO,EAAG,yBAAyB,wBAAwB;MAC3D,aAAa,EACX,6BACA,oEACF;MACA,eAAe;OACb,IAAM,IAAK,SAAS,eAAe,CAAQ;OAC3C,AAAI,aAAc,oBAAkB,EAAG,MAAM;MAC/C;KACF,GACA;MACE,IAAI;MACJ,OAAO,EAAG,yBAAyB,sBAAsB;MACzD,aAAa,EACX,6BACA,wDACF;MACA,eAAe,KAAK,EAAa;KACnC,CACF;IACD,CAAA;GACD,EAAA,CAAA;EAED;MAKL,MAAa,UAAU,IAEvB,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG;GACD,kBAAC,UAAD;IACE,MAAK;IACL,eAAe;KAEb,AADA,EAAY,MAAM,GAClB,EAAU;IACZ;IACA,WAAU;cANZ,CAQE,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,SAAQ;KACR,QAAO;eAEP,kBAAC,QAAD;MACE,eAAc;MACd,gBAAe;MACf,aAAa;MACb,GAAE;KACH,CAAA;IACE,CAAA,GACJ,EAAG,0BAA0B,eAAe,CACvC;;GAER,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;kBAEX,EAAa,QACb,EAAa,YACb,EAAa,OAEZ,OAAO,CAAC,EACR,YAAY;MACZ,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAa,QAAQ,EAAa;OACjC,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBAAuB,EAAa;OAAS,CAAA;OAC1D,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SACE,WAAW,gDAAgD,GAAa,EAAa,IAAI;mBAExF,EAAa,KAAK,QAAQ,KAAK,GAAG;QAC/B,CAAA,GACN,kBAAC,QAAD;SACE,WAAW,gDAAgD,GAAe,EAAa,MAAM;mBAE5F,EAAa;QACV,CAAA,CACH;;MACF,EAAA,CAAA,CACF;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,kCAAkC,UAAU;QAC3C,CAAA,GACP,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa;QACb,CAAA,CACA;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,+BAA+B,OAAO;QACrC,CAAA,GACP,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa;QACb,CAAA,CACA;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,uCAAuC,gBAAgB;QACtD,CAAA,GACP,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa,gBACZ,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,0BAA0B,KAAK;SAC/B,CAAA,IAEN,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,yBAAyB,IAAI;SAC7B,CAAA;QAEP,CAAA,CACA;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,oCAAoC,aAAa;QAChD,CAAA,GACP,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa,aACZ,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,0BAA0B,KAAK;SAC/B,CAAA,IAEN,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,yBAAyB,IAAI;SAC7B,CAAA;QAEP,CAAA,CACA;;OAEJ,EAAa,eACZ,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,qCAAqC,cAAc;QAClD,CAAA,GACP,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa;QACb,CAAA,CACA;;OAGP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,mCAAmC,YAAY;QAC9C,CAAA,GACP,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa,cACV,IAAI,KAAK,EAAa,WAAW,EAAE,eAAe,IAClD,EAAG,6BAA6B,OAAO;QAC1C,CAAA,CACA;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,iCAAiC,SAAS;QACzC,CAAA,GACP,kBAAC,KAAD;SAAG,WAAU;mBACV,IAAI,KAAK,EAAa,SAAS,EAAE,eAAe;QAChD,CAAA,CACA;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SAAO,WAAU;mBACd,EAAG,qCAAqC,cAAc;QAClD,CAAA,GACP,kBAAC,KAAD;SAAG,WAAU;mBACV,IAAI,KAAK,EAAa,SAAS,EAAE,eAAe;QAChD,CAAA,CACA;;MACF;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD;QACE,MAAK;QACL,eAAe,GAAU,CAAY;QACrC,SAAQ;QACR,WAAU;kBAET,EAAG,uBAAuB,WAAW;OAChC,CAAA;OACP,EAAa,WAAW,aACvB,kBAAC,UAAD;QACE,MAAK;QACL,eACE,EAAiB;SAAE,MAAM;SAAc,MAAM;QAAa,CAAC;QAE7D,WAAU;kBAET,EAAG,yBAAyB,aAAa;OACpC,CAAA;OAET,KACC,EAAa,WAAW,aACxB,IAAa,SAAS,iBACpB,kBAAC,UAAD;QACE,MAAK;QACL,eACE,EAAiB;SACf,MAAM;SACN,MAAM;QACR,CAAC;QAEH,WAAU;kBAET,EAAG,gCAAgC,oBAAoB;OAClD,CAAA;MAET;;IACF;;EACF;MAMP,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG;GACD,kBAAC,UAAD;IACE,MAAK;IACL,eAAe;KAEb,AADA,EAAY,MAAM,GAClB,EAAU;IACZ;IACA,WAAU;cANZ,CAQE,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,SAAQ;KACR,QAAO;eAEP,kBAAC,QAAD;MACE,eAAc;MACd,gBAAe;MACf,aAAa;MACb,GAAE;KACH,CAAA;IACE,CAAA,GACJ,EAAG,0BAA0B,eAAe,CACvC;;GAER,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,uBAAuB,WAAW;KACpC,CAAA;KAEH,KACC,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACG,EAAG,0BAA0B,UAAU;OAAG;OAC3C,kBAAC,UAAD,EAAA,UAAS,EAAa,MAAc,CAAA;MACnC;;KAGJ,KACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAW,WAAU,qDAAsD,CAAA,GAC3E,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,KAAD;QAAG,WAAU;kBAAkC;OAAS,CAAA;MACrD,CAAA,CACF;;KAGP,kBAAC,QAAD;MACE,UAAU;MACV,WAAU;MACV,oBAAkB,IAAQ,IAAU,KAAA;MACpC,YAAA;gBAJF;OAME,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAET,EAAG,4BAA4B,UAAU;QACrC,CAAA,GACP,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,cAAY,EAAG,4BAA4B,UAAU;SACrD,OAAO,EAAS;SAChB,WAAW,MACT,EAAY;UAAE,GAAG;UAAU,UAAU,EAAE,OAAO;SAAM,CAAC;SAEvD,WAAU;QACX,CAAA,CACE,EAAA,CAAA,GAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAET,EAAG,wBAAwB,MAAM;QAC7B,CAAA,GACP,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,cAAY,EAAG,wBAAwB,MAAM;SAC7C,OAAO,EAAS;SAChB,WAAW,MACT,EAAY;UAAE,GAAG;UAAU,MAAM,EAAE,OAAO;SAAM,CAAC;SAEnD,WAAU;QACX,CAAA,CACE,EAAA,CAAA,CACF;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAET,EAAG,6BAA6B,YAAY;QACxC,CAAA,GACP,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,cAAY,EAAG,6BAA6B,YAAY;SACxD,OAAO,EAAS;SAChB,WAAW,MACT,EAAY;UAAE,GAAG;UAAU,WAAW,EAAE,OAAO;SAAM,CAAC;SAExD,WAAU;QACX,CAAA,CACE,EAAA,CAAA,GAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAET,EAAG,4BAA4B,WAAW;QACtC,CAAA,GACP,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,cAAY,EAAG,4BAA4B,WAAW;SACtD,OAAO,EAAS;SAChB,WAAW,MACT,EAAY;UAAE,GAAG;UAAU,UAAU,EAAE,OAAO;SAAM,CAAC;SAEvD,WAAU;QACX,CAAA,CACE,EAAA,CAAA,CACF;;OAEL,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAET,EAAG,+BAA+B,cAAc;QAC5C,CAAA;QACP,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,cAAY,EAAG,+BAA+B,cAAc;SAC5D,gBAAc,IAAa,KAAO,KAAA;SAClC,oBACE,IAAa,2BAA2B,KAAA;SAE1C,OAAO,EAAS;SAChB,WAAW,MAAM;UAEf,AADA,EAAY;WAAE,GAAG;WAAU,aAAa,EAAE,OAAO;UAAM,CAAC,GACpD,MACF,EAAc,IAAI,GAClB,EAAS,IAAI;SAEjB;SACA,WAAU;QACX,CAAA;QACA,KACC,kBAAC,KAAD;SACE,IAAG;SACH,MAAK;SACL,WAAU;mBAET;QACA,CAAA;OAEF,EAAA,CAAA;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAET,EAAG,wBAAwB,MAAM;QAC7B,CAAA,GACP,kBAAC,UAAD;SACE,IAAG;SACH,cAAY,EAAG,wBAAwB,MAAM;SAC7C,OAAO,EAAS;SAChB,WAAW,MACT,EAAY;UAAE,GAAG;UAAU,MAAM,EAAE,OAAO;SAAkB,CAAC;SAE/D,WAAU;mBAET,EAAW,KAAK,MACf,kBAAC,UAAD;UAAmB,OAAO;oBACvB,EAAK,QAAQ,KAAK,GAAG;SAChB,GAFK,CAEL,CACT;QACK,CAAA,CACL,EAAA,CAAA,GAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAET,EAAG,0BAA0B,QAAQ;QACjC,CAAA,GACP,kBAAC,UAAD;SACE,IAAG;SACH,cAAY,EAAG,0BAA0B,QAAQ;SACjD,OAAO,EAAS;SAChB,WAAW,MACT,EAAY;UACV,GAAG;UACH,QAAQ,EAAE,OAAO;SACnB,CAAC;SAEH,WAAU;mBAET,EAAc,KAAK,MAClB,kBAAC,UAAD;UAAqB,OAAO;oBACzB;SACK,GAFK,CAEL,CACT;QACK,CAAA,CACL,EAAA,CAAA,CACF;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SACE,MAAK;SACL,IAAG;SACH,SAAS,EAAS;SAClB,WAAW,MACT,EAAY;UAAE,GAAG;UAAU,eAAe,EAAE,OAAO;SAAQ,CAAC;SAE9D,WAAU;QACX,CAAA,GACD,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAET,EAAG,iCAAiC,gBAAgB;QAChD,CAAA,CACJ;;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SACE,MAAK;SACL,UAAU;SACV,SAAQ;SACR,MAAK;SACL,WAAU;mBAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAa,WAAU,sBAAuB,CAAA,GAC7C,EAAG,qBAAqB,WAAW,CACpC,EAAA,CAAA,IAEF,EAAG,0BAA0B,cAAc;QAEvC,CAAA,GACR,kBAAC,UAAD;SACE,MAAK;SACL,eAAe;UAEb,AADA,EAAY,MAAM,GAClB,EAAU;SACZ;SACA,WAAU;mBAET,EAAG,qBAAqB,QAAQ;QAC3B,CAAA,CACL;;MACD;;IACH;;EACF;MA7+BH,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAG,2BAA2B,yBAAyB;GAC9D,aAAa,EACX,6BACA,gCACF;EACD,CAAA;CACE,CAAA;AAs+BX"}
1
+ {"version":3,"file":"UsersPage.js","names":[],"sources":["../../../src/auth/pages/UsersPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useId } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useAuthPermissions } from \"../hooks/useAuthPermissions\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { validatePhoneNumber } from \"../utils/validation\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { validateUserFields } from \"../utils/validation\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport type {\n User,\n UserRole,\n UserStatus,\n UserFilterInput,\n PaginationInput,\n} from \"../types\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\ntype ViewMode = \"list\" | \"edit\" | \"view\";\n\ninterface FormData {\n username: string;\n firstName: string;\n lastName: string;\n name: string;\n phoneNumber: string;\n role: UserRole;\n status: UserStatus;\n emailVerified: boolean;\n}\n\n// SUPER_ADMIN is determined by email allowlist at runtime, not stored as a\n// DB role — filtering by it always returns 0 rows, so it is excluded here.\nconst USER_ROLES: UserRole[] = [\n \"TENANT_ADMIN\",\n \"ORG_ADMIN\",\n \"WORKSPACE_ADMIN\",\n \"MEMBER\",\n \"GUEST\",\n];\n\nconst USER_STATUSES: UserStatus[] = [\n \"ACTIVE\",\n \"INACTIVE\",\n \"SUSPENDED\",\n \"DELETED\",\n];\n\n/**\n * Users CRUDL page for managing users (admin only)\n */\nexport function UsersPage() {\n const tr = useTr();\n const navigate = useNavigate();\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const {\n canViewUsers,\n canUpdateUsers,\n canDeleteUsers,\n isLoading: permissionsLoading,\n } = useAuthPermissions();\n const { listUsers, updateUser, deleteUser, hardDeleteUser, isLoading } =\n useUserOperations();\n\n // Stable IDs for ARIA associations (accessibility)\n const searchId = useId();\n const roleFilterId = useId();\n const statusFilterId = useId();\n const errorId = useId();\n const successId = useId();\n\n const [isExporting, setIsExporting] = useState(false);\n\n const handleExport = async () => {\n setIsExporting(true);\n try {\n const filter: UserFilterInput = {};\n if (searchQuery) filter.search = searchQuery;\n if (filterRole) filter.role = filterRole;\n if (filterStatus) filter.status = filterStatus;\n\n const result = await listUsers(filter, { offset: 0, limit: 10000 });\n const rows = result.users;\n\n const headers = [\n \"ID\",\n \"Email\",\n \"Username\",\n \"First Name\",\n \"Last Name\",\n \"Phone\",\n \"Role\",\n \"Status\",\n \"Email Verified\",\n \"MFA Enabled\",\n \"Last Login\",\n \"Created At\",\n ];\n\n const escape = (v: string | undefined | null) => {\n if (v == null) return \"\";\n const s = String(v);\n return s.includes(\",\") || s.includes('\"') || s.includes(\"\\n\")\n ? `\"${s.replace(/\"/g, '\"\"')}\"`\n : s;\n };\n\n const lines = [\n headers.join(\",\"),\n ...rows.map((u) =>\n [\n escape(u.id),\n escape(u.email),\n escape(u.username),\n escape(u.firstName),\n escape(u.lastName),\n escape(u.phoneNumber),\n escape(u.role),\n escape(u.status),\n escape(String(u.emailVerified)),\n escape(String(u.mfaEnabled)),\n escape(u.lastLoginAt),\n escape(u.createdAt),\n ].join(\",\"),\n ),\n ];\n\n const blob = new Blob([lines.join(\"\\n\")], {\n type: \"text/csv;charset=utf-8;\",\n });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `users-${new Date().toISOString().slice(0, 10)}.csv`;\n a.click();\n URL.revokeObjectURL(url);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to export users\");\n } finally {\n setIsExporting(false);\n }\n };\n\n const [users, setUsers] = useState<User[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [hasMore, setHasMore] = useState(false);\n const [viewMode, setViewMode] = useState<ViewMode>(\"list\");\n const [selectedUser, setSelectedUser] = useState<User | null>(null);\n const [formData, setFormData] = useState<FormData>({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n role: \"MEMBER\",\n status: \"ACTIVE\",\n emailVerified: false,\n });\n const [error, setError] = useState<string | null>(null);\n const [phoneError, setPhoneError] = useState<string | null>(null);\n const [isUnauthorized, setIsUnauthorized] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingAction, setPendingAction] = useState<{\n type: \"deactivate\" | \"hard-delete\";\n user: User;\n } | null>(null);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [filterRole, setFilterRole] = useState<UserRole | \"\">(\"\");\n const [filterStatus, setFilterStatus] = useState<UserStatus | \"\">(\"\");\n const [currentPage, setCurrentPage] = useState(0);\n const pageSize = 10;\n\n const fetchUsers = useCallback(async () => {\n if (!isAuthenticated) return;\n\n try {\n setIsUnauthorized(false);\n const filter: UserFilterInput = {};\n if (searchQuery) filter.search = searchQuery;\n if (filterRole) filter.role = filterRole;\n if (filterStatus) filter.status = filterStatus;\n\n const pagination: PaginationInput = {\n offset: currentPage * pageSize,\n limit: pageSize,\n };\n\n const result = await listUsers(filter, pagination);\n setUsers(result.users);\n setTotalCount(result.totalCount);\n setHasMore(result.hasMore);\n setError(null);\n } catch (err) {\n // Ignore abort errors - they happen during component unmount/re-render\n if (\n err instanceof Error &&\n (err.name === \"AbortError\" || err.message.includes(\"aborted\"))\n ) {\n return;\n }\n const errorMessage =\n err instanceof Error ? err.message : \"Failed to fetch users\";\n // Check if it's an authorization error\n if (\n errorMessage.toLowerCase().includes(\"not authorized\") ||\n errorMessage.toLowerCase().includes(\"forbidden\")\n ) {\n setIsUnauthorized(true);\n setError(null);\n } else {\n setError(errorMessage);\n }\n }\n }, [\n listUsers,\n searchQuery,\n filterRole,\n filterStatus,\n currentPage,\n isAuthenticated,\n ]);\n\n useEffect(() => {\n fetchUsers();\n }, [fetchUsers]);\n\n const resetForm = () => {\n setFormData({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n role: \"MEMBER\",\n status: \"ACTIVE\",\n emailVerified: false,\n });\n setSelectedUser(null);\n setError(null);\n setPhoneError(null);\n setSuccessMessage(null);\n };\n\n const handleUpdate = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!selectedUser) return;\n setError(null);\n setPhoneError(null);\n\n const phoneValidation = validatePhoneNumber(formData.phoneNumber);\n if (!phoneValidation.valid) {\n const phoneMsg = tr(\n \"auth.users.phoneNumber_invalid\",\n \"Invalid phone number\",\n );\n setPhoneError(phoneMsg);\n setError(phoneMsg);\n return;\n }\n\n // BOFF-7214: same name/username invariants the signup form enforces\n // must hold on user edit too, otherwise an admin (or anyone with\n // users:update) can re-introduce a numbers-only account. Same\n // validator the register form uses, same canonicalised output.\n // Pass `requireAll: false` so an existing user whose optional\n // `firstName`/`lastName` is unset (e.g. provisioned via IdP) can\n // still update unrelated fields like phone or role (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.users.${e.field}_${e.code}`;\n return tr(key, e.message);\n })\n .join(\" · \"),\n );\n return;\n }\n\n try {\n await updateUser(selectedUser.id, {\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 role: formData.role,\n status: formData.status,\n emailVerified: formData.emailVerified,\n });\n setSuccessMessage(\n tr(\"auth.users.updateSuccess\", \"User updated successfully!\"),\n );\n setViewMode(\"list\");\n resetForm();\n fetchUsers();\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to update user\");\n }\n };\n\n const handleDelete = async (user: User) => {\n void nativeImpact(\"medium\");\n try {\n const result = await deleteUser(user.id);\n if (result.success) {\n void nativeNotify(\"success\");\n setSuccessMessage(\n tr(\n \"auth.users.userDeactivated\",\n 'User \"{email}\" has been deactivated.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n fetchUsers();\n } else {\n void nativeNotify(\"error\");\n setError(result.message);\n }\n } catch (err) {\n void nativeNotify(\"error\");\n setError(err instanceof Error ? err.message : \"Failed to delete user\");\n }\n };\n\n const handleHardDelete = async (user: User) => {\n try {\n const result = await hardDeleteUser(user.id);\n if (result.success) {\n setSuccessMessage(\n tr(\n \"auth.users.userPermanentlyDeleted\",\n 'User \"{email}\" has been permanently deleted.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n fetchUsers();\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to hard delete user\",\n );\n }\n };\n\n const startEdit = (user: User) => {\n setSelectedUser(user);\n setFormData({\n username: user.username || \"\",\n firstName: user.firstName || \"\",\n lastName: user.lastName || \"\",\n name: user.name || \"\",\n phoneNumber: user.phoneNumber || \"\",\n role: user.role,\n status: user.status,\n emailVerified: user.emailVerified,\n });\n setViewMode(\"edit\");\n setError(null);\n };\n\n const pendingActionDialog = (\n <ConfirmActionDialog\n open={pendingAction !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPendingAction(null);\n }\n }}\n title={\n pendingAction?.type === \"hard-delete\"\n ? tr(\"auth.users.hardDeleteTitle\", \"Permanently delete user?\")\n : tr(\"auth.users.deactivateTitle\", \"Deactivate user?\")\n }\n description={\n pendingAction?.type === \"hard-delete\"\n ? pendingAction\n ? tr(\n \"auth.users.hardDeleteDesc\",\n 'This permanently deletes \"{email}\" and cannot be undone.',\n ).replace(\"{email}\", pendingAction.user.email)\n : \"\"\n : pendingAction\n ? tr(\n \"auth.users.deactivateDesc\",\n '\"{email}\" will be prevented from signing in until reactivated.',\n ).replace(\"{email}\", pendingAction.user.email)\n : \"\"\n }\n confirmText={\n pendingAction?.type === \"hard-delete\"\n ? tr(\"auth.users.hardDeleteConfirm\", \"Delete permanently\")\n : tr(\"auth.users.deactivateConfirm\", \"Deactivate user\")\n }\n variant={pendingAction?.type === \"hard-delete\" ? \"danger\" : \"warning\"}\n onConfirm={async () => {\n if (!pendingAction) return;\n if (pendingAction.type === \"hard-delete\") {\n await handleHardDelete(pendingAction.user);\n return;\n }\n await handleDelete(pendingAction.user);\n }}\n />\n );\n\n const getRoleColor = (role: UserRole) => {\n switch (role) {\n case \"SUPER_ADMIN\":\n return \"bg-accent-purple-subtle text-accent-purple\";\n case \"TENANT_ADMIN\":\n return \"bg-status-info-bg-subtle text-status-info-text\";\n case \"ORG_ADMIN\":\n return \"bg-accent-indigo-subtle text-accent-indigo\";\n case \"WORKSPACE_ADMIN\":\n return \"bg-accent-cyan-subtle text-accent-cyan\";\n case \"MEMBER\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"GUEST\":\n return \"bg-bg-sunken text-text-primary\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n };\n\n const getStatusColor = (status: UserStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"INACTIVE\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"SUSPENDED\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"DELETED\":\n return \"bg-status-error-bg-subtle text-status-error-text\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n };\n\n // Authentication check\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-6xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.users.authRequired\", \"Authentication Required\")}\n description={tr(\n \"auth.users.signInToManage\",\n \"Please sign in to manage users\",\n )}\n />\n </div>\n );\n }\n\n // Loading permissions\n if (permissionsLoading) {\n return (\n <div className=\"max-w-6xl mx-auto p-6\">\n <div className=\"text-center py-16\">\n <SpinnerIcon className=\"size-8 mx-auto text-text-secondary animate-spin\" />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.users.loadingPermissions\", \"Loading permissions...\")}\n </p>\n </div>\n </div>\n );\n }\n\n // Authorization check - user needs user:read permission\n if (!canViewUsers || isUnauthorized) {\n return (\n <div className=\"max-w-6xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.users.adminAccessRequired\", \"Admin Access Required\")}\n description={\n <>\n {tr(\n \"auth.users.adminAccessDesc\",\n \"You need administrator privileges to view and manage users.\",\n )}{\" \"}\n {tr(\n \"auth.users.requiredRoles\",\n \"Required roles: Super Admin, Tenant Admin, or Org Admin\",\n )}\n </>\n }\n />\n </div>\n );\n }\n\n // List view\n if (viewMode === \"list\") {\n return (\n <div className=\"max-w-6xl mx-auto p-6\">\n {pendingActionDialog}\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <div>\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.users.title\", \"Users\")}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\"auth.users.subtitle\", \"Manage users in your organization\")} (\n {totalCount} {tr(\"auth.users.paginationUsers\", \"total\")})\n </p>\n </div>\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\"auth.users.exportAriaLabel\", \"Export user list\")}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon\n className=\"size-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"auth.users.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.users.export\", \"Export\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.users.purpose\",\n \"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.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: directory summary — the one highlighted zone */}\n <EmphasisPanel className=\"mb-6\">\n <p className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.users.summaryLabel\", \"People in this organization\")}\n </p>\n <p className=\"mt-1 text-3xl font-semibold text-text-primary\">\n {totalCount}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.users.summaryHint\",\n \"Use the filters below to find a specific person or role.\",\n )}\n </p>\n </EmphasisPanel>\n\n {/* Live region for status announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {successMessage}\n </div>\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n >\n {error}\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <output\n id={successId}\n className=\"block mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\"\n >\n <p className=\"text-status-success-text\">{successMessage}</p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline\"\n >\n {tr(\"auth.users.dismiss\", \"Dismiss\")}\n </button>\n </output>\n )}\n\n {/* Error Message */}\n {error && (\n <div\n id={errorId}\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <div className=\"flex-1\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-status-error-text hover:underline\"\n >\n {tr(\"auth.users.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"mb-6 p-4 rounded-xl border border-border-subtle bg-bg-surface\">\n <div className=\"flex flex-wrap gap-4 items-end\">\n {/* Visually hidden labels satisfy WCAG — placeholders are not labels */}\n <div className=\"flex-1 min-w-[200px]\">\n <label htmlFor={searchId} className=\"sr-only\">\n {tr(\"auth.users.searchLabel\", \"Search users\")}\n </label>\n <input\n id={searchId}\n type=\"search\"\n aria-label={tr(\"auth.users.searchLabel\", \"Search users\")}\n placeholder={tr(\n \"auth.users.searchPlaceholder\",\n \"Search by email, username, or name...\",\n )}\n value={searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setCurrentPage(0);\n }}\n 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\"\n />\n </div>\n <div>\n <label htmlFor={roleFilterId} className=\"sr-only\">\n {tr(\"auth.users.filterRoleLabel\", \"Filter by role\")}\n </label>\n <select\n id={roleFilterId}\n value={filterRole}\n onChange={(e) => {\n setFilterRole(e.target.value as UserRole | \"\");\n setCurrentPage(0);\n }}\n 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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">\n {tr(\"auth.users.allRoles\", \"All Roles\")}\n </option>\n {USER_ROLES.map((role) => (\n <option key={role} value={role}>\n {role.replace(\"_\", \" \")}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor={statusFilterId} className=\"sr-only\">\n {tr(\"auth.users.filterStatusLabel\", \"Filter by status\")}\n </label>\n <select\n id={statusFilterId}\n value={filterStatus}\n onChange={(e) => {\n setFilterStatus(e.target.value as UserStatus | \"\");\n setCurrentPage(0);\n }}\n 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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">\n {tr(\"auth.users.allStatuses\", \"All Statuses\")}\n </option>\n {USER_STATUSES.map((status) => (\n <option key={status} value={status}>\n {status}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n {isLoading ? (\n <div\n aria-live=\"polite\"\n aria-busy=\"true\"\n className=\"flex items-center justify-center py-12\"\n >\n <SpinnerIcon\n className=\"size-8 text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <span className=\"ml-3 text-text-secondary\">\n {tr(\"auth.users.loadingUsers\", \"Loading users...\")}\n </span>\n </div>\n ) : users.length === 0 ? (\n <IllustratedEmptyState\n illustration={\n searchQuery || filterRole || filterStatus\n ? \"empty-search\"\n : \"empty-people\"\n }\n title={tr(\"auth.users.noUsersFound\", \"No users found\")}\n description={\n searchQuery || filterRole || filterStatus\n ? tr(\n \"auth.users.noUsersFilteredDesc\",\n \"No users match your current search or filters. Try clearing them to see everyone.\",\n )\n : tr(\n \"auth.users.noUsersDesc\",\n \"Once people join your organization they'll show up here for you to manage.\",\n )\n }\n />\n ) : (\n <>\n {/* Users Table */}\n <div className=\"rounded-xl border border-border-subtle bg-bg-surface overflow-hidden shadow-sm\">\n <div className=\"overflow-x-auto\">\n <table\n className=\"w-full min-w-[800px]\"\n aria-label={tr(\"auth.users.tableAriaLabel\", \"Users table\")}\n >\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-canvas\">\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[280px]\"\n >\n {tr(\"auth.users.columnUser\", \"User\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[130px]\"\n >\n {tr(\"auth.users.columnRole\", \"Role\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[100px]\"\n >\n {tr(\"auth.users.columnStatus\", \"Status\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[80px]\"\n >\n {tr(\"auth.users.columnVerified\", \"Verified\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-left p-4 text-sm font-medium text-text-secondary w-[100px]\"\n >\n {tr(\"auth.users.columnLastLogin\", \"Last Login\")}\n </th>\n <th\n scope=\"col\"\n className=\"text-right p-4 text-sm font-medium text-text-secondary w-[180px]\"\n >\n {tr(\"auth.users.columnActions\", \"Actions\")}\n </th>\n </tr>\n </thead>\n <tbody>\n {users.map((user) => (\n <tr\n key={user.id}\n className=\"border-b border-border-subtle last:border-b-0 hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"p-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"auth-brand-badge flex size-10 shrink-0 items-center justify-center rounded-full font-medium\">\n {(user.name || user.username || user.email)\n .charAt(0)\n .toUpperCase()}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-text-primary truncate\">\n {user.name || user.username}\n </p>\n <p className=\"text-sm text-text-secondary truncate\">\n {user.email}\n </p>\n </div>\n </div>\n </td>\n <td className=\"p-4\">\n <span\n className={`inline-block px-2.5 py-1 rounded-full text-xs font-medium whitespace-nowrap ${getRoleColor(user.role)}`}\n >\n {user.role.replace(\"_\", \" \")}\n </span>\n </td>\n <td className=\"p-4\">\n <span\n className={`inline-block px-2.5 py-1 rounded-full text-xs font-medium whitespace-nowrap ${getStatusColor(user.status)}`}\n >\n {user.status}\n </span>\n </td>\n <td className=\"p-4\">\n {user.emailVerified ? (\n <span className=\"inline-flex items-center gap-1 text-status-success-text whitespace-nowrap\">\n <svg\n className=\"size-4 flex-shrink-0\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\n clipRule=\"evenodd\"\n />\n </svg>\n <span className=\"text-sm\">\n {tr(\"auth.users.verifiedYes\", \"Yes\")}\n </span>\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 text-status-warning-text whitespace-nowrap\">\n <svg\n className=\"size-4 flex-shrink-0\"\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z\"\n clipRule=\"evenodd\"\n />\n </svg>\n <span className=\"text-sm\">\n {tr(\"auth.users.verifiedNo\", \"No\")}\n </span>\n </span>\n )}\n </td>\n <td className=\"p-4 text-sm text-text-secondary whitespace-nowrap\">\n {user.lastLoginAt\n ? new Date(user.lastLoginAt).toLocaleDateString()\n : tr(\"auth.users.lastLoginNever\", \"Never\")}\n </td>\n <td className=\"p-4\">\n <div className=\"flex justify-end gap-1.5 flex-nowrap\">\n <button\n type=\"button\"\n onClick={() => navigate(`/auth/users/${user.id}`)}\n aria-label={`${tr(\"auth.users.actionView\", \"View\")} ${user.name || user.username || user.email}`}\n 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\"\n >\n {tr(\"auth.users.actionView\", \"View\")}\n </button>\n {canUpdateUsers && (\n <button\n type=\"button\"\n onClick={() => startEdit(user)}\n aria-label={`${tr(\"auth.users.actionEdit\", \"Edit\")} ${user.name || user.username || user.email}`}\n 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\"\n >\n {tr(\"auth.users.actionEdit\", \"Edit\")}\n </button>\n )}\n {canDeleteUsers && user.status !== \"DELETED\" && (\n <button\n type=\"button\"\n onClick={() =>\n setPendingAction({ type: \"deactivate\", user })\n }\n aria-label={`${tr(\"auth.users.actionDelete\", \"Delete\")} ${user.name || user.username || user.email}`}\n className=\"px-2.5 py-1.5 rounded-lg text-xs font-medium border border-status-error-border text-status-error-text bg-bg-surface hover:bg-status-error-bg-subtle transition-colors whitespace-nowrap flex-shrink-0\"\n >\n {tr(\"auth.users.actionDelete\", \"Delete\")}\n </button>\n )}\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n\n {/* Pagination */}\n <div className=\"mt-6 flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"auth.users.showing\", \"Showing\")}{\" \"}\n {currentPage * pageSize + 1} {tr(\"auth.users.to\", \"to\")}{\" \"}\n {Math.min((currentPage + 1) * pageSize, totalCount)}{\" \"}\n {tr(\"auth.users.of\", \"of\")} {totalCount}{\" \"}\n {tr(\"auth.users.paginationUsers\", \"users\")}\n </p>\n <nav\n aria-label={tr(\n \"auth.users.paginationAriaLabel\",\n \"User list pagination\",\n )}\n >\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() =>\n setCurrentPage((prev) => Math.max(0, prev - 1))\n }\n disabled={currentPage === 0}\n aria-label={tr(\n \"auth.users.previousPageAriaLabel\",\n \"Go to previous page\",\n )}\n 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\"\n >\n {tr(\"auth.users.previousPage\", \"Previous\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((prev) => prev + 1)}\n disabled={!hasMore}\n aria-label={tr(\n \"auth.users.nextPageAriaLabel\",\n \"Go to next page\",\n )}\n 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\"\n >\n {tr(\"auth.users.nextPage\", \"Next\")}\n </button>\n </div>\n </nav>\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-users\"\n steps={[\n {\n id: \"search-user\",\n label: tr(\"auth.users.nextSearch\", \"Find a specific person\"),\n description: tr(\n \"auth.users.nextSearchDesc\",\n \"Use the search and role / status filters above to narrow the list.\",\n ),\n onClick: () => {\n const el = document.getElementById(searchId);\n if (el instanceof HTMLInputElement) el.focus();\n },\n },\n {\n id: \"export-users\",\n label: tr(\"auth.users.nextExport\", \"Export the user list\"),\n description: tr(\n \"auth.users.nextExportDesc\",\n \"Download a CSV of users matching your current filters.\",\n ),\n onClick: () => void handleExport(),\n },\n ]}\n />\n </>\n )}\n </div>\n );\n }\n\n // View details\n if (viewMode === \"view\" && selectedUser) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {pendingActionDialog}\n <button\n type=\"button\"\n onClick={() => {\n setViewMode(\"list\");\n resetForm();\n }}\n className=\"mb-6 flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\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=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.users.backToUsers\", \"Back to Users\")}\n </button>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n <div className=\"flex items-center gap-4 mb-8\">\n <div className=\"auth-brand-badge flex size-20 items-center justify-center rounded-full text-3xl font-semibold\">\n {(\n selectedUser.name ||\n selectedUser.username ||\n selectedUser.email\n )\n .charAt(0)\n .toUpperCase()}\n </div>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {selectedUser.name || selectedUser.username}\n </h1>\n <p className=\"text-text-secondary\">{selectedUser.email}</p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getRoleColor(selectedUser.role)}`}\n >\n {selectedUser.role.replace(\"_\", \" \")}\n </span>\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getStatusColor(selectedUser.status)}`}\n >\n {selectedUser.status}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsUsername\", \"Username\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.username}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsEmail\", \"Email\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.email}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsEmailVerified\", \"Email Verified\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.emailVerified ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.users.verifiedYes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-status-warning-text\">\n {tr(\"auth.users.verifiedNo\", \"No\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsMfaEnabled\", \"MFA Enabled\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.mfaEnabled ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.users.verifiedYes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr(\"auth.users.verifiedNo\", \"No\")}\n </span>\n )}\n </p>\n </div>\n\n {selectedUser.phoneNumber && (\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsPhoneNumber\", \"Phone Number\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.phoneNumber}\n </p>\n </div>\n )}\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsLastLogin\", \"Last Login\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {selectedUser.lastLoginAt\n ? new Date(selectedUser.lastLoginAt).toLocaleString()\n : tr(\"auth.users.lastLoginNever\", \"Never\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsCreated\", \"Created\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {new Date(selectedUser.createdAt).toLocaleString()}\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.users.viewDetailsLastUpdated\", \"Last Updated\")}\n </label>\n <p className=\"text-text-primary font-medium\">\n {new Date(selectedUser.updatedAt).toLocaleString()}\n </p>\n </div>\n </div>\n\n <div className=\"mt-8 pt-6 border-t border-border-subtle flex gap-3\">\n <Button\n type=\"button\"\n onClick={() => startEdit(selectedUser)}\n variant=\"default\"\n className=\"font-semibold\"\n >\n {tr(\"auth.users.editUser\", \"Edit User\")}\n </Button>\n {selectedUser.status !== \"DELETED\" && (\n <button\n type=\"button\"\n onClick={() =>\n setPendingAction({ type: \"deactivate\", user: selectedUser })\n }\n 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\"\n >\n {tr(\"auth.users.deleteUser\", \"Delete User\")}\n </button>\n )}\n {canDeleteUsers &&\n selectedUser.status === \"DELETED\" &&\n currentUser?.role === \"SUPER_ADMIN\" && (\n <button\n type=\"button\"\n onClick={() =>\n setPendingAction({\n type: \"hard-delete\",\n user: selectedUser,\n })\n }\n className=\"px-4 py-2.5 rounded-xl bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover transition-colors\"\n >\n {tr(\"auth.users.permanentlyDelete\", \"Permanently Delete\")}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n }\n\n // Edit form\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n {pendingActionDialog}\n <button\n type=\"button\"\n onClick={() => {\n setViewMode(\"list\");\n resetForm();\n }}\n className=\"mb-6 flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\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=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.users.backToUsers\", \"Back to Users\")}\n </button>\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n <h1 className=\"text-2xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.users.editUser\", \"Edit User\")}\n </h1>\n\n {selectedUser && (\n <p className=\"text-text-secondary mb-6\">\n {tr(\"auth.users.editingUser\", \"Editing:\")}{\" \"}\n <strong>{selectedUser.email}</strong>\n </p>\n )}\n\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\">\n <AlertIcon className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n </div>\n )}\n\n <form\n onSubmit={handleUpdate}\n className=\"space-y-6\"\n aria-describedby={error ? errorId : undefined}\n noValidate\n >\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"edit-username\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldUsername\", \"Username\")}\n </label>\n <input\n id=\"edit-username\"\n type=\"text\"\n aria-label={tr(\"auth.users.fieldUsername\", \"Username\")}\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full px-4 py-3 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\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-name\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldName\", \"Name\")}\n </label>\n <input\n id=\"edit-name\"\n type=\"text\"\n aria-label={tr(\"auth.users.fieldName\", \"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-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\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"edit-firstName\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldFirstName\", \"First Name\")}\n </label>\n <input\n id=\"edit-firstName\"\n type=\"text\"\n aria-label={tr(\"auth.users.fieldFirstName\", \"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-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\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-lastName\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldLastName\", \"Last Name\")}\n </label>\n <input\n id=\"edit-lastName\"\n type=\"text\"\n aria-label={tr(\"auth.users.fieldLastName\", \"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-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\"\n />\n </div>\n </div>\n\n <div>\n <label\n htmlFor=\"edit-phoneNumber\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldPhoneNumber\", \"Phone Number\")}\n </label>\n <input\n id=\"edit-phoneNumber\"\n type=\"tel\"\n aria-label={tr(\"auth.users.fieldPhoneNumber\", \"Phone Number\")}\n aria-invalid={phoneError ? true : undefined}\n aria-describedby={\n phoneError ? \"edit-phoneNumber-error\" : undefined\n }\n value={formData.phoneNumber}\n onChange={(e) => {\n setFormData({ ...formData, phoneNumber: e.target.value });\n if (phoneError) {\n setPhoneError(null);\n setError(null);\n }\n }}\n className=\"block w-full px-4 py-3 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\"\n />\n {phoneError && (\n <p\n id=\"edit-phoneNumber-error\"\n role=\"alert\"\n className=\"mt-2 text-sm text-[var(--color-status-error-text)]\"\n >\n {phoneError}\n </p>\n )}\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"edit-role\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldRole\", \"Role\")}\n </label>\n <select\n id=\"edit-role\"\n aria-label={tr(\"auth.users.fieldRole\", \"Role\")}\n value={formData.role}\n onChange={(e) =>\n setFormData({ ...formData, role: e.target.value as UserRole })\n }\n className=\"block w-full px-4 py-3 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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n {USER_ROLES.map((role) => (\n <option key={role} value={role}>\n {role.replace(\"_\", \" \")}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label\n htmlFor=\"edit-status\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {tr(\"auth.users.fieldStatus\", \"Status\")}\n </label>\n <select\n id=\"edit-status\"\n aria-label={tr(\"auth.users.fieldStatus\", \"Status\")}\n value={formData.status}\n onChange={(e) =>\n setFormData({\n ...formData,\n status: e.target.value as UserStatus,\n })\n }\n className=\"block w-full px-4 py-3 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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n {USER_STATUSES.map((status) => (\n <option key={status} value={status}>\n {status}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3 p-4 rounded-xl bg-bg-canvas/50\">\n <input\n type=\"checkbox\"\n id=\"emailVerified\"\n checked={formData.emailVerified}\n onChange={(e) =>\n setFormData({ ...formData, emailVerified: e.target.checked })\n }\n className=\"size-5 rounded border-border-default text-text-link focus:ring-action-primary-bg\"\n />\n <label\n htmlFor=\"emailVerified\"\n className=\"text-sm font-medium text-text-primary\"\n >\n {tr(\"auth.users.fieldEmailVerified\", \"Email Verified\")}\n </label>\n </div>\n\n <div className=\"flex gap-3 pt-6 mt-6 border-t border-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.users.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.users.saveChanges\", \"Save Changes\")\n )}\n </Button>\n <button\n type=\"button\"\n onClick={() => {\n setViewMode(\"list\");\n resetForm();\n }}\n className=\"inline-flex items-center h-10 px-8 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.users.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -35,7 +35,7 @@ function f(e) {
35
35
  sessionStorage.removeItem(a);
36
36
  return;
37
37
  }
38
- if (n) {
38
+ if (n && !(e.user && e.user.emailVerified === !1)) {
39
39
  if (e.user) {
40
40
  localStorage.setItem(l(n, "user"), JSON.stringify(e.user));
41
41
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"authStore.js","names":[],"sources":["../../../src/auth/store/authStore.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { createProfileScopedStorage } from \"@burdenoff/fe-libs/shared/utils\";\nimport type {\n User,\n Session,\n MfaDevice,\n OAuthAccount,\n TrustedDevice,\n SecurityAudit,\n ApiKey,\n App,\n AppUserDelegation,\n Workspace,\n Organization,\n Project,\n} from \"../types\";\n\n/**\n * Sync auth state to profile-scoped storage (ProfileStorageManager keys).\n * The active profile ID comes from sessionStorage (\"bf-active-profile\").\n * Data is stored under localStorage keys: bf-p-{profileId}-user, bf-p-{profileId}-tokens, etc.\n */\nconst BF_PREFIX = \"bf-p\";\nconst BF_PROFILES = \"bf-profiles\";\nconst BF_ACTIVE = \"bf-active-profile\";\nconst AUTH_STORAGE_KEY = \"auth-storage\";\nconst AUTH_STATE_CLEARED_EVENT = \"bf-auth-state-cleared\";\n\nfunction getActiveProfileForSync(): string | null {\n return sessionStorage.getItem(BF_ACTIVE);\n}\n\nfunction pfKey(profileId: string, suffix: string): string {\n return `${BF_PREFIX}-${profileId}-${suffix}`;\n}\n\nfunction clearPersistedAuthStorage(profileId?: string | null): void {\n localStorage.removeItem(AUTH_STORAGE_KEY);\n if (profileId) {\n localStorage.removeItem(pfKey(profileId, AUTH_STORAGE_KEY));\n }\n for (const key of Object.keys(localStorage)) {\n if (\n key.startsWith(`${BF_PREFIX}-`) &&\n key.endsWith(`-${AUTH_STORAGE_KEY}`)\n ) {\n localStorage.removeItem(key);\n }\n }\n}\n\ninterface ProfileMeta {\n id: string;\n email: string;\n name: string;\n avatar?: string;\n lastLogin: string;\n}\n\n/** Dispatch custom event to notify fe-libs AuthProvider of storage changes (same-tab). */\nfunction notifyProfileStorageChange(): void {\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(new Event(\"bf-profile-storage-change\"));\n }\n}\n\nfunction syncToProfileStorage(data: {\n user?: User | null;\n accessToken?: string | null;\n refreshToken?: string | null;\n workspaceToken?: string | null;\n clear?: boolean;\n}) {\n if (typeof window === \"undefined\") return;\n\n const profileId = getActiveProfileForSync();\n if (!profileId && !data.user && !data.clear) return;\n\n // For login/signup: prefer the user's own ID as the target profile.\n // This prevents overwriting an existing active profile when a NEW user\n // signs up or logs in (multi-account scenario).\n const targetId = data.user?.id ?? profileId;\n if (!targetId && !data.clear) return;\n\n if (data.clear) {\n clearPersistedAuthStorage(targetId);\n if (!targetId) {\n localStorage.removeItem(BF_PROFILES);\n sessionStorage.removeItem(BF_ACTIVE);\n return;\n }\n // Remove profile data keys\n localStorage.removeItem(pfKey(targetId, \"user\"));\n localStorage.removeItem(pfKey(targetId, \"tokens\"));\n localStorage.removeItem(pfKey(targetId, \"workspace-token\"));\n // Remove from profiles list\n try {\n const profilesRaw = localStorage.getItem(BF_PROFILES);\n const profiles: ProfileMeta[] = profilesRaw\n ? JSON.parse(profilesRaw)\n : [];\n const filtered = profiles.filter((p) => p.id !== targetId);\n localStorage.setItem(BF_PROFILES, JSON.stringify(filtered));\n } catch {\n /* ignore */\n }\n sessionStorage.removeItem(BF_ACTIVE);\n return;\n }\n\n if (!targetId) return;\n\n if (data.user) {\n localStorage.setItem(pfKey(targetId, \"user\"), JSON.stringify(data.user));\n // Update profiles list\n try {\n const profilesRaw = localStorage.getItem(BF_PROFILES);\n const profiles: ProfileMeta[] = profilesRaw\n ? JSON.parse(profilesRaw)\n : [];\n const existing = profiles.filter((p) => p.id !== targetId);\n existing.push({\n id: targetId,\n email: data.user.email ?? \"\",\n name:\n data.user.name ??\n `${data.user.firstName ?? \"\"} ${data.user.lastName ?? \"\"}`.trim(),\n avatar: data.user.avatar,\n lastLogin: new Date().toISOString(),\n });\n localStorage.setItem(BF_PROFILES, JSON.stringify(existing));\n } catch {\n /* ignore */\n }\n // Set as active profile in this tab\n sessionStorage.setItem(BF_ACTIVE, targetId);\n }\n\n if (data.accessToken !== undefined || data.refreshToken !== undefined) {\n const existingRaw = localStorage.getItem(pfKey(targetId, \"tokens\"));\n const existing = existingRaw ? JSON.parse(existingRaw) : {};\n // Compute expiresAt from the actual JWT exp claim if a new access token is provided\n let expiresAt = existing.expiresAt ?? Date.now() + 60 * 60 * 1000;\n const tokenToCheck = data.accessToken ?? existing.accessToken;\n if (tokenToCheck) {\n try {\n const raw = tokenToCheck\n .split(\".\")[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n const padded = raw.padEnd(Math.ceil(raw.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded));\n if (typeof payload.exp === \"number\") {\n expiresAt = payload.exp * 1000; // JWT exp is in seconds → ms\n }\n } catch {\n // Fallback: keep existing or default 1h\n }\n }\n const merged = {\n ...existing,\n ...(data.accessToken !== undefined\n ? { accessToken: data.accessToken }\n : {}),\n ...(data.refreshToken !== undefined\n ? { refreshToken: data.refreshToken }\n : {}),\n expiresAt,\n };\n if (merged.accessToken) {\n localStorage.setItem(pfKey(targetId, \"tokens\"), JSON.stringify(merged));\n } else {\n localStorage.removeItem(pfKey(targetId, \"tokens\"));\n }\n }\n\n if (data.workspaceToken !== undefined) {\n if (data.workspaceToken) {\n localStorage.setItem(\n pfKey(targetId, \"workspace-token\"),\n data.workspaceToken,\n );\n } else {\n localStorage.removeItem(pfKey(targetId, \"workspace-token\"));\n }\n }\n\n // Notify fe-libs AuthProvider of the change (same-tab custom event)\n notifyProfileStorageChange();\n\n // Set SSO cookie for cross-domain session sharing (works in all envs)\n // Stores profile ID + the origin where the user logged in.\n // Other shells use the origin to redirect to /auth/sso-relay for token relay.\n try {\n const hostname = window.location.hostname;\n // For local dev, set cookie on shared .local.burdenoff.com domain\n // For production, set cookie on current domain only (relay handles cross-domain)\n const isSharedDomain = hostname.endsWith(\".local.burdenoff.com\");\n const domain = isSharedDomain ? \".local.burdenoff.com\" : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const cookieOpts = `${domainPart}; path=/; samesite=lax${securePart}`;\n\n if (data.accessToken && targetId) {\n // Store profile ID + login origin for SSO relay\n const ssoValue = JSON.stringify({\n id: targetId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${cookieOpts}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${cookieOpts}; max-age=86400`;\n }\n\n if (data.clear) {\n document.cookie = \"bf-sso=; path=/; max-age=0\";\n document.cookie = \"bf-sso-profile=; path=/; max-age=0\";\n if (domain) {\n document.cookie = `bf-sso=; path=/; domain=${domain}; max-age=0`;\n document.cookie = `bf-sso-profile=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && ![\"localhost\", \"127.0.0.1\"].includes(hostname)) {\n document.cookie = `bf-sso=; path=/; domain=${hostname}; max-age=0`;\n document.cookie = `bf-sso-profile=; path=/; domain=${hostname}; max-age=0`;\n }\n }\n } catch {\n /* ignore */\n }\n}\n\ninterface AuthState {\n // Auth state\n user: User | null;\n accessToken: string | null;\n refreshToken: string | null;\n isAuthenticated: boolean;\n\n // Organization state\n organizations: Organization[];\n currentOrganizationId: string | null;\n isLoadingOrganizations: boolean;\n\n // Workspace state\n workspaces: Workspace[];\n currentWorkspaceId: string | null;\n workspaceToken: string | null;\n isLoadingWorkspaces: boolean;\n\n // Project state\n projects: Project[];\n currentProjectId: string | null;\n isLoadingProjects: boolean;\n\n // MFA state\n mfaRequired: boolean;\n mfaChallengeId: string | null;\n mfaDevices: MfaDevice[];\n\n // Session state\n activeSessions: Session[];\n currentSessionId: string | null;\n\n // OAuth state\n linkedAccounts: OAuthAccount[];\n\n // Security state\n trustedDevices: TrustedDevice[];\n securityEvents: SecurityAudit[];\n\n // API Keys state\n apiKeys: ApiKey[];\n\n // Apps state\n apps: App[];\n currentApp: App | null;\n\n // Delegations state\n delegations: AppUserDelegation[];\n grantedDelegations: AppUserDelegation[];\n\n // UI state\n isLoading: boolean;\n error: string | null;\n errorCode: string | null;\n\n // Actions - Auth\n setUser: (user: User | null) => void;\n setAccessToken: (token: string | null) => void;\n setAuthTokens: (accessToken: string, refreshToken: string) => void;\n setAuthenticated: (isAuthenticated: boolean) => void;\n clearAuth: () => void;\n\n // Actions - Organization\n setOrganizations: (organizations: Organization[]) => void;\n setCurrentOrganizationId: (orgId: string | null) => void;\n setLoadingOrganizations: (loading: boolean) => void;\n\n // Actions - Workspace\n setWorkspaces: (workspaces: Workspace[]) => void;\n setCurrentWorkspaceId: (workspaceId: string | null) => void;\n setWorkspaceToken: (token: string | null) => void;\n setLoadingWorkspaces: (loading: boolean) => void;\n clearWorkspace: () => void;\n\n // Actions - Project\n setProjects: (projects: Project[]) => void;\n setCurrentProjectId: (projectId: string | null) => void;\n setLoadingProjects: (loading: boolean) => void;\n clearProject: () => void;\n\n // Actions - Context (clears org, workspace, project)\n clearContext: () => void;\n\n // Actions - Profile Switch (load from profile storage without syncing back)\n loadFromProfile: (profileId: string) => void;\n\n // Actions - MFA\n setMfaRequired: (required: boolean, challengeId?: string) => void;\n setMfaDevices: (devices: MfaDevice[]) => void;\n removeMfaDevice: (deviceId: string) => void;\n\n // Actions - Sessions\n setActiveSessions: (sessions: Session[]) => void;\n removeSession: (sessionId: string) => void;\n\n // Actions - OAuth\n setLinkedAccounts: (accounts: OAuthAccount[]) => void;\n removeLinkedAccount: (accountId: string) => void;\n\n // Actions - Security\n setTrustedDevices: (devices: TrustedDevice[]) => void;\n removeTrustedDevice: (deviceId: string) => void;\n setSecurityEvents: (events: SecurityAudit[]) => void;\n\n // Actions - API Keys\n setApiKeys: (keys: ApiKey[]) => void;\n removeApiKey: (keyId: string) => void;\n\n // Actions - Apps\n setApps: (apps: App[]) => void;\n setCurrentApp: (app: App | null) => void;\n addApp: (app: App) => void;\n updateApp: (app: App) => void;\n removeApp: (appId: string) => void;\n\n // Actions - Delegations\n setDelegations: (delegations: AppUserDelegation[]) => void;\n setGrantedDelegations: (delegations: AppUserDelegation[]) => void;\n addDelegation: (delegation: AppUserDelegation) => void;\n removeDelegation: (delegationId: string) => void;\n\n // Actions - UI\n setLoading: (loading: boolean) => void;\n setError: (error: string | null, errorCode?: string | null) => void;\n}\n\n/**\n * Auth Zustand store — browser-tab-local state.\n *\n * SAFE for in-memory (no Redis needed):\n * - accessToken / refreshToken / user: per-tab, backed by profileScopedStorage\n * (localStorage key bf-p-{profileId}-*). Tabs sharing the same profile ID\n * stay in sync via the \"bf-profile-storage-change\" custom event + \"storage\"\n * cross-tab event.\n * - workspaceToken: same pattern as above.\n * - mfaRequired / mfaChallengeId: transient login-flow state; not needed\n * cross-tab (each login has its own challenge).\n * - organizations / workspaces / projects lists: cached context, re-fetched\n * after account switch; no cross-tab sharing needed.\n * - UI state (isLoading, error): purely ephemeral.\n *\n * NOT safe for in-memory (needs Redis in future if required server-side):\n * - Nothing currently — all server-side session tracking is done by the\n * backend (global-auth-svc) using its own DB. The frontend only holds\n * short-lived JWT access tokens that expire independently.\n */\nexport const useAuthStore = create<AuthState>()(\n persist(\n (set) => ({\n // Initial state\n user: null,\n accessToken: null,\n refreshToken: null,\n isAuthenticated: false,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n workspaceToken: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n mfaRequired: false,\n mfaChallengeId: null,\n mfaDevices: [],\n activeSessions: [],\n currentSessionId: null,\n linkedAccounts: [],\n trustedDevices: [],\n securityEvents: [],\n apiKeys: [],\n apps: [],\n currentApp: null,\n delegations: [],\n grantedDelegations: [],\n isLoading: false,\n error: null,\n errorCode: null,\n\n // Actions - Auth (with profile storage sync)\n setUser: (user) => {\n set({ user });\n syncToProfileStorage({ user });\n },\n\n setAccessToken: (token) => {\n set({ accessToken: token });\n syncToProfileStorage({ accessToken: token });\n },\n\n setAuthTokens: (accessToken, refreshToken) => {\n // Only set tokens here. Do NOT overwrite user — setUser() handles that\n // with the full user object from the GraphQL response (firstName, lastName, etc.).\n // Fall back to JWT-decoded user ONLY if no user is currently in the store.\n const currentState = useAuthStore.getState();\n if (currentState.user) {\n // User already set (from setUser call) — just update tokens\n set({ accessToken, refreshToken });\n syncToProfileStorage({ accessToken, refreshToken });\n } else {\n // No user yet — decode JWT as fallback\n try {\n const b64 = accessToken\n .split(\".\")[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded));\n const user: User = {\n id: payload.sub,\n email: payload.email,\n username: payload.username,\n role: payload.role,\n status: \"ACTIVE\",\n emailVerified: true,\n mfaEnabled: false,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n set({ accessToken, refreshToken, user });\n syncToProfileStorage({ accessToken, refreshToken, user });\n } catch (error) {\n console.error(\"[AuthStore] Failed to decode JWT:\", error);\n set({ accessToken, refreshToken });\n syncToProfileStorage({ accessToken, refreshToken });\n }\n }\n },\n\n setAuthenticated: (isAuthenticated) => set({ isAuthenticated }),\n\n clearAuth: () => {\n set({\n user: null,\n accessToken: null,\n refreshToken: null,\n isAuthenticated: false,\n mfaRequired: false,\n mfaChallengeId: null,\n activeSessions: [],\n currentSessionId: null,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n workspaceToken: null,\n currentWorkspaceId: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n });\n syncToProfileStorage({ clear: true });\n },\n\n // Actions - Organization\n setOrganizations: (organizations) => set({ organizations }),\n\n setCurrentOrganizationId: (orgId) =>\n set((state) =>\n state.currentOrganizationId === orgId\n ? state\n : { currentOrganizationId: orgId },\n ),\n\n setLoadingOrganizations: (loading) =>\n set({ isLoadingOrganizations: loading }),\n\n // Actions - Workspace\n setWorkspaces: (workspaces) => set({ workspaces }),\n\n setCurrentWorkspaceId: (workspaceId) =>\n set((state) =>\n state.currentWorkspaceId === workspaceId\n ? state\n : { currentWorkspaceId: workspaceId },\n ),\n\n setWorkspaceToken: (token) => {\n set({ workspaceToken: token });\n syncToProfileStorage({ workspaceToken: token });\n },\n\n setLoadingWorkspaces: (loading) => set({ isLoadingWorkspaces: loading }),\n\n clearWorkspace: () =>\n set({ currentWorkspaceId: null, workspaceToken: null, workspaces: [] }),\n\n // Actions - Project\n setProjects: (projects) => set({ projects }),\n\n setCurrentProjectId: (projectId) =>\n set((state) =>\n state.currentProjectId === projectId\n ? state\n : { currentProjectId: projectId },\n ),\n\n setLoadingProjects: (loading) => set({ isLoadingProjects: loading }),\n\n clearProject: () => set({ currentProjectId: null, projects: [] }),\n\n // Actions - Context (clears org, workspace, project)\n clearContext: () =>\n set({\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n workspaceToken: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n }),\n\n // Actions - Profile Switch\n // Loads user/tokens from profile-scoped localStorage WITHOUT syncing back.\n // Used during account switching to align zustand with the new active profile.\n loadFromProfile: (profileId: string) => {\n let user: User | null = null;\n let accessToken: string | null = null;\n let refreshToken: string | null = null;\n let workspaceToken: string | null = null;\n\n try {\n const userRaw = localStorage.getItem(pfKey(profileId, \"user\"));\n if (userRaw) user = JSON.parse(userRaw);\n } catch {\n /* ignore */\n }\n\n try {\n const tokensRaw = localStorage.getItem(pfKey(profileId, \"tokens\"));\n if (tokensRaw) {\n const tokens = JSON.parse(tokensRaw);\n accessToken = tokens.accessToken ?? null;\n refreshToken = tokens.refreshToken ?? null;\n }\n } catch {\n /* ignore */\n }\n\n workspaceToken = localStorage.getItem(\n pfKey(profileId, \"workspace-token\"),\n );\n\n // Set everything in one batch — clears context data so it will be re-fetched\n set({\n user,\n accessToken,\n refreshToken,\n isAuthenticated: !!accessToken && !!user,\n workspaceToken,\n // Clear all context — will be re-fetched for the new user\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n // Clear MFA state\n mfaRequired: false,\n mfaChallengeId: null,\n });\n\n // Notify listeners (e.g., AuthTokenProvider) without writing back to storage\n notifyProfileStorageChange();\n },\n\n // Actions - MFA\n setMfaRequired: (required, challengeId) =>\n set({ mfaRequired: required, mfaChallengeId: challengeId || null }),\n\n setMfaDevices: (devices) => set({ mfaDevices: devices }),\n\n removeMfaDevice: (deviceId) =>\n set((state) => ({\n mfaDevices: state.mfaDevices.filter((d) => d.id !== deviceId),\n })),\n\n // Actions - Sessions\n setActiveSessions: (sessions) => set({ activeSessions: sessions }),\n\n removeSession: (sessionId) =>\n set((state) => ({\n activeSessions: state.activeSessions.filter(\n (s) => s.id !== sessionId,\n ),\n })),\n\n // Actions - OAuth\n setLinkedAccounts: (accounts) => set({ linkedAccounts: accounts }),\n\n removeLinkedAccount: (accountId) =>\n set((state) => ({\n linkedAccounts: state.linkedAccounts.filter(\n (a) => a.id !== accountId,\n ),\n })),\n\n // Actions - Security\n setTrustedDevices: (devices) => set({ trustedDevices: devices }),\n\n removeTrustedDevice: (deviceId) =>\n set((state) => ({\n trustedDevices: state.trustedDevices.filter((d) => d.id !== deviceId),\n })),\n\n setSecurityEvents: (events) => set({ securityEvents: events }),\n\n // Actions - API Keys\n setApiKeys: (keys) => set({ apiKeys: keys }),\n\n removeApiKey: (keyId) =>\n set((state) => ({\n apiKeys: state.apiKeys.filter((k) => k.id !== keyId),\n })),\n\n // Actions - Apps\n setApps: (apps) => set({ apps }),\n\n setCurrentApp: (app) => set({ currentApp: app }),\n\n addApp: (app) =>\n set((state) => ({\n apps: [...state.apps, app],\n })),\n\n updateApp: (app) =>\n set((state) => ({\n apps: state.apps.map((a) => (a.id === app.id ? app : a)),\n currentApp: state.currentApp?.id === app.id ? app : state.currentApp,\n })),\n\n removeApp: (appId) =>\n set((state) => ({\n apps: state.apps.filter((a) => a.id !== appId),\n currentApp: state.currentApp?.id === appId ? null : state.currentApp,\n })),\n\n // Actions - Delegations\n setDelegations: (delegations) => set({ delegations }),\n\n setGrantedDelegations: (delegations) =>\n set({ grantedDelegations: delegations }),\n\n addDelegation: (delegation) =>\n set((state) => ({\n delegations: [...state.delegations, delegation],\n })),\n\n removeDelegation: (delegationId) =>\n set((state) => ({\n delegations: state.delegations.filter((d) => d.id !== delegationId),\n grantedDelegations: state.grantedDelegations.filter(\n (d) => d.id !== delegationId,\n ),\n })),\n\n // Actions - UI\n setLoading: (loading) => set({ isLoading: loading }),\n\n setError: (error, errorCode) =>\n set({ error, errorCode: errorCode ?? null }),\n }),\n {\n name: \"auth-storage\",\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n accessToken: state.accessToken,\n refreshToken: state.refreshToken,\n user: state.user,\n isAuthenticated: state.isAuthenticated,\n // Context state\n currentOrganizationId: state.currentOrganizationId,\n currentWorkspaceId: state.currentWorkspaceId,\n currentProjectId: state.currentProjectId,\n workspaceToken: state.workspaceToken,\n // MFA state must be persisted for login verification flow\n mfaRequired: state.mfaRequired,\n mfaChallengeId: state.mfaChallengeId,\n }),\n },\n ),\n);\n\ntype AuthClearWindow = Window & { __bfAuthClearListenerInstalled?: boolean };\n\nif (typeof window !== \"undefined\") {\n const authWindow = window as AuthClearWindow;\n if (!authWindow.__bfAuthClearListenerInstalled) {\n authWindow.__bfAuthClearListenerInstalled = true;\n window.addEventListener(AUTH_STATE_CLEARED_EVENT, () => {\n const state = useAuthStore.getState();\n if (\n state.isAuthenticated ||\n state.accessToken ||\n state.refreshToken ||\n state.user\n ) {\n state.clearAuth();\n return;\n }\n clearPersistedAuthStorage(sessionStorage.getItem(BF_ACTIVE));\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"authStore.js","names":[],"sources":["../../../src/auth/store/authStore.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { createProfileScopedStorage } from \"@burdenoff/fe-libs/shared/utils\";\nimport type {\n User,\n Session,\n MfaDevice,\n OAuthAccount,\n TrustedDevice,\n SecurityAudit,\n ApiKey,\n App,\n AppUserDelegation,\n Workspace,\n Organization,\n Project,\n} from \"../types\";\n\n/**\n * Sync auth state to profile-scoped storage (ProfileStorageManager keys).\n * The active profile ID comes from sessionStorage (\"bf-active-profile\").\n * Data is stored under localStorage keys: bf-p-{profileId}-user, bf-p-{profileId}-tokens, etc.\n */\nconst BF_PREFIX = \"bf-p\";\nconst BF_PROFILES = \"bf-profiles\";\nconst BF_ACTIVE = \"bf-active-profile\";\nconst AUTH_STORAGE_KEY = \"auth-storage\";\nconst AUTH_STATE_CLEARED_EVENT = \"bf-auth-state-cleared\";\n\nfunction getActiveProfileForSync(): string | null {\n return sessionStorage.getItem(BF_ACTIVE);\n}\n\nfunction pfKey(profileId: string, suffix: string): string {\n return `${BF_PREFIX}-${profileId}-${suffix}`;\n}\n\nfunction clearPersistedAuthStorage(profileId?: string | null): void {\n localStorage.removeItem(AUTH_STORAGE_KEY);\n if (profileId) {\n localStorage.removeItem(pfKey(profileId, AUTH_STORAGE_KEY));\n }\n for (const key of Object.keys(localStorage)) {\n if (\n key.startsWith(`${BF_PREFIX}-`) &&\n key.endsWith(`-${AUTH_STORAGE_KEY}`)\n ) {\n localStorage.removeItem(key);\n }\n }\n}\n\ninterface ProfileMeta {\n id: string;\n email: string;\n name: string;\n avatar?: string;\n lastLogin: string;\n}\n\n/** Dispatch custom event to notify fe-libs AuthProvider of storage changes (same-tab). */\nfunction notifyProfileStorageChange(): void {\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(new Event(\"bf-profile-storage-change\"));\n }\n}\n\nfunction syncToProfileStorage(data: {\n user?: User | null;\n accessToken?: string | null;\n refreshToken?: string | null;\n workspaceToken?: string | null;\n clear?: boolean;\n}) {\n if (typeof window === \"undefined\") return;\n\n const profileId = getActiveProfileForSync();\n if (!profileId && !data.user && !data.clear) return;\n\n // For login/signup: prefer the user's own ID as the target profile.\n // This prevents overwriting an existing active profile when a NEW user\n // signs up or logs in (multi-account scenario).\n const targetId = data.user?.id ?? profileId;\n if (!targetId && !data.clear) return;\n\n if (data.clear) {\n clearPersistedAuthStorage(targetId);\n if (!targetId) {\n localStorage.removeItem(BF_PROFILES);\n sessionStorage.removeItem(BF_ACTIVE);\n return;\n }\n // Remove profile data keys\n localStorage.removeItem(pfKey(targetId, \"user\"));\n localStorage.removeItem(pfKey(targetId, \"tokens\"));\n localStorage.removeItem(pfKey(targetId, \"workspace-token\"));\n // Remove from profiles list\n try {\n const profilesRaw = localStorage.getItem(BF_PROFILES);\n const profiles: ProfileMeta[] = profilesRaw\n ? JSON.parse(profilesRaw)\n : [];\n const filtered = profiles.filter((p) => p.id !== targetId);\n localStorage.setItem(BF_PROFILES, JSON.stringify(filtered));\n } catch {\n /* ignore */\n }\n sessionStorage.removeItem(BF_ACTIVE);\n return;\n }\n\n if (!targetId) return;\n\n // An account whose email is not verified cannot sign in (the backend rejects\n // it with EMAIL_NOT_VERIFIED), so it must never become a switchable profile.\n // signUp() keeps the pending user in this store for display only (BOFF-7215).\n if (data.user && data.user.emailVerified === false) return;\n\n if (data.user) {\n localStorage.setItem(pfKey(targetId, \"user\"), JSON.stringify(data.user));\n // Update profiles list\n try {\n const profilesRaw = localStorage.getItem(BF_PROFILES);\n const profiles: ProfileMeta[] = profilesRaw\n ? JSON.parse(profilesRaw)\n : [];\n const existing = profiles.filter((p) => p.id !== targetId);\n existing.push({\n id: targetId,\n email: data.user.email ?? \"\",\n name:\n data.user.name ??\n `${data.user.firstName ?? \"\"} ${data.user.lastName ?? \"\"}`.trim(),\n avatar: data.user.avatar,\n lastLogin: new Date().toISOString(),\n });\n localStorage.setItem(BF_PROFILES, JSON.stringify(existing));\n } catch {\n /* ignore */\n }\n // Set as active profile in this tab\n sessionStorage.setItem(BF_ACTIVE, targetId);\n }\n\n if (data.accessToken !== undefined || data.refreshToken !== undefined) {\n const existingRaw = localStorage.getItem(pfKey(targetId, \"tokens\"));\n const existing = existingRaw ? JSON.parse(existingRaw) : {};\n // Compute expiresAt from the actual JWT exp claim if a new access token is provided\n let expiresAt = existing.expiresAt ?? Date.now() + 60 * 60 * 1000;\n const tokenToCheck = data.accessToken ?? existing.accessToken;\n if (tokenToCheck) {\n try {\n const raw = tokenToCheck\n .split(\".\")[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n const padded = raw.padEnd(Math.ceil(raw.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded));\n if (typeof payload.exp === \"number\") {\n expiresAt = payload.exp * 1000; // JWT exp is in seconds → ms\n }\n } catch {\n // Fallback: keep existing or default 1h\n }\n }\n const merged = {\n ...existing,\n ...(data.accessToken !== undefined\n ? { accessToken: data.accessToken }\n : {}),\n ...(data.refreshToken !== undefined\n ? { refreshToken: data.refreshToken }\n : {}),\n expiresAt,\n };\n if (merged.accessToken) {\n localStorage.setItem(pfKey(targetId, \"tokens\"), JSON.stringify(merged));\n } else {\n localStorage.removeItem(pfKey(targetId, \"tokens\"));\n }\n }\n\n if (data.workspaceToken !== undefined) {\n if (data.workspaceToken) {\n localStorage.setItem(\n pfKey(targetId, \"workspace-token\"),\n data.workspaceToken,\n );\n } else {\n localStorage.removeItem(pfKey(targetId, \"workspace-token\"));\n }\n }\n\n // Notify fe-libs AuthProvider of the change (same-tab custom event)\n notifyProfileStorageChange();\n\n // Set SSO cookie for cross-domain session sharing (works in all envs)\n // Stores profile ID + the origin where the user logged in.\n // Other shells use the origin to redirect to /auth/sso-relay for token relay.\n try {\n const hostname = window.location.hostname;\n // For local dev, set cookie on shared .local.burdenoff.com domain\n // For production, set cookie on current domain only (relay handles cross-domain)\n const isSharedDomain = hostname.endsWith(\".local.burdenoff.com\");\n const domain = isSharedDomain ? \".local.burdenoff.com\" : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const cookieOpts = `${domainPart}; path=/; samesite=lax${securePart}`;\n\n if (data.accessToken && targetId) {\n // Store profile ID + login origin for SSO relay\n const ssoValue = JSON.stringify({\n id: targetId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${cookieOpts}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${cookieOpts}; max-age=86400`;\n }\n\n if (data.clear) {\n document.cookie = \"bf-sso=; path=/; max-age=0\";\n document.cookie = \"bf-sso-profile=; path=/; max-age=0\";\n if (domain) {\n document.cookie = `bf-sso=; path=/; domain=${domain}; max-age=0`;\n document.cookie = `bf-sso-profile=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && ![\"localhost\", \"127.0.0.1\"].includes(hostname)) {\n document.cookie = `bf-sso=; path=/; domain=${hostname}; max-age=0`;\n document.cookie = `bf-sso-profile=; path=/; domain=${hostname}; max-age=0`;\n }\n }\n } catch {\n /* ignore */\n }\n}\n\ninterface AuthState {\n // Auth state\n user: User | null;\n accessToken: string | null;\n refreshToken: string | null;\n isAuthenticated: boolean;\n\n // Organization state\n organizations: Organization[];\n currentOrganizationId: string | null;\n isLoadingOrganizations: boolean;\n\n // Workspace state\n workspaces: Workspace[];\n currentWorkspaceId: string | null;\n workspaceToken: string | null;\n isLoadingWorkspaces: boolean;\n\n // Project state\n projects: Project[];\n currentProjectId: string | null;\n isLoadingProjects: boolean;\n\n // MFA state\n mfaRequired: boolean;\n mfaChallengeId: string | null;\n mfaDevices: MfaDevice[];\n\n // Session state\n activeSessions: Session[];\n currentSessionId: string | null;\n\n // OAuth state\n linkedAccounts: OAuthAccount[];\n\n // Security state\n trustedDevices: TrustedDevice[];\n securityEvents: SecurityAudit[];\n\n // API Keys state\n apiKeys: ApiKey[];\n\n // Apps state\n apps: App[];\n currentApp: App | null;\n\n // Delegations state\n delegations: AppUserDelegation[];\n grantedDelegations: AppUserDelegation[];\n\n // UI state\n isLoading: boolean;\n error: string | null;\n errorCode: string | null;\n\n // Actions - Auth\n setUser: (user: User | null) => void;\n setAccessToken: (token: string | null) => void;\n setAuthTokens: (accessToken: string, refreshToken: string) => void;\n setAuthenticated: (isAuthenticated: boolean) => void;\n clearAuth: () => void;\n\n // Actions - Organization\n setOrganizations: (organizations: Organization[]) => void;\n setCurrentOrganizationId: (orgId: string | null) => void;\n setLoadingOrganizations: (loading: boolean) => void;\n\n // Actions - Workspace\n setWorkspaces: (workspaces: Workspace[]) => void;\n setCurrentWorkspaceId: (workspaceId: string | null) => void;\n setWorkspaceToken: (token: string | null) => void;\n setLoadingWorkspaces: (loading: boolean) => void;\n clearWorkspace: () => void;\n\n // Actions - Project\n setProjects: (projects: Project[]) => void;\n setCurrentProjectId: (projectId: string | null) => void;\n setLoadingProjects: (loading: boolean) => void;\n clearProject: () => void;\n\n // Actions - Context (clears org, workspace, project)\n clearContext: () => void;\n\n // Actions - Profile Switch (load from profile storage without syncing back)\n loadFromProfile: (profileId: string) => void;\n\n // Actions - MFA\n setMfaRequired: (required: boolean, challengeId?: string) => void;\n setMfaDevices: (devices: MfaDevice[]) => void;\n removeMfaDevice: (deviceId: string) => void;\n\n // Actions - Sessions\n setActiveSessions: (sessions: Session[]) => void;\n removeSession: (sessionId: string) => void;\n\n // Actions - OAuth\n setLinkedAccounts: (accounts: OAuthAccount[]) => void;\n removeLinkedAccount: (accountId: string) => void;\n\n // Actions - Security\n setTrustedDevices: (devices: TrustedDevice[]) => void;\n removeTrustedDevice: (deviceId: string) => void;\n setSecurityEvents: (events: SecurityAudit[]) => void;\n\n // Actions - API Keys\n setApiKeys: (keys: ApiKey[]) => void;\n removeApiKey: (keyId: string) => void;\n\n // Actions - Apps\n setApps: (apps: App[]) => void;\n setCurrentApp: (app: App | null) => void;\n addApp: (app: App) => void;\n updateApp: (app: App) => void;\n removeApp: (appId: string) => void;\n\n // Actions - Delegations\n setDelegations: (delegations: AppUserDelegation[]) => void;\n setGrantedDelegations: (delegations: AppUserDelegation[]) => void;\n addDelegation: (delegation: AppUserDelegation) => void;\n removeDelegation: (delegationId: string) => void;\n\n // Actions - UI\n setLoading: (loading: boolean) => void;\n setError: (error: string | null, errorCode?: string | null) => void;\n}\n\n/**\n * Auth Zustand store — browser-tab-local state.\n *\n * SAFE for in-memory (no Redis needed):\n * - accessToken / refreshToken / user: per-tab, backed by profileScopedStorage\n * (localStorage key bf-p-{profileId}-*). Tabs sharing the same profile ID\n * stay in sync via the \"bf-profile-storage-change\" custom event + \"storage\"\n * cross-tab event.\n * - workspaceToken: same pattern as above.\n * - mfaRequired / mfaChallengeId: transient login-flow state; not needed\n * cross-tab (each login has its own challenge).\n * - organizations / workspaces / projects lists: cached context, re-fetched\n * after account switch; no cross-tab sharing needed.\n * - UI state (isLoading, error): purely ephemeral.\n *\n * NOT safe for in-memory (needs Redis in future if required server-side):\n * - Nothing currently — all server-side session tracking is done by the\n * backend (global-auth-svc) using its own DB. The frontend only holds\n * short-lived JWT access tokens that expire independently.\n */\nexport const useAuthStore = create<AuthState>()(\n persist(\n (set) => ({\n // Initial state\n user: null,\n accessToken: null,\n refreshToken: null,\n isAuthenticated: false,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n workspaceToken: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n mfaRequired: false,\n mfaChallengeId: null,\n mfaDevices: [],\n activeSessions: [],\n currentSessionId: null,\n linkedAccounts: [],\n trustedDevices: [],\n securityEvents: [],\n apiKeys: [],\n apps: [],\n currentApp: null,\n delegations: [],\n grantedDelegations: [],\n isLoading: false,\n error: null,\n errorCode: null,\n\n // Actions - Auth (with profile storage sync)\n setUser: (user) => {\n set({ user });\n syncToProfileStorage({ user });\n },\n\n setAccessToken: (token) => {\n set({ accessToken: token });\n syncToProfileStorage({ accessToken: token });\n },\n\n setAuthTokens: (accessToken, refreshToken) => {\n // Only set tokens here. Do NOT overwrite user — setUser() handles that\n // with the full user object from the GraphQL response (firstName, lastName, etc.).\n // Fall back to JWT-decoded user ONLY if no user is currently in the store.\n const currentState = useAuthStore.getState();\n if (currentState.user) {\n // User already set (from setUser call) — just update tokens\n set({ accessToken, refreshToken });\n syncToProfileStorage({ accessToken, refreshToken });\n } else {\n // No user yet — decode JWT as fallback\n try {\n const b64 = accessToken\n .split(\".\")[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded));\n const user: User = {\n id: payload.sub,\n email: payload.email,\n username: payload.username,\n role: payload.role,\n status: \"ACTIVE\",\n emailVerified: true,\n mfaEnabled: false,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n set({ accessToken, refreshToken, user });\n syncToProfileStorage({ accessToken, refreshToken, user });\n } catch (error) {\n console.error(\"[AuthStore] Failed to decode JWT:\", error);\n set({ accessToken, refreshToken });\n syncToProfileStorage({ accessToken, refreshToken });\n }\n }\n },\n\n setAuthenticated: (isAuthenticated) => set({ isAuthenticated }),\n\n clearAuth: () => {\n set({\n user: null,\n accessToken: null,\n refreshToken: null,\n isAuthenticated: false,\n mfaRequired: false,\n mfaChallengeId: null,\n activeSessions: [],\n currentSessionId: null,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n workspaceToken: null,\n currentWorkspaceId: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n });\n syncToProfileStorage({ clear: true });\n },\n\n // Actions - Organization\n setOrganizations: (organizations) => set({ organizations }),\n\n setCurrentOrganizationId: (orgId) =>\n set((state) =>\n state.currentOrganizationId === orgId\n ? state\n : { currentOrganizationId: orgId },\n ),\n\n setLoadingOrganizations: (loading) =>\n set({ isLoadingOrganizations: loading }),\n\n // Actions - Workspace\n setWorkspaces: (workspaces) => set({ workspaces }),\n\n setCurrentWorkspaceId: (workspaceId) =>\n set((state) =>\n state.currentWorkspaceId === workspaceId\n ? state\n : { currentWorkspaceId: workspaceId },\n ),\n\n setWorkspaceToken: (token) => {\n set({ workspaceToken: token });\n syncToProfileStorage({ workspaceToken: token });\n },\n\n setLoadingWorkspaces: (loading) => set({ isLoadingWorkspaces: loading }),\n\n clearWorkspace: () =>\n set({ currentWorkspaceId: null, workspaceToken: null, workspaces: [] }),\n\n // Actions - Project\n setProjects: (projects) => set({ projects }),\n\n setCurrentProjectId: (projectId) =>\n set((state) =>\n state.currentProjectId === projectId\n ? state\n : { currentProjectId: projectId },\n ),\n\n setLoadingProjects: (loading) => set({ isLoadingProjects: loading }),\n\n clearProject: () => set({ currentProjectId: null, projects: [] }),\n\n // Actions - Context (clears org, workspace, project)\n clearContext: () =>\n set({\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n workspaceToken: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n }),\n\n // Actions - Profile Switch\n // Loads user/tokens from profile-scoped localStorage WITHOUT syncing back.\n // Used during account switching to align zustand with the new active profile.\n loadFromProfile: (profileId: string) => {\n let user: User | null = null;\n let accessToken: string | null = null;\n let refreshToken: string | null = null;\n let workspaceToken: string | null = null;\n\n try {\n const userRaw = localStorage.getItem(pfKey(profileId, \"user\"));\n if (userRaw) user = JSON.parse(userRaw);\n } catch {\n /* ignore */\n }\n\n try {\n const tokensRaw = localStorage.getItem(pfKey(profileId, \"tokens\"));\n if (tokensRaw) {\n const tokens = JSON.parse(tokensRaw);\n accessToken = tokens.accessToken ?? null;\n refreshToken = tokens.refreshToken ?? null;\n }\n } catch {\n /* ignore */\n }\n\n workspaceToken = localStorage.getItem(\n pfKey(profileId, \"workspace-token\"),\n );\n\n // Set everything in one batch — clears context data so it will be re-fetched\n set({\n user,\n accessToken,\n refreshToken,\n isAuthenticated: !!accessToken && !!user,\n workspaceToken,\n // Clear all context — will be re-fetched for the new user\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n // Clear MFA state\n mfaRequired: false,\n mfaChallengeId: null,\n });\n\n // Notify listeners (e.g., AuthTokenProvider) without writing back to storage\n notifyProfileStorageChange();\n },\n\n // Actions - MFA\n setMfaRequired: (required, challengeId) =>\n set({ mfaRequired: required, mfaChallengeId: challengeId || null }),\n\n setMfaDevices: (devices) => set({ mfaDevices: devices }),\n\n removeMfaDevice: (deviceId) =>\n set((state) => ({\n mfaDevices: state.mfaDevices.filter((d) => d.id !== deviceId),\n })),\n\n // Actions - Sessions\n setActiveSessions: (sessions) => set({ activeSessions: sessions }),\n\n removeSession: (sessionId) =>\n set((state) => ({\n activeSessions: state.activeSessions.filter(\n (s) => s.id !== sessionId,\n ),\n })),\n\n // Actions - OAuth\n setLinkedAccounts: (accounts) => set({ linkedAccounts: accounts }),\n\n removeLinkedAccount: (accountId) =>\n set((state) => ({\n linkedAccounts: state.linkedAccounts.filter(\n (a) => a.id !== accountId,\n ),\n })),\n\n // Actions - Security\n setTrustedDevices: (devices) => set({ trustedDevices: devices }),\n\n removeTrustedDevice: (deviceId) =>\n set((state) => ({\n trustedDevices: state.trustedDevices.filter((d) => d.id !== deviceId),\n })),\n\n setSecurityEvents: (events) => set({ securityEvents: events }),\n\n // Actions - API Keys\n setApiKeys: (keys) => set({ apiKeys: keys }),\n\n removeApiKey: (keyId) =>\n set((state) => ({\n apiKeys: state.apiKeys.filter((k) => k.id !== keyId),\n })),\n\n // Actions - Apps\n setApps: (apps) => set({ apps }),\n\n setCurrentApp: (app) => set({ currentApp: app }),\n\n addApp: (app) =>\n set((state) => ({\n apps: [...state.apps, app],\n })),\n\n updateApp: (app) =>\n set((state) => ({\n apps: state.apps.map((a) => (a.id === app.id ? app : a)),\n currentApp: state.currentApp?.id === app.id ? app : state.currentApp,\n })),\n\n removeApp: (appId) =>\n set((state) => ({\n apps: state.apps.filter((a) => a.id !== appId),\n currentApp: state.currentApp?.id === appId ? null : state.currentApp,\n })),\n\n // Actions - Delegations\n setDelegations: (delegations) => set({ delegations }),\n\n setGrantedDelegations: (delegations) =>\n set({ grantedDelegations: delegations }),\n\n addDelegation: (delegation) =>\n set((state) => ({\n delegations: [...state.delegations, delegation],\n })),\n\n removeDelegation: (delegationId) =>\n set((state) => ({\n delegations: state.delegations.filter((d) => d.id !== delegationId),\n grantedDelegations: state.grantedDelegations.filter(\n (d) => d.id !== delegationId,\n ),\n })),\n\n // Actions - UI\n setLoading: (loading) => set({ isLoading: loading }),\n\n setError: (error, errorCode) =>\n set({ error, errorCode: errorCode ?? null }),\n }),\n {\n name: \"auth-storage\",\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n accessToken: state.accessToken,\n refreshToken: state.refreshToken,\n user: state.user,\n isAuthenticated: state.isAuthenticated,\n // Context state\n currentOrganizationId: state.currentOrganizationId,\n currentWorkspaceId: state.currentWorkspaceId,\n currentProjectId: state.currentProjectId,\n workspaceToken: state.workspaceToken,\n // MFA state must be persisted for login verification flow\n mfaRequired: state.mfaRequired,\n mfaChallengeId: state.mfaChallengeId,\n }),\n },\n ),\n);\n\ntype AuthClearWindow = Window & { __bfAuthClearListenerInstalled?: boolean };\n\nif (typeof window !== \"undefined\") {\n const authWindow = window as AuthClearWindow;\n if (!authWindow.__bfAuthClearListenerInstalled) {\n authWindow.__bfAuthClearListenerInstalled = true;\n window.addEventListener(AUTH_STATE_CLEARED_EVENT, () => {\n const state = useAuthStore.getState();\n if (\n state.isAuthenticated ||\n state.accessToken ||\n state.refreshToken ||\n state.user\n ) {\n state.clearAuth();\n return;\n }\n clearPersistedAuthStorage(sessionStorage.getItem(BF_ACTIVE));\n });\n }\n}\n"],"mappings":"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"}