@burdenoff/microfe-auth 2026.527.1 → 2026.527.3

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.
Files changed (67) hide show
  1. package/dist/auth/AuthRoutes.js +1 -1
  2. package/dist/auth/AuthRoutes.js.map +1 -1
  3. package/dist/auth/components/AuthLayout.js +14 -14
  4. package/dist/auth/components/AuthLayout.js.map +1 -1
  5. package/dist/auth/components/CaptchaV2.js +1 -4
  6. package/dist/auth/components/CaptchaV2.js.map +1 -1
  7. package/dist/auth/components/ConfirmActionDialog.js +3 -3
  8. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  9. package/dist/auth/components/profile/UserProfileMenu.js +10 -6
  10. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  11. package/dist/auth/components/workspace/WorkspaceSelector.js +108 -98
  12. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  13. package/dist/auth/hooks/index.js +2 -2
  14. package/dist/auth/hooks/useContextSwitcherData.js +1 -1
  15. package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
  16. package/dist/auth/hooks/useMyWorkspaces.js +1 -1
  17. package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
  18. package/dist/auth/hooks/useWorkspaceToken.js +24 -24
  19. package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
  20. package/dist/auth/index.js +4 -4
  21. package/dist/auth/pages/AuditLogPage.js +10 -12
  22. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  23. package/dist/auth/pages/EmailVerifyPage.js +30 -28
  24. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  25. package/dist/auth/pages/LoginPage.js +23 -19
  26. package/dist/auth/pages/LoginPage.js.map +1 -1
  27. package/dist/auth/pages/MagicLinkPage.js +97 -98
  28. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  29. package/dist/auth/pages/MfaSetupPage.js +26 -21
  30. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  31. package/dist/auth/pages/MfaVerifyPage.js +22 -21
  32. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  33. package/dist/auth/pages/OAuthCallbackPage.js +13 -7
  34. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  35. package/dist/auth/pages/PasswordResetPage.js +28 -22
  36. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  37. package/dist/auth/pages/ReactivatePage.js +19 -15
  38. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  39. package/dist/auth/pages/RegisterPage.js +203 -194
  40. package/dist/auth/pages/RegisterPage.js.map +1 -1
  41. package/dist/auth/pages/SSOClearPage.js +1 -1
  42. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  43. package/dist/auth/pages/SSODepositPage.js +1 -1
  44. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  45. package/dist/auth/pages/SSORelayCallbackPage.js +1 -1
  46. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  47. package/dist/auth/pages/SSORelayPage.js +40 -34
  48. package/dist/auth/pages/SSORelayPage.js.map +1 -1
  49. package/dist/auth/pages/SecurityPage.js +22 -22
  50. package/dist/auth/pages/SecurityPage.js.map +1 -1
  51. package/dist/auth/pages/SessionsPage.js +24 -28
  52. package/dist/auth/pages/SessionsPage.js.map +1 -1
  53. package/dist/auth/pages/SsoCallbackPage.js +27 -20
  54. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  55. package/dist/auth/pages/SsoConfigPage.js +51 -28
  56. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  57. package/dist/auth/pages/SsoLoginPage.js +17 -14
  58. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  59. package/dist/auth/pages/UserDetailsPage.js +16 -17
  60. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  61. package/dist/auth/pages/UserProfilePage.js +38 -26
  62. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  63. package/dist/auth/pages/UsersPage.js +56 -45
  64. package/dist/auth/pages/UsersPage.js.map +1 -1
  65. package/dist/index.js +11 -11
  66. package/dist/microfe-auth.css +1 -1
  67. package/package.json +1 -1
@@ -1,17 +1,17 @@
1
1
  import "./providers/AuthProvider.js";
2
2
  import "./store/authStore.js";
3
3
  import "./hooks/useAuthOperations.js";
4
+ import "./hooks/useMyWorkspaces.js";
5
+ import "./pages/SSORelayPage.js";
6
+ import "./pages/SSORelayCallbackPage.js";
7
+ import "./AuthRoot.js";
4
8
  import "./hooks/useMfaOperations.js";
5
9
  import "./hooks/usePasswordOperations.js";
6
10
  import "./hooks/useSessionOperations.js";
7
11
  import "./hooks/useWorkspaceOperations.js";
8
- import "./hooks/useMyWorkspaces.js";
9
12
  import "./hooks/useContextSwitcherData.js";
10
13
  import "./hooks/useUserOperations.js";
11
14
  import "./hooks/index.js";
12
- import "./pages/SSORelayPage.js";
13
- import "./pages/SSORelayCallbackPage.js";
14
- import "./AuthRoot.js";
15
15
  import "./hooks/useWorkspaceToken.js";
16
16
  import "./hooks/useTokenRefresh.js";
17
17
  import "./components/workspace/WorkspaceSelector.js";
@@ -60,10 +60,10 @@ function p() {
60
60
  "aria-label": m("auth.auditLog.exportAriaLabel", "Export audit log"),
61
61
  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",
62
62
  children: T ? /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c(n, {
63
- className: "w-4 h-4 animate-spin",
63
+ className: "size-4 animate-spin",
64
64
  "aria-hidden": "true"
65
65
  }), m("auth.auditLog.exporting", "Exporting...")] }) : /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("svg", {
66
- className: "w-4 h-4",
66
+ className: "size-4",
67
67
  fill: "none",
68
68
  viewBox: "0 0 24 24",
69
69
  stroke: "currentColor",
@@ -81,7 +81,7 @@ function p() {
81
81
  role: "alert",
82
82
  className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
83
83
  children: [/* @__PURE__ */ c(t, {
84
- className: "w-5 h-5 text-status-error-text flex-shrink-0 mt-0.5",
84
+ className: "size-5 text-status-error-text flex-shrink-0 mt-0.5",
85
85
  "aria-hidden": "true"
86
86
  }), /* @__PURE__ */ c("p", {
87
87
  className: "text-sm text-status-error-text",
@@ -93,7 +93,7 @@ function p() {
93
93
  "aria-busy": "true",
94
94
  className: "text-center py-12",
95
95
  children: [/* @__PURE__ */ c(n, {
96
- className: "w-8 h-8 mx-auto text-text-link animate-spin",
96
+ className: "size-8 mx-auto text-text-link animate-spin",
97
97
  "aria-hidden": "true"
98
98
  }), /* @__PURE__ */ c("p", {
99
99
  className: "mt-3 text-text-secondary",
@@ -106,9 +106,8 @@ function p() {
106
106
  className: "text-status-error-text",
107
107
  children: m("auth.auditLog.error", "Unable to load audit events")
108
108
  })
109
- }) : S.length === 0 ? /* @__PURE__ */ c("div", {
110
- className: "text-center py-12",
111
- role: "status",
109
+ }) : S.length === 0 ? /* @__PURE__ */ c("output", {
110
+ className: "block text-center py-12",
112
111
  children: /* @__PURE__ */ c("p", {
113
112
  className: "text-text-secondary",
114
113
  children: m("auth.auditLog.noEvents", "No security events recorded")
@@ -123,7 +122,7 @@ function p() {
123
122
  className: "flex items-start gap-4",
124
123
  children: [
125
124
  /* @__PURE__ */ c("div", {
126
- className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-bg-muted text-xs font-semibold text-text-secondary",
125
+ className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-muted text-xs font-semibold text-text-secondary",
127
126
  "aria-hidden": "true",
128
127
  children: k(e.action)
129
128
  }),
@@ -147,8 +146,7 @@ function p() {
147
146
  })
148
147
  ]
149
148
  }),
150
- e.action.includes("FAILURE") && /* @__PURE__ */ c("span", {
151
- role: "status",
149
+ e.action.includes("FAILURE") && /* @__PURE__ */ c("output", {
152
150
  className: "px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium",
153
151
  children: m("auth.auditLog.failed", "Failed")
154
152
  })
@@ -191,9 +189,9 @@ function p() {
191
189
  className: "text-center py-16 border-2 border-dashed border-border-subtle rounded-xl",
192
190
  children: [
193
191
  /* @__PURE__ */ c("div", {
194
- className: "w-16 h-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center",
192
+ className: "size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center",
195
193
  children: /* @__PURE__ */ c(t, {
196
- className: "w-8 h-8 text-status-warning-text",
194
+ className: "size-8 text-status-warning-text",
197
195
  "aria-hidden": "true"
198
196
  })
199
197
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"AuditLogPage.js","names":[],"sources":["../../../src/auth/pages/AuditLogPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { useFeatureFlagDetails } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { formatDateTime } from \"../utils\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport {\n useAuthAuditLogsQuery,\n type AuditLogEntry_AuthAuditLogFragment,\n} from \"../../generated/global-operations\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\n\nconst PAGE_SIZE = 20;\n\n/**\n * Security audit log page with pagination and export\n *\n * Fragment: AuditLogEntry_AuthAuditLog (graphql/operations/global/fragments/AuditLogEntry.fragment.graphql)\n * Query: AuthAuditLogs (graphql/operations/global/queries/auditLog.graphql)\n *\n * TODO: When authAuditLogs backend migrates to AuthAuditLogConnection type,\n * update this query to use: authAuditLogs { auditLogs: [...], totalCount, hasMore }\n * and remove the client-side hasMore approximation below.\n */\nexport function AuditLogPage() {\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 [page, setPage] = useState(1);\n const offset = (page - 1) * PAGE_SIZE;\n\n // Default-deny: only show when the flag is explicitly enabled in the system.\n // useFeatureFlag uses default-allow (returns true when not found), so we use\n // useFeatureFlagDetails + explicit value check instead.\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n\n const { data, loading, error } = useAuthAuditLogsQuery({\n skip: !showAuditLog,\n fetchPolicy: \"cache-and-network\",\n variables: { pagination: { limit: PAGE_SIZE, offset } },\n });\n\n const auditLogs = data?.authAuditLogs ?? [];\n // Approximate hasMore: if we received a full page there may be more\n const hasMore = auditLogs.length === PAGE_SIZE;\n\n const wspaceClient = useGatewayClient(\"workspace\");\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation({ client: wspaceClient });\n const [exportError, setExportError] = useState<string | null>(null);\n\n const handleExport = async () => {\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: \"Audit Log Export\",\n // permission_audit_logs covers auth audit logs in the export system\n entityTypes: [\"permission_audit_logs\"],\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 const getEventMarker = (eventType: string) => {\n switch (eventType) {\n case \"LOGIN_SUCCESS\":\n return \"OK\";\n case \"LOGIN_FAILURE\":\n return \"ER\";\n case \"LOGOUT\":\n return \"LO\";\n case \"PASSWORD_CHANGE\":\n return \"PW\";\n case \"MFA_ENABLED\":\n case \"MFA_VERIFIED\":\n return \"MF\";\n case \"ANOMALY_DETECTED\":\n return \"AL\";\n default:\n return \"AU\";\n }\n };\n\n if (!showAuditLog) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"w-16 h-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon\n className=\"w-8 h-8 text-status-warning-text\"\n aria-hidden=\"true\"\n />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.auditLog.notAvailable\", \"Page Not Available\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.auditLog.notEnabled\",\n \"The Security Audit Log is not enabled for your account.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.auditLog.title\", \"Security Audit Log\")}\n </h1>\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\"auth.auditLog.exportAriaLabel\", \"Export audit log\")}\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=\"w-4 h-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"auth.auditLog.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"w-4 h-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.auditLog.export\", \"Export\")}\n </>\n )}\n </button>\n </div>\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=\"w-5 h-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 {loading ? (\n <div aria-live=\"polite\" aria-busy=\"true\" className=\"text-center py-12\">\n <SpinnerIcon\n className=\"w-8 h-8 mx-auto text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-3 text-text-secondary\">\n {tr(\"auth.auditLog.loading\", \"Loading audit events...\")}\n </p>\n </div>\n ) : error ? (\n <div\n role=\"alert\"\n className=\"text-center py-12 border border-status-error-border rounded-xl bg-status-error-bg-subtle\"\n >\n <p className=\"text-status-error-text\">\n {tr(\"auth.auditLog.error\", \"Unable to load audit events\")}\n </p>\n </div>\n ) : auditLogs.length === 0 ? (\n <div className=\"text-center py-12\" role=\"status\">\n <p className=\"text-text-secondary\">\n {tr(\"auth.auditLog.noEvents\", \"No security events recorded\")}\n </p>\n </div>\n ) : (\n <>\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={loading}>\n {auditLogs.map((event: AuditLogEntry_AuthAuditLogFragment) => (\n <div\n key={event.id}\n className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-bg-muted text-xs font-semibold text-text-secondary\"\n aria-hidden=\"true\"\n >\n {getEventMarker(event.action)}\n </div>\n <div className=\"flex-1\">\n <h3 className=\"text-lg font-medium text-text-primary mb-1\">\n {event.action.replace(/_/g, \" \")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-2\">\n <time dateTime={event.createdAt}>\n {formatDateTime(event.createdAt)}\n </time>\n </p>\n <p className=\"text-sm text-text-secondary\">\n {event.description ??\n tr(\n \"auth.auditLog.defaultDescription\",\n \"Authentication event\",\n )}\n {event.ipAddress && ` • ${event.ipAddress}`}\n </p>\n </div>\n {event.action.includes(\"FAILURE\") && (\n <span\n role=\"status\"\n className=\"px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium\"\n >\n {tr(\"auth.auditLog.failed\", \"Failed\")}\n </span>\n )}\n </div>\n </div>\n ))}\n </div>\n\n {/* Pagination */}\n <nav\n aria-label={tr(\n \"auth.auditLog.paginationAriaLabel\",\n \"Audit log pagination\",\n )}\n className=\"mt-6 flex items-center justify-between\"\n >\n <p className=\"text-sm text-text-secondary\">\n {tr(\"auth.auditLog.page\", \"Page\")} {page}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((prev) => Math.max(1, prev - 1))}\n disabled={page === 1}\n aria-label={tr(\n \"auth.auditLog.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.auditLog.previous\", \"Previous\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setPage((prev) => prev + 1)}\n disabled={!hasMore}\n aria-label={tr(\n \"auth.auditLog.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.auditLog.next\", \"Next\")}\n </button>\n </div>\n </nav>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AuditLogPage.js","names":[],"sources":["../../../src/auth/pages/AuditLogPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { useFeatureFlagDetails } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { formatDateTime } from \"../utils/formatters\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport {\n useAuthAuditLogsQuery,\n type AuditLogEntry_AuthAuditLogFragment,\n} from \"../../generated/global-operations\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\n\nconst PAGE_SIZE = 20;\n\n/**\n * Security audit log page with pagination and export\n *\n * Fragment: AuditLogEntry_AuthAuditLog (graphql/operations/global/fragments/AuditLogEntry.fragment.graphql)\n * Query: AuthAuditLogs (graphql/operations/global/queries/auditLog.graphql)\n *\n * TODO: When authAuditLogs backend migrates to AuthAuditLogConnection type,\n * update this query to use: authAuditLogs { auditLogs: [...], totalCount, hasMore }\n * and remove the client-side hasMore approximation below.\n */\nexport function AuditLogPage() {\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 [page, setPage] = useState(1);\n const offset = (page - 1) * PAGE_SIZE;\n\n // Default-deny: only show when the flag is explicitly enabled in the system.\n // useFeatureFlag uses default-allow (returns true when not found), so we use\n // useFeatureFlagDetails + explicit value check instead.\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n\n const { data, loading, error } = useAuthAuditLogsQuery({\n skip: !showAuditLog,\n fetchPolicy: \"cache-and-network\",\n variables: { pagination: { limit: PAGE_SIZE, offset } },\n });\n\n const auditLogs = data?.authAuditLogs ?? [];\n // Approximate hasMore: if we received a full page there may be more\n const hasMore = auditLogs.length === PAGE_SIZE;\n\n const wspaceClient = useGatewayClient(\"workspace\");\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation({ client: wspaceClient });\n const [exportError, setExportError] = useState<string | null>(null);\n\n const handleExport = async () => {\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: \"Audit Log Export\",\n // permission_audit_logs covers auth audit logs in the export system\n entityTypes: [\"permission_audit_logs\"],\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 const getEventMarker = (eventType: string) => {\n switch (eventType) {\n case \"LOGIN_SUCCESS\":\n return \"OK\";\n case \"LOGIN_FAILURE\":\n return \"ER\";\n case \"LOGOUT\":\n return \"LO\";\n case \"PASSWORD_CHANGE\":\n return \"PW\";\n case \"MFA_ENABLED\":\n case \"MFA_VERIFIED\":\n return \"MF\";\n case \"ANOMALY_DETECTED\":\n return \"AL\";\n default:\n return \"AU\";\n }\n };\n\n if (!showAuditLog) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon\n className=\"size-8 text-status-warning-text\"\n aria-hidden=\"true\"\n />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.auditLog.notAvailable\", \"Page Not Available\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.auditLog.notEnabled\",\n \"The Security Audit Log is not enabled for your account.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.auditLog.title\", \"Security Audit Log\")}\n </h1>\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\"auth.auditLog.exportAriaLabel\", \"Export audit log\")}\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.auditLog.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.auditLog.export\", \"Export\")}\n </>\n )}\n </button>\n </div>\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 {loading ? (\n <div aria-live=\"polite\" aria-busy=\"true\" className=\"text-center py-12\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-3 text-text-secondary\">\n {tr(\"auth.auditLog.loading\", \"Loading audit events...\")}\n </p>\n </div>\n ) : error ? (\n <div\n role=\"alert\"\n className=\"text-center py-12 border border-status-error-border rounded-xl bg-status-error-bg-subtle\"\n >\n <p className=\"text-status-error-text\">\n {tr(\"auth.auditLog.error\", \"Unable to load audit events\")}\n </p>\n </div>\n ) : auditLogs.length === 0 ? (\n <output className=\"block text-center py-12\">\n <p className=\"text-text-secondary\">\n {tr(\"auth.auditLog.noEvents\", \"No security events recorded\")}\n </p>\n </output>\n ) : (\n <>\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={loading}>\n {auditLogs.map((event: AuditLogEntry_AuthAuditLogFragment) => (\n <div\n key={event.id}\n className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-muted text-xs font-semibold text-text-secondary\"\n aria-hidden=\"true\"\n >\n {getEventMarker(event.action)}\n </div>\n <div className=\"flex-1\">\n <h3 className=\"text-lg font-medium text-text-primary mb-1\">\n {event.action.replace(/_/g, \" \")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-2\">\n <time dateTime={event.createdAt}>\n {formatDateTime(event.createdAt)}\n </time>\n </p>\n <p className=\"text-sm text-text-secondary\">\n {event.description ??\n tr(\n \"auth.auditLog.defaultDescription\",\n \"Authentication event\",\n )}\n {event.ipAddress && ` • ${event.ipAddress}`}\n </p>\n </div>\n {event.action.includes(\"FAILURE\") && (\n <output\n className=\"px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium\"\n >\n {tr(\"auth.auditLog.failed\", \"Failed\")}\n </output>\n )}\n </div>\n </div>\n ))}\n </div>\n\n {/* Pagination */}\n <nav\n aria-label={tr(\n \"auth.auditLog.paginationAriaLabel\",\n \"Audit log pagination\",\n )}\n className=\"mt-6 flex items-center justify-between\"\n >\n <p className=\"text-sm text-text-secondary\">\n {tr(\"auth.auditLog.page\", \"Page\")} {page}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((prev) => Math.max(1, prev - 1))}\n disabled={page === 1}\n aria-label={tr(\n \"auth.auditLog.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.auditLog.previous\", \"Previous\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setPage((prev) => prev + 1)}\n disabled={!hasMore}\n aria-label={tr(\n \"auth.auditLog.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.auditLog.next\", \"Next\")}\n </button>\n </div>\n </nav>\n </>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAYA,IAAM,IAAY;AAYlB,SAAgB,IAAe;CAC7B,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAGnC,CAAC,GAAM,KAAW,EAAS,EAAE,EAC7B,KAAU,IAAO,KAAK,GAMtB,IADe,EAAsB,iBAAiB,EACzB,UAAU,IAEvC,EAAE,SAAM,YAAS,aAAU,EAAsB;EACrD,MAAM,CAAC;EACP,aAAa;EACb,WAAW,EAAE,YAAY;GAAE,OAAO;GAAW;GAAQ,EAAE;EACxD,CAAC,EAEI,IAAY,GAAM,iBAAiB,EAAE,EAErC,IAAU,EAAU,WAAW,GAG/B,CAAC,GAAiB,EAAE,SAAS,OACjC,EAA+B,EAAE,QAFd,EAAiB,YAAY,EAEO,CAAC,EACpD,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,IAAe,YAAY;AAC/B,IAAe,KAAK;AACpB,MAAI;AACF,SAAM,EAAgB,EACpB,WAAW,EACT,OAAO;IACL,MAAM;IAEN,aAAa,CAAC,wBAAwB;IACtC,QAAQ;IACR,aAAa;IACb,iBAAiB;IAClB,EACF,EACF,CAAC;WACK,GAAK;AACZ,KACE,aAAe,QAAQ,EAAI,UAAU,yBACtC;;IAIC,KAAkB,MAAsB;AAC5C,UAAQ,GAAR;GACE,KAAK,gBACH,QAAO;GACT,KAAK,gBACH,QAAO;GACT,KAAK,SACH,QAAO;GACT,KAAK,kBACH,QAAO;GACT,KAAK;GACL,KAAK,eACH,QAAO;GACT,KAAK,mBACH,QAAO;GACT,QACE,QAAO;;;AA4Bb,QAxBK,IAyBH,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,uBAAuB,qBAAqB;KAC7C,CAAA,EACL,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,KAAK,GAAc;KAClC,UAAU;KACV,cAAY,EAAG,iCAAiC,mBAAmB;KACnE,WAAU;eAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;MACE,WAAU;MACV,eAAY;MACZ,CAAA,EACD,EAAG,2BAA2B,eAAe,CAC7C,EAAA,CAAA,GAEH,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;OACF,CAAA;MACE,CAAA,EACL,EAAG,wBAAwB,SAAS,CACpC,EAAA,CAAA;KAEE,CAAA,CACL;;GAGL,KACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAFZ,CAIE,kBAAC,GAAD;KACE,WAAU;KACV,eAAY;KACZ,CAAA,EACF,kBAAC,KAAD;KAAG,WAAU;eAAkC;KAAgB,CAAA,CAC3D;;GAGP,IACC,kBAAC,OAAD;IAAK,aAAU;IAAS,aAAU;IAAO,WAAU;cAAnD,CACE,kBAAC,GAAD;KACE,WAAU;KACV,eAAY;KACZ,CAAA,EACF,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,yBAAyB,0BAA0B;KACrD,CAAA,CACA;QACJ,IACF,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAEV,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,uBAAuB,8BAA8B;KACvD,CAAA;IACA,CAAA,GACJ,EAAU,WAAW,IACvB,kBAAC,UAAD;IAAQ,WAAU;cAChB,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,0BAA0B,8BAA8B;KAC1D,CAAA;IACG,CAAA,GAET,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;IAAY,aAAU;IAAS,aAAW;cACtD,EAAU,KAAK,MACd,kBAAC,OAAD;KAEE,WAAU;eAEV,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QACE,WAAU;QACV,eAAY;kBAEX,EAAe,EAAM,OAAO;QACzB,CAAA;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAM,OAAO,QAAQ,MAAM,IAAI;UAC7B,CAAA;SACL,kBAAC,KAAD;UAAG,WAAU;oBACX,kBAAC,QAAD;WAAM,UAAU,EAAM;qBACnB,EAAe,EAAM,UAAU;WAC3B,CAAA;UACL,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAb,CACG,EAAM,eACL,EACE,oCACA,uBACD,EACF,EAAM,aAAa,MAAM,EAAM,YAC9B;;SACA;;OACL,EAAM,OAAO,SAAS,UAAU,IAC/B,kBAAC,UAAD;QACE,WAAU;kBAET,EAAG,wBAAwB,SAAS;QAC9B,CAAA;OAEP;;KACF,EApCC,EAAM,GAoCP,CACN;IACE,CAAA,EAGN,kBAAC,OAAD;IACE,cAAY,EACV,qCACA,uBACD;IACD,WAAU;cALZ,CAOE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAG,sBAAsB,OAAO;MAAC;MAAE;MAClC;QACJ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,GAAS,MAAS,KAAK,IAAI,GAAG,IAAO,EAAE,CAAC;MACvD,UAAU,MAAS;MACnB,cAAY,EACV,uCACA,sBACD;MACD,WAAU;gBAET,EAAG,0BAA0B,WAAW;MAClC,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,GAAS,MAAS,IAAO,EAAE;MAC1C,UAAU,CAAC;MACX,cAAY,EACV,mCACA,kBACD;MACD,WAAU;gBAET,EAAG,sBAAsB,OAAO;MAC1B,CAAA,CACL;OACF;MACL,EAAA,CAAA;GAED;MA9LJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,WAAU;MACV,eAAY;MACZ,CAAA;KACE,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,qBAAqB;KACpD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,4BACA,0DACD;KACC,CAAA;IACA;;EACF,CAAA"}
@@ -1,6 +1,5 @@
1
1
  import { useAuthStore as e } from "../store/authStore.js";
2
2
  import { useAuthOperations as t } from "../hooks/useAuthOperations.js";
3
- import "../hooks/index.js";
4
3
  import { AuthLayout as n } from "../components/AuthLayout.js";
5
4
  import { SpinnerIcon as r } from "../components/AuthIcons.js";
6
5
  import { useCallback as i, useEffect as a, useRef as o, useState as s } from "react";
@@ -17,43 +16,43 @@ function h() {
17
16
  let { t: h } = l(), g = (e, t) => {
18
17
  let n = h(e);
19
18
  return n === e ? t : n;
20
- }, [_] = c(), v = _.get("token"), y = _.get("email"), { verifyEmail: b, resendVerification: x } = t(), S = e((e) => e.user), [C, w] = s("verifying"), [T, E] = s(y || void 0), [D, O] = s(!1), [k, A] = s(!1), [j, M] = s(p), N = o(!1);
19
+ }, [_] = c(), v = _.get("token"), y = _.get("email"), { verifyEmail: b, resendVerification: x } = t(), S = e((e) => e.user), [C, w] = s("verifying"), T = o(y || void 0), [E, D] = s(!1), [O, k] = s(!1), [A, j] = s(p), M = o(!1);
21
20
  a(() => {
22
- if (!v || N.current) return;
21
+ if (!v || M.current) return;
23
22
  let e = `auth:verifyEmail:inflight:${v}`;
24
23
  if (!(typeof window < "u" && window.sessionStorage.getItem(e))) {
25
- N.current = !0;
24
+ M.current = !0;
26
25
  try {
27
26
  window.sessionStorage.setItem(e, "1");
28
27
  } catch {}
29
28
  b(v).then((e) => {
30
- e.success ? (w("success"), e.email && E(e.email)) : w("error");
29
+ e.success ? (w("success"), e.email && (T.current = e.email)) : w("error");
31
30
  }).catch(() => w("error"));
32
31
  }
33
- }, [v]), a(() => {
32
+ }, [v, b]), a(() => {
34
33
  if (C !== "success") return;
35
34
  let e = setInterval(() => {
36
- M((t) => t <= 1 ? (clearInterval(e), m(T), 0) : t - 1);
35
+ j((t) => t <= 1 ? (clearInterval(e), m(T.current), 0) : t - 1);
37
36
  }, 1e3);
38
37
  return () => clearInterval(e);
39
- }, [C, T]);
40
- let P = async () => {
38
+ }, [C]);
39
+ let N = async () => {
41
40
  let e = y || S?.email;
42
41
  if (!e) {
43
42
  console.error("No email available for resend");
44
43
  return;
45
44
  }
46
- O(!0);
45
+ D(!0);
47
46
  try {
48
- await x(e), A(!0);
47
+ await x(e), k(!0);
49
48
  } catch (e) {
50
49
  console.error("Failed to resend verification:", e);
51
50
  } finally {
52
- O(!1);
51
+ D(!1);
53
52
  }
54
- }, F = i(() => {
55
- m(T);
56
- }, [T]), I = i(() => {
53
+ }, P = i(() => {
54
+ m(T.current);
55
+ }, []), F = i(() => {
57
56
  m();
58
57
  }, []);
59
58
  return C === "verifying" ? /* @__PURE__ */ d(n, {
@@ -62,8 +61,8 @@ function h() {
62
61
  children: /* @__PURE__ */ f("div", {
63
62
  className: "flex flex-col items-center justify-center py-12",
64
63
  children: [/* @__PURE__ */ d("div", {
65
- className: "w-16 h-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center mb-6",
66
- children: /* @__PURE__ */ d(r, { className: "w-8 h-8 text-[var(--color-text-link)] animate-spin" })
64
+ className: "size-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center mb-6",
65
+ children: /* @__PURE__ */ d(r, { className: "size-8 text-[var(--color-text-link)] animate-spin" })
67
66
  }), /* @__PURE__ */ d("p", {
68
67
  className: "text-[var(--color-text-secondary)]",
69
68
  children: g("auth.emailVerify.moment", "This will only take a moment...")
@@ -76,9 +75,9 @@ function h() {
76
75
  className: "flex flex-col items-center justify-center py-6",
77
76
  children: [
78
77
  /* @__PURE__ */ d("div", {
79
- className: "w-20 h-20 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center mb-6",
78
+ className: "size-20 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center mb-6",
80
79
  children: /* @__PURE__ */ d("svg", {
81
- className: "w-10 h-10 text-[var(--color-status-success-text)]",
80
+ className: "size-10 text-[var(--color-status-success-text)]",
82
81
  fill: "none",
83
82
  viewBox: "0 0 24 24",
84
83
  stroke: "currentColor",
@@ -101,14 +100,15 @@ function h() {
101
100
  " ",
102
101
  /* @__PURE__ */ d("span", {
103
102
  className: "font-semibold text-[var(--color-text-primary)]",
104
- children: j
103
+ children: A
105
104
  }),
106
105
  " ",
107
106
  g("auth.emailVerify.seconds", "seconds...")
108
107
  ]
109
108
  }),
110
109
  /* @__PURE__ */ d("button", {
111
- onClick: F,
110
+ type: "button",
111
+ onClick: P,
112
112
  className: "auth-primary-button w-full px-4 py-3 font-semibold",
113
113
  children: g("auth.emailVerify.continueSignIn", "Continue to sign in now")
114
114
  })
@@ -121,9 +121,9 @@ function h() {
121
121
  className: "flex flex-col items-center justify-center py-6",
122
122
  children: [
123
123
  /* @__PURE__ */ d("div", {
124
- className: "w-20 h-20 rounded-full bg-[var(--color-status-error-bg-subtle)] flex items-center justify-center mb-6",
124
+ className: "size-20 rounded-full bg-[var(--color-status-error-bg-subtle)] flex items-center justify-center mb-6",
125
125
  children: /* @__PURE__ */ d("svg", {
126
- className: "w-10 h-10 text-[var(--color-status-error-text)]",
126
+ className: "size-10 text-[var(--color-status-error-text)]",
127
127
  fill: "none",
128
128
  viewBox: "0 0 24 24",
129
129
  stroke: "currentColor",
@@ -139,20 +139,22 @@ function h() {
139
139
  className: "text-center text-[var(--color-text-secondary)] mb-8",
140
140
  children: g("auth.emailVerify.invalidLink", "The verification link is invalid or has expired. Please request a new verification email.")
141
141
  }),
142
- k ? /* @__PURE__ */ d("div", {
142
+ O ? /* @__PURE__ */ d("div", {
143
143
  className: "w-full p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)] text-center mb-4",
144
144
  children: /* @__PURE__ */ d("p", {
145
145
  className: "text-sm text-[var(--color-status-success-text)]",
146
146
  children: g("auth.emailVerify.emailSent", "Verification email sent! Please check your inbox.")
147
147
  })
148
148
  }) : /* @__PURE__ */ d("button", {
149
- onClick: P,
150
- disabled: D,
149
+ type: "button",
150
+ onClick: N,
151
+ disabled: E,
151
152
  className: "auth-primary-button mb-4 flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold",
152
- children: D ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(r, { className: "w-5 h-5 animate-spin" }), /* @__PURE__ */ d("span", { children: "Sending..." })] }) : g("auth.emailVerify.resend", "Resend verification email")
153
+ children: E ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(r, { className: "size-5 animate-spin" }), /* @__PURE__ */ d("span", { children: "Sending…" })] }) : g("auth.emailVerify.resend", "Resend verification email")
153
154
  }),
154
155
  /* @__PURE__ */ d("button", {
155
- onClick: I,
156
+ type: "button",
157
+ onClick: F,
156
158
  className: "w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all",
157
159
  children: g("auth.emailVerify.backToSignIn", "Back to sign in")
158
160
  })
@@ -1 +1 @@
1
- {"version":3,"file":"EmailVerifyPage.js","names":[],"sources":["../../../src/auth/pages/EmailVerifyPage.tsx"],"sourcesContent":["import { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { useAuthOperations } from \"../hooks\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { SpinnerIcon } from \"../components/AuthIcons\";\n\nconst REDIRECT_DELAY = 5; // seconds\n\n/**\n * Navigate to login page with optional email pre-fill\n * Uses window.location.href for reliable navigation (works in new tabs)\n */\nfunction navigateToLogin(email?: string) {\n const baseUrl = \"/auth/login\";\n const url = email ? `${baseUrl}?email=${encodeURIComponent(email)}` : baseUrl;\n window.location.href = url;\n}\n\n/**\n * Email verification page with professional design\n */\nexport function EmailVerifyPage() {\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 [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n const urlEmail = searchParams.get(\"email\");\n const { verifyEmail, resendVerification } = useAuthOperations();\n const user = useAuthStore((state) => state.user);\n const [status, setStatus] = useState<\"verifying\" | \"success\" | \"error\">(\n \"verifying\",\n );\n const [verifiedEmail, setVerifiedEmail] = useState<string | undefined>(\n urlEmail || undefined,\n );\n const [isResending, setIsResending] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const [countdown, setCountdown] = useState(REDIRECT_DELAY);\n\n // Guard against duplicate calls. useRef alone resets across remounts\n // (StrictMode, Suspense resolution of the lazy Auth root, HMR), so we\n // additionally key on the token via sessionStorage — any remount for the\n // same token short-circuits instead of firing a second mutation.\n const verificationInitiatedRef = useRef(false);\n\n useEffect(() => {\n if (!token || verificationInitiatedRef.current) return;\n\n const storageKey = `auth:verifyEmail:inflight:${token}`;\n if (\n typeof window !== \"undefined\" &&\n window.sessionStorage.getItem(storageKey)\n ) {\n return;\n }\n verificationInitiatedRef.current = true;\n try {\n window.sessionStorage.setItem(storageKey, \"1\");\n } catch {\n // sessionStorage unavailable (private mode, SSR); ref guard still applies.\n }\n\n verifyEmail(token)\n .then((result) => {\n if (result.success) {\n setStatus(\"success\");\n if (result.email) setVerifiedEmail(result.email);\n } else {\n setStatus(\"error\");\n }\n })\n .catch(() => setStatus(\"error\"));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [token]);\n\n // Auto-redirect countdown after successful verification\n useEffect(() => {\n if (status !== \"success\") return;\n\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n navigateToLogin(verifiedEmail);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [status, verifiedEmail]);\n\n const handleResend = async () => {\n const resendEmail = urlEmail || user?.email;\n if (!resendEmail) {\n console.error(\"No email available for resend\");\n return;\n }\n setIsResending(true);\n try {\n await resendVerification(resendEmail);\n setResendSuccess(true);\n } catch (err) {\n console.error(\"Failed to resend verification:\", err);\n } finally {\n setIsResending(false);\n }\n };\n\n const handleContinueToLogin = useCallback(() => {\n navigateToLogin(verifiedEmail);\n }, [verifiedEmail]);\n\n const handleBackToLogin = useCallback(() => {\n navigateToLogin();\n }, []);\n\n if (status === \"verifying\") {\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.verifyingTitle\", \"Verifying your email\")}\n subtitle={tr(\n \"auth.emailVerify.verifyingSubtitle\",\n \"Please wait while we confirm your email address\",\n )}\n >\n {/* Loading Spinner */}\n <div className=\"flex flex-col items-center justify-center py-12\">\n <div className=\"w-16 h-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center mb-6\">\n <SpinnerIcon className=\"w-8 h-8 text-[var(--color-text-link)] animate-spin\" />\n </div>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\"auth.emailVerify.moment\", \"This will only take a moment...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n if (status === \"success\") {\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.verifiedTitle\", \"Email verified!\")}\n subtitle={tr(\n \"auth.emailVerify.verifiedSubtitle\",\n \"Your email address has been confirmed\",\n )}\n >\n {/* Success Icon */}\n <div className=\"flex flex-col items-center justify-center py-6\">\n <div className=\"w-20 h-20 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center mb-6\">\n <svg\n className=\"w-10 h-10 text-[var(--color-status-success-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=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.emailVerify.successMessage\",\n \"Your email has been successfully verified. You can now sign in to access all features.\",\n )}\n </p>\n\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)] mb-6\">\n {tr(\"auth.emailVerify.redirecting\", \"Redirecting to sign in in\")}{\" \"}\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {countdown}\n </span>{\" \"}\n {tr(\"auth.emailVerify.seconds\", \"seconds...\")}\n </p>\n\n <button\n onClick={handleContinueToLogin}\n className=\"auth-primary-button w-full px-4 py-3 font-semibold\"\n >\n {tr(\"auth.emailVerify.continueSignIn\", \"Continue to sign in now\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.failedTitle\", \"Verification failed\")}\n subtitle={tr(\n \"auth.emailVerify.failedSubtitle\",\n \"We couldn't verify your email address\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center py-6\">\n {/* Error Icon */}\n <div className=\"w-20 h-20 rounded-full bg-[var(--color-status-error-bg-subtle)] flex items-center justify-center mb-6\">\n <svg\n className=\"w-10 h-10 text-[var(--color-status-error-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=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8\">\n {tr(\n \"auth.emailVerify.invalidLink\",\n \"The verification link is invalid or has expired. Please request a new verification email.\",\n )}\n </p>\n\n {resendSuccess ? (\n <div className=\"w-full p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)] text-center mb-4\">\n <p className=\"text-sm text-[var(--color-status-success-text)]\">\n {tr(\n \"auth.emailVerify.emailSent\",\n \"Verification email sent! Please check your inbox.\",\n )}\n </p>\n </div>\n ) : (\n <button\n onClick={handleResend}\n disabled={isResending}\n className=\"auth-primary-button mb-4 flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isResending ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>Sending...</span>\n </>\n ) : (\n tr(\"auth.emailVerify.resend\", \"Resend verification email\")\n )}\n </button>\n )}\n\n <button\n onClick={handleBackToLogin}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all\"\n >\n {tr(\"auth.emailVerify.backToSignIn\", \"Back to sign in\")}\n </button>\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EmailVerifyPage.js","names":[],"sources":["../../../src/auth/pages/EmailVerifyPage.tsx"],"sourcesContent":["import { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { SpinnerIcon } from \"../components/AuthIcons\";\n\nconst REDIRECT_DELAY = 5; // seconds\n\n/**\n * Navigate to login page with optional email pre-fill\n * Uses window.location.href for reliable navigation (works in new tabs)\n */\nfunction navigateToLogin(email?: string) {\n const baseUrl = \"/auth/login\";\n const url = email ? `${baseUrl}?email=${encodeURIComponent(email)}` : baseUrl;\n window.location.href = url;\n}\n\n/**\n * Email verification page with professional design\n */\nexport function EmailVerifyPage() {\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 [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n const urlEmail = searchParams.get(\"email\");\n const { verifyEmail, resendVerification } = useAuthOperations();\n const user = useAuthStore((state) => state.user);\n const [status, setStatus] = useState<\"verifying\" | \"success\" | \"error\">(\n \"verifying\",\n );\n const verifiedEmailRef = useRef<string | undefined>(urlEmail || undefined);\n const [isResending, setIsResending] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const [countdown, setCountdown] = useState(REDIRECT_DELAY);\n\n // Guard against duplicate calls. useRef alone resets across remounts\n // (StrictMode, Suspense resolution of the lazy Auth root, HMR), so we\n // additionally key on the token via sessionStorage — any remount for the\n // same token short-circuits instead of firing a second mutation.\n const verificationInitiatedRef = useRef(false);\n\n useEffect(() => {\n if (!token || verificationInitiatedRef.current) return;\n\n const storageKey = `auth:verifyEmail:inflight:${token}`;\n if (\n typeof window !== \"undefined\" &&\n window.sessionStorage.getItem(storageKey)\n ) {\n return;\n }\n verificationInitiatedRef.current = true;\n try {\n window.sessionStorage.setItem(storageKey, \"1\");\n } catch {\n // sessionStorage unavailable (private mode, SSR); ref guard still applies.\n }\n\n verifyEmail(token)\n .then((result) => {\n if (result.success) {\n setStatus(\"success\");\n if (result.email) verifiedEmailRef.current = result.email;\n } else {\n setStatus(\"error\");\n }\n })\n .catch(() => setStatus(\"error\"));\n }, [token, verifyEmail]);\n\n // Auto-redirect countdown after successful verification\n useEffect(() => {\n if (status !== \"success\") return;\n\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n navigateToLogin(verifiedEmailRef.current);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [status]);\n\n const handleResend = async () => {\n const resendEmail = urlEmail || user?.email;\n if (!resendEmail) {\n console.error(\"No email available for resend\");\n return;\n }\n setIsResending(true);\n try {\n await resendVerification(resendEmail);\n setResendSuccess(true);\n } catch (err) {\n console.error(\"Failed to resend verification:\", err);\n } finally {\n setIsResending(false);\n }\n };\n\n const handleContinueToLogin = useCallback(() => {\n navigateToLogin(verifiedEmailRef.current);\n }, []);\n\n const handleBackToLogin = useCallback(() => {\n navigateToLogin();\n }, []);\n\n if (status === \"verifying\") {\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.verifyingTitle\", \"Verifying your email\")}\n subtitle={tr(\n \"auth.emailVerify.verifyingSubtitle\",\n \"Please wait while we confirm your email address\",\n )}\n >\n {/* Loading Spinner */}\n <div className=\"flex flex-col items-center justify-center py-12\">\n <div className=\"size-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center mb-6\">\n <SpinnerIcon className=\"size-8 text-[var(--color-text-link)] animate-spin\" />\n </div>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\"auth.emailVerify.moment\", \"This will only take a moment...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n if (status === \"success\") {\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.verifiedTitle\", \"Email verified!\")}\n subtitle={tr(\n \"auth.emailVerify.verifiedSubtitle\",\n \"Your email address has been confirmed\",\n )}\n >\n {/* Success Icon */}\n <div className=\"flex flex-col items-center justify-center py-6\">\n <div className=\"size-20 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center mb-6\">\n <svg\n className=\"size-10 text-[var(--color-status-success-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=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.emailVerify.successMessage\",\n \"Your email has been successfully verified. You can now sign in to access all features.\",\n )}\n </p>\n\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)] mb-6\">\n {tr(\"auth.emailVerify.redirecting\", \"Redirecting to sign in in\")}{\" \"}\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {countdown}\n </span>{\" \"}\n {tr(\"auth.emailVerify.seconds\", \"seconds...\")}\n </p>\n\n <button type=\"button\"\n onClick={handleContinueToLogin}\n className=\"auth-primary-button w-full px-4 py-3 font-semibold\"\n >\n {tr(\"auth.emailVerify.continueSignIn\", \"Continue to sign in now\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.failedTitle\", \"Verification failed\")}\n subtitle={tr(\n \"auth.emailVerify.failedSubtitle\",\n \"We couldn't verify your email address\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center py-6\">\n {/* Error Icon */}\n <div className=\"size-20 rounded-full bg-[var(--color-status-error-bg-subtle)] flex items-center justify-center mb-6\">\n <svg\n className=\"size-10 text-[var(--color-status-error-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=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8\">\n {tr(\n \"auth.emailVerify.invalidLink\",\n \"The verification link is invalid or has expired. Please request a new verification email.\",\n )}\n </p>\n\n {resendSuccess ? (\n <div className=\"w-full p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)] text-center mb-4\">\n <p className=\"text-sm text-[var(--color-status-success-text)]\">\n {tr(\n \"auth.emailVerify.emailSent\",\n \"Verification email sent! Please check your inbox.\",\n )}\n </p>\n </div>\n ) : (\n <button type=\"button\"\n onClick={handleResend}\n disabled={isResending}\n className=\"auth-primary-button mb-4 flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isResending ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>Sending…</span>\n </>\n ) : (\n tr(\"auth.emailVerify.resend\", \"Resend verification email\")\n )}\n </button>\n )}\n\n <button type=\"button\"\n onClick={handleBackToLogin}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all\"\n >\n {tr(\"auth.emailVerify.backToSignIn\", \"Back to sign in\")}\n </button>\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -2,7 +2,6 @@ import { useAuthStore as e } from "../store/authStore.js";
2
2
  import { useAuthOperations as t } from "../hooks/useAuthOperations.js";
3
3
  import { useMyWorkspaces as n } from "../hooks/useMyWorkspaces.js";
4
4
  import { useOAuthOperations as r } from "../hooks/useOAuthOperations.js";
5
- import "../hooks/index.js";
6
5
  import { AuthLayout as i } from "../components/AuthLayout.js";
7
6
  import { AlertIcon as a, EmailIcon as o, EyeIcon as s, EyeOffIcon as c, GoogleIcon as l, LockIcon as ee, MicrosoftIcon as u, SpinnerIcon as te } from "../components/AuthIcons.js";
8
7
  import { useCaptchaV3 as ne } from "../hooks/useCaptchaV3.js";
@@ -296,7 +295,7 @@ function x() {
296
295
  Fe && /* @__PURE__ */ g("div", {
297
296
  className: "flex items-start gap-3 p-4 rounded-xl border bg-status-info-bg-subtle border-status-info-border",
298
297
  children: [/* @__PURE__ */ h("svg", {
299
- className: "w-5 h-5 flex-shrink-0 mt-0.5 text-text-link",
298
+ className: "size-5 flex-shrink-0 mt-0.5 text-text-link",
300
299
  fill: "none",
301
300
  viewBox: "0 0 24 24",
302
301
  stroke: "currentColor",
@@ -313,14 +312,14 @@ function x() {
313
312
  }),
314
313
  J && /* @__PURE__ */ g("div", {
315
314
  className: "flex items-start gap-3 p-4 rounded-xl border bg-status-warning-bg-subtle border-status-warning-border",
316
- children: [/* @__PURE__ */ h(a, { className: "w-5 h-5 flex-shrink-0 mt-0.5 text-status-warning-text" }), /* @__PURE__ */ h("p", {
315
+ children: [/* @__PURE__ */ h(a, { className: "size-5 flex-shrink-0 mt-0.5 text-status-warning-text" }), /* @__PURE__ */ h("p", {
317
316
  className: "text-sm text-status-warning-text",
318
317
  children: J
319
318
  })]
320
319
  }),
321
320
  Le && /* @__PURE__ */ g("div", {
322
321
  className: "flex items-start gap-3 p-4 rounded-xl border bg-status-warning-bg-subtle border-status-warning-border",
323
- children: [/* @__PURE__ */ h(a, { className: "w-5 h-5 flex-shrink-0 mt-0.5 text-status-warning-text" }), /* @__PURE__ */ g("div", {
322
+ children: [/* @__PURE__ */ h(a, { className: "size-5 flex-shrink-0 mt-0.5 text-status-warning-text" }), /* @__PURE__ */ g("div", {
324
323
  className: "flex-1",
325
324
  children: [
326
325
  /* @__PURE__ */ h("p", {
@@ -336,7 +335,7 @@ function x() {
336
335
  onClick: () => b(decodeURIComponent($.current || Q || "/dashboard")),
337
336
  className: "mt-3 inline-flex items-center gap-2 px-4 py-2.5 rounded-xl border border-status-warning-border bg-bg-surface text-status-warning-text text-sm font-medium hover:bg-status-warning-bg-subtle hover:border-status-warning-border transition-all shadow-sm",
338
337
  children: [/* @__PURE__ */ h("svg", {
339
- className: "w-4 h-4",
338
+ className: "size-4",
340
339
  fill: "none",
341
340
  viewBox: "0 0 24 24",
342
341
  stroke: "currentColor",
@@ -353,7 +352,7 @@ function x() {
353
352
  }),
354
353
  T && !J && !Le && /* @__PURE__ */ g("div", {
355
354
  className: `flex items-start gap-3 p-4 rounded-xl border ${I || B || H ? "bg-status-warning-bg-subtle border-status-warning-border" : "bg-status-error-bg-subtle border-status-error-border"}`,
356
- children: [/* @__PURE__ */ h(a, { className: `w-5 h-5 flex-shrink-0 mt-0.5 ${I || B || H ? "text-status-warning-text" : "text-status-error-text"}` }), /* @__PURE__ */ g("div", {
355
+ children: [/* @__PURE__ */ h(a, { className: `size-5 flex-shrink-0 mt-0.5 ${I || B || H ? "text-status-warning-text" : "text-status-error-text"}` }), /* @__PURE__ */ g("div", {
357
356
  className: "flex-1",
358
357
  children: [
359
358
  /* @__PURE__ */ h("p", {
@@ -371,7 +370,7 @@ function x() {
371
370
  children: Ne ? /* @__PURE__ */ g("div", {
372
371
  className: "flex items-center gap-2 text-sm text-status-success-text",
373
372
  children: [/* @__PURE__ */ h("svg", {
374
- className: "w-4 h-4",
373
+ className: "size-4",
375
374
  fill: "none",
376
375
  viewBox: "0 0 24 24",
377
376
  stroke: "currentColor",
@@ -387,8 +386,8 @@ function x() {
387
386
  onClick: He,
388
387
  disabled: je,
389
388
  className: "inline-flex items-center gap-2 px-4 py-2.5 rounded-xl border border-status-warning-border bg-bg-surface text-status-warning-text text-sm font-medium hover:bg-status-warning-bg-subtle hover:border-status-warning-border transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed",
390
- children: je ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(te, { className: "w-4 h-4 animate-spin" }), C("auth.login.sending", "Sending...")] }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("svg", {
391
- className: "w-4 h-4",
389
+ children: je ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(te, { className: "size-4 animate-spin" }), C("auth.login.sending", "Sending...")] }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("svg", {
390
+ className: "size-4",
392
391
  fill: "none",
393
392
  viewBox: "0 0 24 24",
394
393
  stroke: "currentColor",
@@ -407,12 +406,12 @@ function x() {
407
406
  type: "button",
408
407
  onClick: Be,
409
408
  className: "inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border-default bg-bg-surface text-text-primary text-sm font-medium hover:bg-bg-canvas hover:border-border-default transition-all shadow-sm",
410
- children: [/* @__PURE__ */ h(l, { className: "w-5 h-5" }), C("auth.login.signInWithGoogle", "Sign in with Google")]
409
+ children: [/* @__PURE__ */ h(l, { className: "size-5" }), C("auth.login.signInWithGoogle", "Sign in with Google")]
411
410
  }), k("microsoft") && /* @__PURE__ */ g("button", {
412
411
  type: "button",
413
412
  onClick: Ve,
414
413
  className: "inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border-default bg-bg-surface text-text-primary text-sm font-medium hover:bg-bg-canvas hover:border-border-default transition-all shadow-sm",
415
- children: [/* @__PURE__ */ h(u, { className: "w-5 h-5" }), C("auth.login.signInWithMicrosoft", "Sign in with Microsoft")]
414
+ children: [/* @__PURE__ */ h(u, { className: "size-5" }), C("auth.login.signInWithMicrosoft", "Sign in with Microsoft")]
416
415
  })]
417
416
  })
418
417
  ]
@@ -428,13 +427,14 @@ function x() {
428
427
  className: "relative",
429
428
  children: [/* @__PURE__ */ h("div", {
430
429
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
431
- children: /* @__PURE__ */ h(o, { className: "w-5 h-5 text-[var(--color-text-tertiary)]" })
430
+ children: /* @__PURE__ */ h(o, { className: "size-5 text-[var(--color-text-tertiary)]" })
432
431
  }), /* @__PURE__ */ h("input", {
433
432
  id: "email",
434
433
  type: "email",
435
434
  autoComplete: "email",
436
435
  required: !0,
437
436
  "aria-required": "true",
437
+ "aria-label": C("auth.login.emailLabel", "Email"),
438
438
  value: K.email,
439
439
  onChange: (e) => q({
440
440
  ...K,
@@ -456,7 +456,7 @@ function x() {
456
456
  children: [
457
457
  /* @__PURE__ */ h("div", {
458
458
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
459
- children: /* @__PURE__ */ h(ee, { className: "w-5 h-5 text-[var(--color-text-tertiary)]" })
459
+ children: /* @__PURE__ */ h(ee, { className: "size-5 text-[var(--color-text-tertiary)]" })
460
460
  }),
461
461
  /* @__PURE__ */ h("input", {
462
462
  id: "password",
@@ -464,6 +464,7 @@ function x() {
464
464
  autoComplete: "current-password",
465
465
  required: !0,
466
466
  "aria-required": "true",
467
+ "aria-label": C("auth.login.passwordLabel", "Password"),
467
468
  value: K.password,
468
469
  onChange: (e) => q({
469
470
  ...K,
@@ -477,7 +478,7 @@ function x() {
477
478
  onClick: () => Oe(!F),
478
479
  "aria-label": F ? "Hide password" : "Show password",
479
480
  className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors",
480
- children: h(F ? c : s, { className: "w-5 h-5" })
481
+ children: h(F ? c : s, { className: "size-5" })
481
482
  })
482
483
  ]
483
484
  })]
@@ -490,6 +491,7 @@ function x() {
490
491
  className: "relative",
491
492
  children: [/* @__PURE__ */ h("input", {
492
493
  type: "checkbox",
494
+ "aria-label": C("auth.login.rememberMe", "Remember me"),
493
495
  checked: K.rememberMe,
494
496
  onChange: (e) => {
495
497
  let t = e.target.checked;
@@ -504,9 +506,9 @@ function x() {
504
506
  },
505
507
  className: "sr-only peer"
506
508
  }), /* @__PURE__ */ h("div", {
507
- className: "w-5 h-5 rounded-md border-2 border-[var(--color-border-default)] bg-[var(--color-bg-surface)] peer-checked:bg-status-info-bg-subtle0 peer-checked:border-action-primary-bg transition-all peer-focus:ring-2 peer-focus:ring-action-primary-bg/20",
509
+ className: "size-5 rounded-md border-2 border-[var(--color-border-default)] bg-[var(--color-bg-surface)] peer-checked:bg-status-info-bg-subtle0 peer-checked:border-action-primary-bg transition-all peer-focus:ring-2 peer-focus:ring-action-primary-bg/20",
508
510
  children: /* @__PURE__ */ h("svg", {
509
- className: "w-full h-full text-white opacity-0 peer-checked:opacity-100 transition-opacity p-0.5",
511
+ className: "size-full text-white opacity-0 peer-checked:opacity-100 transition-opacity p-0.5",
510
512
  fill: "none",
511
513
  viewBox: "0 0 24 24",
512
514
  stroke: "currentColor",
@@ -533,7 +535,7 @@ function x() {
533
535
  type: "submit",
534
536
  disabled: w || j,
535
537
  className: "auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold",
536
- children: w || j ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(te, { className: "w-5 h-5 animate-spin" }), /* @__PURE__ */ h("span", { children: j ? C("auth.login.settingUpWorkspace", "Setting up workspace...") : C("auth.login.signingIn", "Signing in...") })] }) : C("auth.login.signIn", "Sign in")
538
+ children: w || j ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(te, { className: "size-5 animate-spin" }), /* @__PURE__ */ h("span", { children: j ? C("auth.login.settingUpWorkspace", "Setting up workspace...") : C("auth.login.signingIn", "Signing in...") })] }) : C("auth.login.signIn", "Sign in")
537
539
  })
538
540
  ]
539
541
  }),
@@ -552,7 +554,7 @@ function x() {
552
554
  }),
553
555
  Pe && /* @__PURE__ */ g("div", {
554
556
  className: "flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border",
555
- children: [/* @__PURE__ */ h(a, { className: "w-5 h-5 flex-shrink-0 mt-0.5 text-status-error-text" }), /* @__PURE__ */ h("p", {
557
+ children: [/* @__PURE__ */ h(a, { className: "size-5 flex-shrink-0 mt-0.5 text-status-error-text" }), /* @__PURE__ */ h("p", {
556
558
  className: "text-sm text-status-error-text",
557
559
  children: Pe
558
560
  })]
@@ -566,7 +568,7 @@ function x() {
566
568
  title: e.tooltip,
567
569
  "aria-label": e.name,
568
570
  className: `flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${e.disabled ? "border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50" : "border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]"}`,
569
- children: [/* @__PURE__ */ h(e.icon, { className: `w-5 h-5 ${e.disabled ? "opacity-50" : ""}` }), /* @__PURE__ */ h("span", {
571
+ children: [/* @__PURE__ */ h(e.icon, { className: `size-5 ${e.disabled ? "opacity-50" : ""}` }), /* @__PURE__ */ h("span", {
570
572
  className: `sr-only sm:not-sr-only text-sm font-medium transition-colors hidden sm:inline ${e.disabled ? "text-[var(--color-text-disabled)]" : "text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]"}`,
571
573
  children: e.name
572
574
  })]
@@ -575,6 +577,7 @@ function x() {
575
577
  /* @__PURE__ */ h("div", {
576
578
  className: "mt-4 text-center",
577
579
  children: /* @__PURE__ */ g("button", {
580
+ type: "button",
578
581
  onClick: () => b("/auth/magic-link"),
579
582
  className: "text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors",
580
583
  children: [
@@ -593,6 +596,7 @@ function x() {
593
596
  C("auth.login.dontHaveAccount", "Don't have an account?"),
594
597
  " ",
595
598
  /* @__PURE__ */ h("button", {
599
+ type: "button",
596
600
  onClick: () => b("/auth/register"),
597
601
  className: "font-semibold text-text-link hover:text-text-linkHover transition-colors",
598
602
  children: C("auth.login.signUp", "Sign up")