@burdenoff/microfe-auth 2026.627.1 → 2026.627.2
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.
- package/dist/auth/hooks/useContextSwitcherData.js +215 -161
- package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
- package/dist/auth/pages/SecurityPage.js.map +1 -1
- package/dist/auth/pages/SsoConfigPage.js.map +1 -1
- package/dist/auth/pages/UserDetailsPage.js.map +1 -1
- package/dist/auth/pages/UserProfilePage.js.map +1 -1
- package/dist/auth/pages/UsersPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserDetailsPage.js","names":[],"sources":["../../../src/auth/pages/UserDetailsPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\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 { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\nimport type { User, UserRole, UserStatus } from \"../types\";\n\nconst 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\nconst 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/**\n * User details page (admin only).\n *\n * Route: /auth/users/:id\n *\n * Renders the full profile of a single user, with admin actions\n * (edit, deactivate, hard-delete, export). Reuses the same getUser query\n * that UsersPage relies on.\n */\nexport function UserDetailsPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const {\n canViewUsers,\n canDeleteUsers,\n isLoading: permissionsLoading,\n } = useAuthPermissions();\n const { getUser, deleteUser, hardDeleteUser } = useUserOperations();\n\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingAction, setPendingAction] = useState<\n \"deactivate\" | \"hard-delete\" | null\n >(null);\n\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation();\n const [exportError, setExportError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n if (!id) {\n setError(\"Missing user id\");\n setLoading(false);\n return;\n }\n if (!isAuthenticated) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n getUser(id)\n .then((fetched) => {\n if (cancelled) return;\n if (!fetched) {\n setError(tr(\"auth.userDetails.userNotFound\", \"User not found\"));\n } else {\n setUser(fetched);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setError(\n err instanceof Error ? err.message : \"Failed to fetch user details\",\n );\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n // tr is stable within the render, id/getUser/isAuthenticated drive the effect\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [id, getUser, isAuthenticated]);\n\n const handleDeactivate = async () => {\n if (!user) return;\n try {\n const result = await deleteUser(user.id);\n if (result.success) {\n setSuccessMessage(\n tr(\n \"auth.userDetails.userDeactivated\",\n 'User \"{email}\" has been deactivated.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n // Refresh\n const fresh = await getUser(user.id);\n if (fresh) setUser(fresh);\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to delete user\");\n }\n };\n\n const handleHardDelete = async () => {\n if (!user) return;\n try {\n const result = await hardDeleteUser(user.id);\n if (result.success) {\n setPendingAction(null);\n navigate(\"/auth/users\");\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 handleExport = async () => {\n if (!user) return;\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: `User Data Export - ${user.email}`,\n entityTypes: [\n \"user_profile\",\n \"user_sessions\",\n \"permission_audit_logs\",\n ],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n // Auth + role guards\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.userDetails.authRequired\", \"Authentication Required\")}\n description={tr(\n \"auth.userDetails.signInToView\",\n \"Please sign in to view user details\",\n )}\n />\n </div>\n );\n }\n\n if (permissionsLoading || loading) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16\" aria-live=\"polite\" aria-busy=\"true\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-secondary animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.userDetails.loading\", \"Loading user...\")}\n </p>\n </div>\n </div>\n );\n }\n\n if (!canViewUsers) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.userDetails.adminRequired\", \"Admin Access Required\")}\n description={tr(\n \"auth.userDetails.adminRequiredDesc\",\n \"You need administrator privileges to view user details.\",\n )}\n />\n </div>\n );\n }\n\n if (error || !user) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\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 aria-hidden=\"true\"\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.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n <div role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tr(\"auth.userDetails.userNotFound\", \"User not found\")}\n description={\n error ??\n tr(\n \"auth.userDetails.userNotFoundDesc\",\n \"We couldn't find this user. They may have been removed, or the link is out of date.\",\n )\n }\n />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingAction !== null}\n onOpenChange={(open) => {\n if (!open) setPendingAction(null);\n }}\n title={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteTitle\", \"Permanently delete user?\")\n : tr(\"auth.userDetails.deactivateTitle\", \"Deactivate user?\")\n }\n description={\n pendingAction === \"hard-delete\"\n ? tr(\n \"auth.userDetails.hardDeleteDesc\",\n 'This permanently deletes \"{email}\" and cannot be undone.',\n ).replace(\"{email}\", user.email)\n : tr(\n \"auth.userDetails.deactivateDesc\",\n '\"{email}\" will be prevented from signing in until reactivated.',\n ).replace(\"{email}\", user.email)\n }\n confirmText={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteConfirm\", \"Delete permanently\")\n : tr(\"auth.userDetails.deactivateConfirm\", \"Deactivate user\")\n }\n variant={pendingAction === \"hard-delete\" ? \"danger\" : \"warning\"}\n onConfirm={async () => {\n if (pendingAction === \"hard-delete\") await handleHardDelete();\n else if (pendingAction === \"deactivate\") await handleDeactivate();\n }}\n />\n\n {/* Header row */}\n <div className=\"flex items-center justify-between mb-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"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 aria-hidden=\"true\"\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.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\n \"auth.userDetails.exportAriaLabel\",\n \"Export user data\",\n )}\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 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n {tr(\"auth.userDetails.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.userDetails.exportButton\", \"Export User Data\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.userDetails.purpose\",\n \"The full record for this person — their role, status, verification, and activity. From here you can export their data, deactivate them, or (for super admins) permanently delete a removed account.\",\n )}\n </PagePurpose>\n\n {/* Export error */}\n {exportError && (\n <div\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 <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\">\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.userDetails.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n {/* Identity header — the one emphasis zone on this page */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"flex items-center gap-4\">\n <div\n className=\"auth-brand-badge flex size-20 items-center justify-center rounded-full text-3xl font-semibold\"\n aria-hidden=\"true\"\n >\n {(user.name || user.username || user.email)\n .charAt(0)\n .toUpperCase()}\n </div>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {user.name || user.username}\n </h1>\n <p className=\"text-text-secondary\" data-testid=\"user-email\">\n {user.email}\n </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(user.role)}`}\n >\n {user.role.replace(\"_\", \" \")}\n </span>\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getStatusColor(user.status)}`}\n >\n {user.status}\n </span>\n </div>\n </div>\n </div>\n </EmphasisPanel>\n\n <dl className=\"grid grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.username\", \"Username\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.username}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.email\", \"Email\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.email}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.emailVerified\", \"Email Verified\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.emailVerified ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-status-warning-text\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.mfaEnabled\", \"MFA Enabled\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.mfaEnabled ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n {user.phoneNumber && (\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.phoneNumber\", \"Phone Number\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.phoneNumber}\n </dd>\n </div>\n )}\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastLogin\", \"Last Login\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.lastLoginAt\n ? new Date(user.lastLoginAt).toLocaleString()\n : tr(\"auth.userDetails.never\", \"Never\")}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.created\", \"Created\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.createdAt).toLocaleString()}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastUpdated\", \"Last Updated\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.updatedAt).toLocaleString()}\n </dd>\n </div>\n </dl>\n\n <div className=\"mt-8 pt-6 border-t border-border-subtle flex gap-3\">\n {user.status !== \"DELETED\" && canDeleteUsers && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"deactivate\")}\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.userDetails.deactivateUser\", \"Deactivate User\")}\n </button>\n )}\n {canDeleteUsers &&\n user.status === \"DELETED\" &&\n currentUser?.role === \"SUPER_ADMIN\" && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"hard-delete\")}\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.userDetails.permanentlyDelete\", \"Permanently Delete\")}\n </button>\n )}\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-user-details\"\n steps={[\n {\n id: \"back-to-users\",\n label: tr(\n \"auth.userDetails.nextBack\",\n \"Return to the user list\",\n ),\n description: tr(\n \"auth.userDetails.nextBackDesc\",\n \"Browse or search everyone in your organization.\",\n ),\n onClick: () => navigate(\"/auth/users\"),\n },\n {\n id: \"export-user\",\n label: tr(\n \"auth.userDetails.nextExport\",\n \"Export this user's data\",\n ),\n description: tr(\n \"auth.userDetails.nextExportDesc\",\n \"Download their profile, sessions, and audit history as CSV.\",\n ),\n onClick: () => void handleExport(),\n },\n ]}\n />\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UserDetailsPage.js","names":[],"sources":["../../../src/auth/pages/UserDetailsPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\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 { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\nimport type { User, UserRole, UserStatus } from \"../types\";\n\nconst 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\nconst 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/**\n * User details page (admin only).\n *\n * Route: /auth/users/:id\n *\n * Renders the full profile of a single user, with admin actions\n * (edit, deactivate, hard-delete, export). Reuses the same getUser query\n * that UsersPage relies on.\n */\nexport function UserDetailsPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const {\n canViewUsers,\n canDeleteUsers,\n isLoading: permissionsLoading,\n } = useAuthPermissions();\n const { getUser, deleteUser, hardDeleteUser } = useUserOperations();\n\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingAction, setPendingAction] = useState<\n \"deactivate\" | \"hard-delete\" | null\n >(null);\n\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation();\n const [exportError, setExportError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n if (!id) {\n setError(\"Missing user id\");\n setLoading(false);\n return;\n }\n if (!isAuthenticated) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n getUser(id)\n .then((fetched) => {\n if (cancelled) return;\n if (!fetched) {\n setError(tr(\"auth.userDetails.userNotFound\", \"User not found\"));\n } else {\n setUser(fetched);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setError(\n err instanceof Error ? err.message : \"Failed to fetch user details\",\n );\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n // tr is stable within the render, id/getUser/isAuthenticated drive the effect\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [id, getUser, isAuthenticated]);\n\n const handleDeactivate = async () => {\n if (!user) return;\n try {\n const result = await deleteUser(user.id);\n if (result.success) {\n setSuccessMessage(\n tr(\n \"auth.userDetails.userDeactivated\",\n 'User \"{email}\" has been deactivated.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n // Refresh\n const fresh = await getUser(user.id);\n if (fresh) setUser(fresh);\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to delete user\");\n }\n };\n\n const handleHardDelete = async () => {\n if (!user) return;\n try {\n const result = await hardDeleteUser(user.id);\n if (result.success) {\n setPendingAction(null);\n navigate(\"/auth/users\");\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 handleExport = async () => {\n if (!user) return;\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: `User Data Export - ${user.email}`,\n entityTypes: [\n \"user_profile\",\n \"user_sessions\",\n \"permission_audit_logs\",\n ],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n // Auth + role guards\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.userDetails.authRequired\", \"Authentication Required\")}\n description={tr(\n \"auth.userDetails.signInToView\",\n \"Please sign in to view user details\",\n )}\n />\n </div>\n );\n }\n\n if (permissionsLoading || loading) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16\" aria-live=\"polite\" aria-busy=\"true\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-secondary animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.userDetails.loading\", \"Loading user...\")}\n </p>\n </div>\n </div>\n );\n }\n\n if (!canViewUsers) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.userDetails.adminRequired\", \"Admin Access Required\")}\n description={tr(\n \"auth.userDetails.adminRequiredDesc\",\n \"You need administrator privileges to view user details.\",\n )}\n />\n </div>\n );\n }\n\n if (error || !user) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\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 aria-hidden=\"true\"\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.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n <div role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tr(\"auth.userDetails.userNotFound\", \"User not found\")}\n description={\n error ??\n tr(\n \"auth.userDetails.userNotFoundDesc\",\n \"We couldn't find this user. They may have been removed, or the link is out of date.\",\n )\n }\n />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingAction !== null}\n onOpenChange={(open) => {\n if (!open) setPendingAction(null);\n }}\n title={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteTitle\", \"Permanently delete user?\")\n : tr(\"auth.userDetails.deactivateTitle\", \"Deactivate user?\")\n }\n description={\n pendingAction === \"hard-delete\"\n ? tr(\n \"auth.userDetails.hardDeleteDesc\",\n 'This permanently deletes \"{email}\" and cannot be undone.',\n ).replace(\"{email}\", user.email)\n : tr(\n \"auth.userDetails.deactivateDesc\",\n '\"{email}\" will be prevented from signing in until reactivated.',\n ).replace(\"{email}\", user.email)\n }\n confirmText={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteConfirm\", \"Delete permanently\")\n : tr(\"auth.userDetails.deactivateConfirm\", \"Deactivate user\")\n }\n variant={pendingAction === \"hard-delete\" ? \"danger\" : \"warning\"}\n onConfirm={async () => {\n if (pendingAction === \"hard-delete\") await handleHardDelete();\n else if (pendingAction === \"deactivate\") await handleDeactivate();\n }}\n />\n\n {/* Header row */}\n <div className=\"flex items-center justify-between mb-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"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 aria-hidden=\"true\"\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.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\n \"auth.userDetails.exportAriaLabel\",\n \"Export user data\",\n )}\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 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n {tr(\"auth.userDetails.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.userDetails.exportButton\", \"Export User Data\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.userDetails.purpose\",\n \"The full record for this person — their role, status, verification, and activity. From here you can export their data, deactivate them, or (for super admins) permanently delete a removed account.\",\n )}\n </PagePurpose>\n\n {/* Export error */}\n {exportError && (\n <div\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 <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\">\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.userDetails.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n {/* Identity header — the one emphasis zone on this page */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"flex items-center gap-4\">\n <div\n className=\"auth-brand-badge flex size-20 items-center justify-center rounded-full text-3xl font-semibold\"\n aria-hidden=\"true\"\n >\n {(user.name || user.username || user.email)\n .charAt(0)\n .toUpperCase()}\n </div>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {user.name || user.username}\n </h1>\n <p className=\"text-text-secondary\" data-testid=\"user-email\">\n {user.email}\n </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(user.role)}`}\n >\n {user.role.replace(\"_\", \" \")}\n </span>\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getStatusColor(user.status)}`}\n >\n {user.status}\n </span>\n </div>\n </div>\n </div>\n </EmphasisPanel>\n\n <dl className=\"grid grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.username\", \"Username\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.username}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.email\", \"Email\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.email}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.emailVerified\", \"Email Verified\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.emailVerified ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-status-warning-text\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.mfaEnabled\", \"MFA Enabled\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.mfaEnabled ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n {user.phoneNumber && (\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.phoneNumber\", \"Phone Number\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.phoneNumber}\n </dd>\n </div>\n )}\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastLogin\", \"Last Login\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.lastLoginAt\n ? new Date(user.lastLoginAt).toLocaleString()\n : tr(\"auth.userDetails.never\", \"Never\")}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.created\", \"Created\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.createdAt).toLocaleString()}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastUpdated\", \"Last Updated\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.updatedAt).toLocaleString()}\n </dd>\n </div>\n </dl>\n\n <div className=\"mt-8 pt-6 border-t border-border-subtle flex gap-3\">\n {user.status !== \"DELETED\" && canDeleteUsers && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"deactivate\")}\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.userDetails.deactivateUser\", \"Deactivate User\")}\n </button>\n )}\n {canDeleteUsers &&\n user.status === \"DELETED\" &&\n currentUser?.role === \"SUPER_ADMIN\" && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"hard-delete\")}\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.userDetails.permanentlyDelete\", \"Permanently Delete\")}\n </button>\n )}\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-user-details\"\n steps={[\n {\n id: \"back-to-users\",\n label: tr(\"auth.userDetails.nextBack\", \"Return to the user list\"),\n description: tr(\n \"auth.userDetails.nextBackDesc\",\n \"Browse or search everyone in your organization.\",\n ),\n onClick: () => navigate(\"/auth/users\"),\n },\n {\n id: \"export-user\",\n label: tr(\n \"auth.userDetails.nextExport\",\n \"Export this user's data\",\n ),\n description: tr(\n \"auth.userDetails.nextExportDesc\",\n \"Download their profile, sessions, and audit history as CSV.\",\n ),\n onClick: () => void handleExport(),\n },\n ]}\n />\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserProfilePage.js","names":[],"sources":["../../../src/auth/pages/UserProfilePage.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useId } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useAuth as useShellAuth } from \"@burdenoff/fe-libs/shared/providers/shell/AuthProvider\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport {\n SpinnerIcon,\n AlertIcon,\n UserIcon,\n EmailIcon,\n CheckIcon,\n} from \"../components/AuthIcons\";\nimport {\n useInitiateAvatarUploadMutation,\n useCompleteAvatarUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\n\ninterface ProfileFormData {\n username: string;\n firstName: string;\n lastName: string;\n name: string;\n phoneNumber: string;\n}\n\n/**\n * User Profile page for viewing and editing own profile\n */\nexport function UserProfilePage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigate = useNavigate();\n const shellAuth = useShellAuth();\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const setStoreUser = useAuthStore((state) => state.setUser);\n const { updateCurrentUserProfile, isLoading } = useUserOperations();\n\n const usernameId = useId();\n const displayNameId = useId();\n const firstNameId = useId();\n const lastNameId = useId();\n const phoneNumberId = useId();\n\n const currentWorkspaceId = useAuthStore((state) => state.currentWorkspaceId);\n const wspaceClient = useGatewayClient(\"workspace\");\n const [initiateUpload] = useInitiateAvatarUploadMutation({\n client: wspaceClient,\n });\n const [completeUpload] = useCompleteAvatarUploadMutation({\n client: wspaceClient,\n });\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);\n\n const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n if (!file.type.startsWith(\"image/\")) {\n setError(\n tr(\"auth.profile.invalidImageType\", \"Please select an image file\"),\n );\n return;\n }\n if (!currentWorkspaceId) {\n setError(\n tr(\n \"auth.profile.noWorkspace\",\n \"Please select a workspace before uploading a profile picture\",\n ),\n );\n return;\n }\n setIsUploadingAvatar(true);\n setError(null);\n const wsHeaders = { \"x-workspace-id\": currentWorkspaceId };\n try {\n const extension = file.name.split(\".\").pop()?.toLowerCase() ?? \"\";\n const initiateResult = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type,\n extension,\n },\n },\n context: { headers: wsHeaders },\n });\n const session = initiateResult.data?.initiateUpload.session;\n const uploadUrl = initiateResult.data?.initiateUpload.uploadUrl;\n if (!session || !uploadUrl) throw new Error(\"Failed to initiate upload\");\n\n const uploadResp = await fetch(uploadUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": file.type },\n body: file,\n });\n if (!uploadResp.ok) throw new Error(\"Failed to upload file\");\n\n const completeResult = await completeUpload({\n variables: { input: { sessionId: session.id } },\n context: { headers: wsHeaders },\n });\n const avatarUrl = completeResult.data?.completeUpload.file?.downloadUrl;\n if (!avatarUrl) throw new Error(\"Upload succeeded but no URL returned\");\n\n await updateCurrentUserProfile({ avatar: avatarUrl });\n // Update both the local authStore and the shell AuthProvider so the nav\n // bar avatar reflects the change immediately without a page reload.\n if (currentUser) {\n setStoreUser({ ...currentUser, avatar: avatarUrl });\n }\n shellAuth.updateUser({ avatar: avatarUrl });\n setSuccessMessage(\n tr(\"auth.profile.avatarUpdated\", \"Profile picture updated!\"),\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.avatarFailed\", \"Failed to upload picture\"),\n );\n } finally {\n setIsUploadingAvatar(false);\n if (fileInputRef.current) fileInputRef.current.value = \"\";\n }\n };\n\n const [isEditing, setIsEditing] = useState(false);\n const [formData, setFormData] = useState<ProfileFormData>({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n });\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // Populate form with current user data\n useEffect(() => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n }, [currentUser]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccessMessage(null);\n\n try {\n await updateCurrentUserProfile({\n username: formData.username || undefined,\n firstName: formData.firstName || undefined,\n lastName: formData.lastName || undefined,\n name: formData.name || undefined,\n phoneNumber: formData.phoneNumber || undefined,\n });\n setSuccessMessage(\n tr(\"auth.profile.updateSuccess\", \"Profile updated successfully!\"),\n );\n setIsEditing(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.updateFailed\", \"Failed to update profile\"),\n );\n }\n };\n\n const cancelEdit = () => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n setIsEditing(false);\n setError(null);\n };\n\n // Not authenticated\n if (!isAuthenticated || !currentUser) {\n return (\n <AuthLayout\n title={tr(\"auth.profile.title\", \"Profile\")}\n subtitle={tr(\"auth.profile.subtitle\", \"View and manage your profile\")}\n >\n <div className=\"text-center py-12\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-[var(--color-status-warning-bg-subtle)] flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-[var(--color-status-warning-text)]\" />\n </div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.profile.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.profile.signInToView\",\n \"Please sign in to view your profile\",\n )}\n </p>\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {tr(\"auth.profile.signIn\", \"Sign In\")}\n </Button>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.myProfile\", \"My Profile\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.profile.viewAndManage\",\n \"View and manage your account information\",\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n \"auth.profile.purpose\",\n \"This is how you appear across the platform. Update your name, photo, and contact details here — teammates see this information, and we use your email for sign-in and important notices.\",\n )}\n </PagePurpose>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div className=\"mb-6 flex items-center gap-3 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <CheckIcon className=\"size-5 text-[var(--color-status-success-text)] flex-shrink-0\" />\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"ml-auto text-sm text-[var(--color-status-success-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Profile Card */}\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm overflow-hidden\">\n {/* Profile Header — the one emphasis zone on this page */}\n <div className=\"bg-accent-wash border-b border-border-seam p-6 text-text-primary\">\n <div className=\"flex items-center gap-4\">\n <div className=\"relative group\">\n {currentUser.avatar ? (\n <img\n src={currentUser.avatar}\n alt={tr(\"auth.profile.avatarAlt\", \"Profile picture\")}\n className=\"size-20 rounded-full object-cover shadow-lg\"\n />\n ) : (\n <div className=\"size-20 rounded-full bg-accent-soft backdrop-blur-sm flex items-center justify-center text-[var(--color-action-primary-bg)] text-3xl font-semibold shadow-lg\">\n {(\n currentUser.name ||\n currentUser.username ||\n currentUser.email\n )\n .charAt(0)\n .toUpperCase()}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={isUploadingAvatar}\n aria-label={tr(\n \"auth.profile.changeAvatar\",\n \"Change profile picture\",\n )}\n className=\"absolute inset-0 rounded-full flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-pointer\"\n >\n {isUploadingAvatar ? (\n <SpinnerIcon className=\"size-6 text-white animate-spin\" />\n ) : (\n <svg\n className=\"size-6 text-white\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 13a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n )}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n onChange={(e) => void handleAvatarUpload(e)}\n tabIndex={-1}\n aria-label={tr(\n \"auth.profile.avatarUpload\",\n \"Upload avatar image\",\n )}\n />\n </div>\n <div className=\"text-text-primary\">\n <h2 className=\"text-2xl font-semibold\">\n {currentUser.name ||\n `${currentUser.firstName || \"\"} ${currentUser.lastName || \"\"}`.trim() ||\n currentUser.username}\n </h2>\n <p className=\"text-text-secondary flex items-center gap-2\">\n <EmailIcon className=\"size-4\" />\n {currentUser.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-bg-surface/60 text-text-secondary backdrop-blur-sm\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </span>\n {currentUser.emailVerified && (\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-status-success-bg-subtle text-status-success-text flex items-center gap-1\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* Profile Body */}\n <div className=\"p-6\">\n {isEditing ? (\n /* Edit Form */\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label\n htmlFor={usernameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <UserIcon\n className=\"size-5 text-[var(--color-text-tertiary)]\"\n aria-hidden=\"true\"\n />\n </div>\n <input\n id={usernameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.username\", \"Username\")}\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.usernamePlaceholder\",\n \"Enter username\",\n )}\n />\n </div>\n </div>\n\n <div>\n <label\n htmlFor={displayNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <input\n id={displayNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.displayName\", \"Display Name\")}\n value={formData.name}\n onChange={(e) =>\n setFormData({ ...formData, name: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.displayNamePlaceholder\",\n \"Enter display name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={firstNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <input\n id={firstNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={lastNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <input\n id={lastNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n\n <div className=\"md:col-span-2\">\n <label\n htmlFor={phoneNumberId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <input\n id={phoneNumberId}\n type=\"tel\"\n aria-label={tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n value={formData.phoneNumber}\n onChange={(e) =>\n setFormData({ ...formData, phoneNumber: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.phonePlaceholder\",\n \"Enter phone number\",\n )}\n />\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-4 border-t border-[var(--color-border-default)]\">\n <Button\n type=\"submit\"\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.profile.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.profile.saveChanges\", \"Save Changes\")\n )}\n </Button>\n <button\n type=\"button\"\n onClick={cancelEdit}\n className=\"px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.profile.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n ) : (\n /* View Mode */\n <>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.username || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.name || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.firstName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.email\", \"Email\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium flex items-center gap-2\">\n {currentUser.email}\n {currentUser.emailVerified && (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-success-text)] text-xs\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.phoneNumber || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.role\", \"Role\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.mfaStatus\", \"MFA Status\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.mfaEnabled ? (\n <span className=\"text-[var(--color-status-success-text)] flex items-center gap-1\">\n <CheckIcon className=\"size-4\" />\n {tr(\"auth.profile.enabled\", \"Enabled\")}\n </span>\n ) : (\n <span className=\"text-[var(--color-status-warning-text)]\">\n {tr(\"auth.profile.notEnabled\", \"Not Enabled\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastLogin\", \"Last Login\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastLoginAt\n ? new Date(currentUser.lastLoginAt).toLocaleString()\n : tr(\"auth.profile.never\", \"Never\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.accountCreated\", \"Account Created\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.createdAt\n ? new Date(currentUser.createdAt).toLocaleString()\n : \"-\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-6 mt-6 border-t border-[var(--color-border-default)]\">\n <Button\n type=\"button\"\n onClick={() => setIsEditing(true)}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {tr(\"auth.profile.editProfile\", \"Edit Profile\")}\n </Button>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/security\")}\n className=\"px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.profile.securitySettings\", \"Security Settings\")}\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-profile\"\n steps={[\n {\n id: \"edit-profile\",\n label: tr(\n \"auth.profile.nextEdit\",\n \"Complete your profile\",\n ),\n description: tr(\n \"auth.profile.nextEditDesc\",\n \"Add your display name and photo so teammates recognise you.\",\n ),\n onClick: () => setIsEditing(true),\n done: Boolean(currentUser.name && currentUser.avatar),\n },\n {\n id: \"enable-mfa\",\n label: tr(\n \"auth.profile.nextMfa\",\n \"Secure your account with 2FA\",\n ),\n description: tr(\n \"auth.profile.nextMfaDesc\",\n \"Turn on two-factor authentication for stronger protection.\",\n ),\n onClick: () => navigate(\"/auth/mfa\"),\n done: Boolean(currentUser.mfaEnabled),\n },\n {\n id: \"security-center\",\n label: tr(\n \"auth.profile.nextSecurity\",\n \"Visit the Security Center\",\n ),\n description: tr(\n \"auth.profile.nextSecurityDesc\",\n \"Review sessions, password, and account options.\",\n ),\n onClick: () => navigate(\"/auth/security\"),\n },\n ]}\n />\n\n {/* Quick Links */}\n <div className=\"mt-8 grid grid-cols-1 md:grid-cols-3 gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa\")}\n className=\"p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-primary-border)] transition-colors duration-200 text-left group\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-bg-accent)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-primary-bg-subtle)] transition-colors duration-200\">\n <svg\n className=\"size-5 text-[var(--color-text-link)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.twoFactorAuth\", \"Two-Factor Auth\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {currentUser.mfaEnabled\n ? tr(\"auth.profile.manageMfa\", \"Manage MFA settings\")\n : tr(\"auth.profile.enableMfa\", \"Enable MFA for extra security\")}\n </p>\n </button>\n\n <button\n type=\"button\"\n onClick={() => navigate(\"/devportal/api-keys\")}\n className=\"p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-secondary-border)] transition-colors duration-200 text-left group\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-action-secondary-bg-subtle)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-secondary-bg-subtle)] transition-colors duration-200\">\n <svg\n className=\"size-5 text-[var(--color-action-secondary-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\"\n />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.apiKeys\", \"API Keys\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {tr(\n \"auth.profile.manageApiKeys\",\n \"Manage your API access credentials\",\n )}\n </p>\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UserProfilePage.js","names":[],"sources":["../../../src/auth/pages/UserProfilePage.tsx"],"sourcesContent":["import { useState, useEffect, useRef, useId } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useAuth as useShellAuth } from \"@burdenoff/fe-libs/shared/providers/shell/AuthProvider\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport {\n SpinnerIcon,\n AlertIcon,\n UserIcon,\n EmailIcon,\n CheckIcon,\n} from \"../components/AuthIcons\";\nimport {\n useInitiateAvatarUploadMutation,\n useCompleteAvatarUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\n\ninterface ProfileFormData {\n username: string;\n firstName: string;\n lastName: string;\n name: string;\n phoneNumber: string;\n}\n\n/**\n * User Profile page for viewing and editing own profile\n */\nexport function UserProfilePage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigate = useNavigate();\n const shellAuth = useShellAuth();\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const setStoreUser = useAuthStore((state) => state.setUser);\n const { updateCurrentUserProfile, isLoading } = useUserOperations();\n\n const usernameId = useId();\n const displayNameId = useId();\n const firstNameId = useId();\n const lastNameId = useId();\n const phoneNumberId = useId();\n\n const currentWorkspaceId = useAuthStore((state) => state.currentWorkspaceId);\n const wspaceClient = useGatewayClient(\"workspace\");\n const [initiateUpload] = useInitiateAvatarUploadMutation({\n client: wspaceClient,\n });\n const [completeUpload] = useCompleteAvatarUploadMutation({\n client: wspaceClient,\n });\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);\n\n const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n if (!file.type.startsWith(\"image/\")) {\n setError(\n tr(\"auth.profile.invalidImageType\", \"Please select an image file\"),\n );\n return;\n }\n if (!currentWorkspaceId) {\n setError(\n tr(\n \"auth.profile.noWorkspace\",\n \"Please select a workspace before uploading a profile picture\",\n ),\n );\n return;\n }\n setIsUploadingAvatar(true);\n setError(null);\n const wsHeaders = { \"x-workspace-id\": currentWorkspaceId };\n try {\n const extension = file.name.split(\".\").pop()?.toLowerCase() ?? \"\";\n const initiateResult = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type,\n extension,\n },\n },\n context: { headers: wsHeaders },\n });\n const session = initiateResult.data?.initiateUpload.session;\n const uploadUrl = initiateResult.data?.initiateUpload.uploadUrl;\n if (!session || !uploadUrl) throw new Error(\"Failed to initiate upload\");\n\n const uploadResp = await fetch(uploadUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": file.type },\n body: file,\n });\n if (!uploadResp.ok) throw new Error(\"Failed to upload file\");\n\n const completeResult = await completeUpload({\n variables: { input: { sessionId: session.id } },\n context: { headers: wsHeaders },\n });\n const avatarUrl = completeResult.data?.completeUpload.file?.downloadUrl;\n if (!avatarUrl) throw new Error(\"Upload succeeded but no URL returned\");\n\n await updateCurrentUserProfile({ avatar: avatarUrl });\n // Update both the local authStore and the shell AuthProvider so the nav\n // bar avatar reflects the change immediately without a page reload.\n if (currentUser) {\n setStoreUser({ ...currentUser, avatar: avatarUrl });\n }\n shellAuth.updateUser({ avatar: avatarUrl });\n setSuccessMessage(\n tr(\"auth.profile.avatarUpdated\", \"Profile picture updated!\"),\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.avatarFailed\", \"Failed to upload picture\"),\n );\n } finally {\n setIsUploadingAvatar(false);\n if (fileInputRef.current) fileInputRef.current.value = \"\";\n }\n };\n\n const [isEditing, setIsEditing] = useState(false);\n const [formData, setFormData] = useState<ProfileFormData>({\n username: \"\",\n firstName: \"\",\n lastName: \"\",\n name: \"\",\n phoneNumber: \"\",\n });\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // Populate form with current user data\n useEffect(() => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n }, [currentUser]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccessMessage(null);\n\n try {\n await updateCurrentUserProfile({\n username: formData.username || undefined,\n firstName: formData.firstName || undefined,\n lastName: formData.lastName || undefined,\n name: formData.name || undefined,\n phoneNumber: formData.phoneNumber || undefined,\n });\n setSuccessMessage(\n tr(\"auth.profile.updateSuccess\", \"Profile updated successfully!\"),\n );\n setIsEditing(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.profile.updateFailed\", \"Failed to update profile\"),\n );\n }\n };\n\n const cancelEdit = () => {\n if (currentUser) {\n setFormData({\n username: currentUser.username || \"\",\n firstName: currentUser.firstName || \"\",\n lastName: currentUser.lastName || \"\",\n name: currentUser.name || \"\",\n phoneNumber: currentUser.phoneNumber || \"\",\n });\n }\n setIsEditing(false);\n setError(null);\n };\n\n // Not authenticated\n if (!isAuthenticated || !currentUser) {\n return (\n <AuthLayout\n title={tr(\"auth.profile.title\", \"Profile\")}\n subtitle={tr(\"auth.profile.subtitle\", \"View and manage your profile\")}\n >\n <div className=\"text-center py-12\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-[var(--color-status-warning-bg-subtle)] flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-[var(--color-status-warning-text)]\" />\n </div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.profile.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.profile.signInToView\",\n \"Please sign in to view your profile\",\n )}\n </p>\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {tr(\"auth.profile.signIn\", \"Sign In\")}\n </Button>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.myProfile\", \"My Profile\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.profile.viewAndManage\",\n \"View and manage your account information\",\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n \"auth.profile.purpose\",\n \"This is how you appear across the platform. Update your name, photo, and contact details here — teammates see this information, and we use your email for sign-in and important notices.\",\n )}\n </PagePurpose>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div className=\"mb-6 flex items-center gap-3 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <CheckIcon className=\"size-5 text-[var(--color-status-success-text)] flex-shrink-0\" />\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"ml-auto text-sm text-[var(--color-status-success-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.profile.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {/* Profile Card */}\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm overflow-hidden\">\n {/* Profile Header — the one emphasis zone on this page */}\n <div className=\"bg-accent-wash border-b border-border-seam p-6 text-text-primary\">\n <div className=\"flex items-center gap-4\">\n <div className=\"relative group\">\n {currentUser.avatar ? (\n <img\n src={currentUser.avatar}\n alt={tr(\"auth.profile.avatarAlt\", \"Profile picture\")}\n className=\"size-20 rounded-full object-cover shadow-lg\"\n />\n ) : (\n <div className=\"size-20 rounded-full bg-accent-soft backdrop-blur-sm flex items-center justify-center text-[var(--color-action-primary-bg)] text-3xl font-semibold shadow-lg\">\n {(\n currentUser.name ||\n currentUser.username ||\n currentUser.email\n )\n .charAt(0)\n .toUpperCase()}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={isUploadingAvatar}\n aria-label={tr(\n \"auth.profile.changeAvatar\",\n \"Change profile picture\",\n )}\n className=\"absolute inset-0 rounded-full flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-pointer\"\n >\n {isUploadingAvatar ? (\n <SpinnerIcon className=\"size-6 text-white animate-spin\" />\n ) : (\n <svg\n className=\"size-6 text-white\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 13a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n )}\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n className=\"hidden\"\n onChange={(e) => void handleAvatarUpload(e)}\n tabIndex={-1}\n aria-label={tr(\n \"auth.profile.avatarUpload\",\n \"Upload avatar image\",\n )}\n />\n </div>\n <div className=\"text-text-primary\">\n <h2 className=\"text-2xl font-semibold\">\n {currentUser.name ||\n `${currentUser.firstName || \"\"} ${currentUser.lastName || \"\"}`.trim() ||\n currentUser.username}\n </h2>\n <p className=\"text-text-secondary flex items-center gap-2\">\n <EmailIcon className=\"size-4\" />\n {currentUser.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-bg-surface/60 text-text-secondary backdrop-blur-sm\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </span>\n {currentUser.emailVerified && (\n <span className=\"px-2.5 py-1 rounded-full text-xs font-medium bg-status-success-bg-subtle text-status-success-text flex items-center gap-1\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* Profile Body */}\n <div className=\"p-6\">\n {isEditing ? (\n /* Edit Form */\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label\n htmlFor={usernameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <UserIcon\n className=\"size-5 text-[var(--color-text-tertiary)]\"\n aria-hidden=\"true\"\n />\n </div>\n <input\n id={usernameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.username\", \"Username\")}\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.usernamePlaceholder\",\n \"Enter username\",\n )}\n />\n </div>\n </div>\n\n <div>\n <label\n htmlFor={displayNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <input\n id={displayNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.displayName\", \"Display Name\")}\n value={formData.name}\n onChange={(e) =>\n setFormData({ ...formData, name: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.displayNamePlaceholder\",\n \"Enter display name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={firstNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <input\n id={firstNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor={lastNameId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <input\n id={lastNameId}\n type=\"text\"\n aria-label={tr(\"auth.profile.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n\n <div className=\"md:col-span-2\">\n <label\n htmlFor={phoneNumberId}\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <input\n id={phoneNumberId}\n type=\"tel\"\n aria-label={tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n value={formData.phoneNumber}\n onChange={(e) =>\n setFormData({ ...formData, phoneNumber: e.target.value })\n }\n className=\"block w-full px-4 py-3 border border-[var(--color-border-default)] rounded-xl bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n placeholder={tr(\n \"auth.profile.phonePlaceholder\",\n \"Enter phone number\",\n )}\n />\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-4 border-t border-[var(--color-border-default)]\">\n <Button\n type=\"submit\"\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.profile.saving\", \"Saving...\")}\n </>\n ) : (\n tr(\"auth.profile.saveChanges\", \"Save Changes\")\n )}\n </Button>\n <button\n type=\"button\"\n onClick={cancelEdit}\n className=\"px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.profile.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n ) : (\n /* View Mode */\n <>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.username\", \"Username\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.username || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.displayName\", \"Display Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.name || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.firstName\", \"First Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.firstName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastName\", \"Last Name\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastName || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.email\", \"Email\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium flex items-center gap-2\">\n {currentUser.email}\n {currentUser.emailVerified && (\n <span className=\"inline-flex items-center gap-1 text-[var(--color-status-success-text)] text-xs\">\n <CheckIcon className=\"size-3\" />\n {tr(\"auth.profile.verified\", \"Verified\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.phoneNumber\", \"Phone Number\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.phoneNumber || \"-\"}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.role\", \"Role\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.role?.replace(\"_\", \" \") ||\n tr(\"auth.profile.member\", \"Member\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.mfaStatus\", \"MFA Status\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.mfaEnabled ? (\n <span className=\"text-[var(--color-status-success-text)] flex items-center gap-1\">\n <CheckIcon className=\"size-4\" />\n {tr(\"auth.profile.enabled\", \"Enabled\")}\n </span>\n ) : (\n <span className=\"text-[var(--color-status-warning-text)]\">\n {tr(\"auth.profile.notEnabled\", \"Not Enabled\")}\n </span>\n )}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.lastLogin\", \"Last Login\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.lastLoginAt\n ? new Date(currentUser.lastLoginAt).toLocaleString()\n : tr(\"auth.profile.never\", \"Never\")}\n </p>\n </div>\n\n <div className=\"p-4 rounded-xl bg-[var(--color-bg-muted)]\">\n <label className=\"block text-sm font-medium text-[var(--color-text-tertiary)] mb-1\">\n {tr(\"auth.profile.accountCreated\", \"Account Created\")}\n </label>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {currentUser.createdAt\n ? new Date(currentUser.createdAt).toLocaleString()\n : \"-\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex gap-3 pt-6 mt-6 border-t border-[var(--color-border-default)]\">\n <Button\n type=\"button\"\n onClick={() => setIsEditing(true)}\n variant=\"default\"\n size=\"lg\"\n className=\"font-semibold\"\n >\n {tr(\"auth.profile.editProfile\", \"Edit Profile\")}\n </Button>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/security\")}\n className=\"px-6 py-3 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.profile.securitySettings\", \"Security Settings\")}\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-profile\"\n steps={[\n {\n id: \"edit-profile\",\n label: tr(\"auth.profile.nextEdit\", \"Complete your profile\"),\n description: tr(\n \"auth.profile.nextEditDesc\",\n \"Add your display name and photo so teammates recognise you.\",\n ),\n onClick: () => setIsEditing(true),\n done: Boolean(currentUser.name && currentUser.avatar),\n },\n {\n id: \"enable-mfa\",\n label: tr(\"auth.profile.nextMfa\", \"Secure your account with 2FA\"),\n description: tr(\n \"auth.profile.nextMfaDesc\",\n \"Turn on two-factor authentication for stronger protection.\",\n ),\n onClick: () => navigate(\"/auth/mfa\"),\n done: Boolean(currentUser.mfaEnabled),\n },\n {\n id: \"security-center\",\n label: tr(\"auth.profile.nextSecurity\", \"Visit the Security Center\"),\n description: tr(\n \"auth.profile.nextSecurityDesc\",\n \"Review sessions, password, and account options.\",\n ),\n onClick: () => navigate(\"/auth/security\"),\n },\n ]}\n />\n\n {/* Quick Links */}\n <div className=\"mt-8 grid grid-cols-1 md:grid-cols-3 gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa\")}\n className=\"p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-primary-border)] transition-colors duration-200 text-left group\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-bg-accent)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-primary-bg-subtle)] transition-colors duration-200\">\n <svg\n className=\"size-5 text-[var(--color-text-link)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.twoFactorAuth\", \"Two-Factor Auth\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {currentUser.mfaEnabled\n ? tr(\"auth.profile.manageMfa\", \"Manage MFA settings\")\n : tr(\"auth.profile.enableMfa\", \"Enable MFA for extra security\")}\n </p>\n </button>\n\n <button\n type=\"button\"\n onClick={() => navigate(\"/devportal/api-keys\")}\n className=\"p-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:border-[var(--color-action-secondary-border)] transition-colors duration-200 text-left group\"\n >\n <div className=\"size-10 rounded-lg bg-[var(--color-action-secondary-bg-subtle)] flex items-center justify-center mb-3 group-hover:bg-[var(--color-action-secondary-bg-subtle)] transition-colors duration-200\">\n <svg\n className=\"size-5 text-[var(--color-action-secondary-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\"\n />\n </svg>\n </div>\n <h3 className=\"font-medium text-[var(--color-text-primary)]\">\n {tr(\"auth.profile.apiKeys\", \"API Keys\")}\n </h3>\n <p className=\"text-sm text-[var(--color-text-tertiary)] mt-1\">\n {tr(\n \"auth.profile.manageApiKeys\",\n \"Manage your API access credentials\",\n )}\n </p>\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|