@burdenoff/microfe-auth 2026.1030.1 → 2026.1101.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/auth/AuthRoot.js +14 -11
  2. package/dist/auth/AuthRoot.js.map +1 -1
  3. package/dist/auth/AuthRoutes.js +1 -2
  4. package/dist/auth/AuthRoutes.js.map +1 -1
  5. package/dist/auth/components/AuthIcons.js +1 -2
  6. package/dist/auth/components/AuthIcons.js.map +1 -1
  7. package/dist/auth/components/AuthLayout.js +1 -2
  8. package/dist/auth/components/AuthLayout.js.map +1 -1
  9. package/dist/auth/components/CaptchaV2.js +9 -10
  10. package/dist/auth/components/CaptchaV2.js.map +1 -1
  11. package/dist/auth/components/ConfirmActionDialog.js +1 -2
  12. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  13. package/dist/auth/components/profile/UserProfileMenu.js +7 -6
  14. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  15. package/dist/auth/components/workspace/WorkspaceSelector.js +1 -2
  16. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  17. package/dist/auth/constants/permissions.js +1 -2
  18. package/dist/auth/constants/permissions.js.map +1 -1
  19. package/dist/auth/constants/sessionKeys.js +1 -2
  20. package/dist/auth/constants/sessionKeys.js.map +1 -1
  21. package/dist/auth/hooks/contextDeepLink.js +1 -2
  22. package/dist/auth/hooks/contextDeepLink.js.map +1 -1
  23. package/dist/auth/hooks/index.js +12 -11
  24. package/dist/auth/hooks/useAccountOperations.js +1 -2
  25. package/dist/auth/hooks/useAccountOperations.js.map +1 -1
  26. package/dist/auth/hooks/useActiveContextBootstrapMirror.js +1 -2
  27. package/dist/auth/hooks/useActiveContextBootstrapMirror.js.map +1 -1
  28. package/dist/auth/hooks/useAuthOperations.js +37 -37
  29. package/dist/auth/hooks/useAuthOperations.js.map +1 -1
  30. package/dist/auth/hooks/useAuthPermissions.js +1 -2
  31. package/dist/auth/hooks/useAuthPermissions.js.map +1 -1
  32. package/dist/auth/hooks/useCaptchaV3.js +1 -2
  33. package/dist/auth/hooks/useCaptchaV3.js.map +1 -1
  34. package/dist/auth/hooks/useContextSwitcherData.js +81 -78
  35. package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
  36. package/dist/auth/hooks/useMfaOperations.js +4 -5
  37. package/dist/auth/hooks/useMfaOperations.js.map +1 -1
  38. package/dist/auth/hooks/useMyWorkspaces.js +4 -5
  39. package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
  40. package/dist/auth/hooks/useOAuthOperations.js +11 -12
  41. package/dist/auth/hooks/useOAuthOperations.js.map +1 -1
  42. package/dist/auth/hooks/usePasswordOperations.js +1 -2
  43. package/dist/auth/hooks/usePasswordOperations.js.map +1 -1
  44. package/dist/auth/hooks/useSessionOperations.js +3 -4
  45. package/dist/auth/hooks/useSessionOperations.js.map +1 -1
  46. package/dist/auth/hooks/useSignupCaptcha.js +1 -2
  47. package/dist/auth/hooks/useSignupCaptcha.js.map +1 -1
  48. package/dist/auth/hooks/useSsoOperations.js +80 -81
  49. package/dist/auth/hooks/useSsoOperations.js.map +1 -1
  50. package/dist/auth/hooks/useTokenRefresh.js +8 -12
  51. package/dist/auth/hooks/useTokenRefresh.js.map +1 -1
  52. package/dist/auth/hooks/useUserOperations.js +1 -2
  53. package/dist/auth/hooks/useUserOperations.js.map +1 -1
  54. package/dist/auth/hooks/useWorkspaceOperations.js +1 -2
  55. package/dist/auth/hooks/useWorkspaceOperations.js.map +1 -1
  56. package/dist/auth/hooks/useWorkspaceToken.js +1 -2
  57. package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
  58. package/dist/auth/index.js +19 -18
  59. package/dist/auth/pages/AuditLogPage.js +21 -22
  60. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  61. package/dist/auth/pages/EmailVerifyPage.js +70 -64
  62. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  63. package/dist/auth/pages/LoginPage.js +89 -105
  64. package/dist/auth/pages/LoginPage.js.map +1 -1
  65. package/dist/auth/pages/MagicLinkPage.js +30 -31
  66. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  67. package/dist/auth/pages/MfaSetupPage.js +18 -20
  68. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  69. package/dist/auth/pages/MfaVerifyPage.js +31 -35
  70. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  71. package/dist/auth/pages/OAuthCallbackPage.js +4 -11
  72. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  73. package/dist/auth/pages/PasswordResetPage.js +20 -23
  74. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  75. package/dist/auth/pages/ReactivatePage.js +14 -18
  76. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  77. package/dist/auth/pages/RegisterPage.js +180 -181
  78. package/dist/auth/pages/RegisterPage.js.map +1 -1
  79. package/dist/auth/pages/SSOClearPage.js +9 -10
  80. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  81. package/dist/auth/pages/SSODepositPage.js +9 -13
  82. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  83. package/dist/auth/pages/SSORelayCallbackPage.js +3 -7
  84. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  85. package/dist/auth/pages/SSORelayPage.js +11 -10
  86. package/dist/auth/pages/SSORelayPage.js.map +1 -1
  87. package/dist/auth/pages/SecurityPage.js +19 -21
  88. package/dist/auth/pages/SecurityPage.js.map +1 -1
  89. package/dist/auth/pages/SessionBridgePage.js +9 -16
  90. package/dist/auth/pages/SessionBridgePage.js.map +1 -1
  91. package/dist/auth/pages/SessionsPage.js +17 -18
  92. package/dist/auth/pages/SessionsPage.js.map +1 -1
  93. package/dist/auth/pages/SsoCallbackPage.js +17 -22
  94. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  95. package/dist/auth/pages/SsoConfigPage.js +69 -69
  96. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  97. package/dist/auth/pages/SsoLoginPage.js +9 -10
  98. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  99. package/dist/auth/pages/UserDetailsPage.js +16 -23
  100. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  101. package/dist/auth/pages/UserProfilePage.js +140 -141
  102. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  103. package/dist/auth/pages/UsersPage.js +446 -453
  104. package/dist/auth/pages/UsersPage.js.map +1 -1
  105. package/dist/auth/providers/AuthProvider.js +1 -2
  106. package/dist/auth/providers/AuthProvider.js.map +1 -1
  107. package/dist/auth/providers/CaptchaProvider.js +1 -2
  108. package/dist/auth/providers/CaptchaProvider.js.map +1 -1
  109. package/dist/auth/store/authStore.js +5 -12
  110. package/dist/auth/store/authStore.js.map +1 -1
  111. package/dist/auth/utils/captchaConfig.js +5 -8
  112. package/dist/auth/utils/captchaConfig.js.map +1 -1
  113. package/dist/auth/utils/formatters.js +1 -2
  114. package/dist/auth/utils/formatters.js.map +1 -1
  115. package/dist/auth/utils/gatewayBaseUrls.js +1 -2
  116. package/dist/auth/utils/gatewayBaseUrls.js.map +1 -1
  117. package/dist/auth/utils/legalUrls.js +2 -3
  118. package/dist/auth/utils/legalUrls.js.map +1 -1
  119. package/dist/auth/utils/redirect.js +1 -2
  120. package/dist/auth/utils/redirect.js.map +1 -1
  121. package/dist/auth/utils/ssoOrigins.js +2 -3
  122. package/dist/auth/utils/ssoOrigins.js.map +1 -1
  123. package/dist/auth/utils/validation.js +34 -36
  124. package/dist/auth/utils/validation.js.map +1 -1
  125. package/dist/generated/global-operations.js +2 -3
  126. package/dist/generated/global-operations.js.map +1 -1
  127. package/dist/generated/wspace-operations.js +2 -3
  128. package/dist/generated/wspace-operations.js.map +1 -1
  129. package/dist/microfe-auth.css +1 -1
  130. package/dist/shared/hooks/useTr.js +1 -2
  131. package/dist/shared/hooks/useTr.js.map +1 -1
  132. package/dist/translations/en.js +1 -2
  133. package/dist/translations/en.js.map +1 -1
  134. package/dist/utils/nativeBridge.js +1 -2
  135. package/package.json +7 -3
@@ -1,7 +1,7 @@
1
1
  import { useAuthStore as e } from "../store/authStore.js";
2
2
  import { useCallback as t, useState as n } from "react";
3
3
  import { graphqlFetch as r } from "@burdenoff/fe-libs/shared/graphql/fetch";
4
- //#region src/auth/hooks/useWorkspaceToken.ts
4
+ // src/auth/hooks/useWorkspaceToken.ts
5
5
  function i(i = {}) {
6
6
  let [a, o] = n(!1), [s, c] = n(null), l = t(async (t, n, i, a) => {
7
7
  o(!0), c(null);
@@ -88,7 +88,6 @@ function i(i = {}) {
88
88
  error: s
89
89
  };
90
90
  }
91
- //#endregion
92
91
  export { i as useWorkspaceToken };
93
92
 
94
93
  //# sourceMappingURL=useWorkspaceToken.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useWorkspaceToken.js","names":[],"sources":["../../../src/auth/hooks/useWorkspaceToken.ts"],"sourcesContent":["import { useState, useCallback } from \"react\";\nimport { graphqlFetch } from \"@burdenoff/fe-libs/shared/graphql/fetch\";\nimport { useAuthStore } from \"../store/authStore\";\n\n/**\n * Workspace token management hook\n *\n * Provides functions to issue, refresh, and verify workspace tokens.\n * Automatically updates the auth store with new tokens.\n *\n * API Flow:\n * - Calls workspace gateway (https://gw-wspace.local.burdenoff.com/workspaces/graphql)\n * - Gateway federates wspace-auth-svc which handles token operations\n * - Auth token (from global-auth-svc) goes in Authorization header\n *\n * Usage:\n * ```tsx\n * const { issueToken, refreshToken, verifyToken, isLoading, error } = useWorkspaceToken({\n * workspaceGatewayUrl: 'https://gw-wspace.local.burdenoff.com/workspaces/graphql'\n * });\n *\n * // After login and organization/workspace selection\n * await issueToken(organizationId, workspaceId);\n *\n * // Refresh token before expiry (uses stored org/workspace IDs)\n * await refreshToken();\n * ```\n */\nexport interface UseWorkspaceTokenOptions {\n /**\n * URL of workspace gateway GraphQL endpoint\n * Default: https://gw-wspace.local.burdenoff.com/workspaces/graphql\n *\n * Note: This should point to the workspace gateway, NOT directly to wspace-auth-svc.\n * The gateway federates wspace-auth-svc and handles routing.\n */\n workspaceGatewayUrl?: string;\n}\n\nexport interface WorkspaceTokenPayload {\n sub: string; // User ID\n workspaceId: string;\n tenantId?: string;\n email?: string;\n username?: string;\n role?: string;\n scopes: string[];\n iat: number;\n exp: number;\n}\n\nexport function useWorkspaceToken(_options: UseWorkspaceTokenOptions = {}) {\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n /**\n * Issue a new workspace token for the given workspace\n *\n * @param organizationId - Organization ID (required)\n * @param workspaceId - Workspace ID (optional, auto-resolves to first accessible)\n * @param projectId - Project ID (optional, auto-resolves to first accessible)\n * @param scopes - Requested scopes (optional, defaults to full access)\n */\n const issueToken = useCallback(\n async (\n organizationId: string,\n workspaceId?: string,\n projectId?: string,\n scopes?: string[],\n ): Promise<string> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const accessToken = useAuthStore.getState().accessToken;\n\n if (!accessToken) {\n throw new Error(\"No access token available. Please login first.\");\n }\n\n const response = await graphqlFetch<{\n issueWorkspaceToken: {\n success: boolean;\n token: string;\n expiresAt: string;\n organizationId: string;\n workspaceId: string;\n projectId?: string;\n scopes: string[];\n error?: { code?: string; message?: string };\n };\n }>({\n gateway: \"workspace\",\n query: `\n mutation IssueWorkspaceToken($input: IssueWorkspaceTokenInput!) {\n issueWorkspaceToken(input: $input) {\n success\n token\n expiresAt\n organizationId\n workspaceId\n projectId\n scopes\n error {\n code\n message\n }\n }\n }\n `,\n variables: {\n input: {\n organizationId,\n ...(workspaceId && { workspaceId }),\n ...(projectId && { projectId }),\n ...(scopes && { scopes }),\n },\n },\n authToken: accessToken,\n });\n\n const result = response.data?.issueWorkspaceToken;\n\n if (!result?.success || !result?.token) {\n const errorMsg =\n typeof result?.error?.message === \"string\"\n ? result.error.message\n : typeof result?.error === \"string\"\n ? result.error\n : \"Failed to issue workspace token\";\n throw new Error(errorMsg);\n }\n\n useAuthStore.getState().setWorkspaceToken(result.token);\n useAuthStore.getState().setCurrentWorkspaceId(result.workspaceId);\n\n setIsLoading(false);\n return result.token;\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Unknown error occurred\";\n console.error(\"[useWorkspaceToken] Error issuing token:\", errorMessage);\n setError(errorMessage);\n setIsLoading(false);\n throw err;\n }\n },\n [],\n );\n\n /**\n * Verify a workspace token\n *\n * @param token - Optional token to verify (uses stored token if not provided)\n */\n const verifyToken = useCallback(\n async (token?: string): Promise<WorkspaceTokenPayload> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToVerify = token || workspaceToken;\n\n if (!tokenToVerify) {\n throw new Error(\"No workspace token available to verify\");\n }\n\n const response = await graphqlFetch<{\n verifyWorkspaceToken: {\n valid: boolean;\n payload?: WorkspaceTokenPayload;\n error?: { code?: string; message?: string };\n };\n }>({\n gateway: \"workspace\",\n query: `\n query VerifyWorkspaceToken($token: String!) {\n verifyWorkspaceToken(token: $token) {\n valid\n payload {\n sub\n workspaceId\n tenantId\n email\n username\n role\n scopes\n iat\n exp\n }\n error {\n code\n message\n }\n }\n }\n `,\n variables: { token: tokenToVerify },\n });\n\n const result = response.data?.verifyWorkspaceToken;\n\n if (!result?.valid) {\n const errorMsg =\n typeof result?.error?.message === \"string\"\n ? result.error.message\n : typeof result?.error === \"string\"\n ? result.error\n : \"Token verification failed\";\n throw new Error(errorMsg);\n }\n\n setIsLoading(false);\n return result.payload!;\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Unknown error occurred\";\n console.error(\n \"[useWorkspaceToken] Error verifying token:\",\n errorMessage,\n );\n setError(errorMessage);\n setIsLoading(false);\n throw err;\n }\n },\n [],\n );\n\n /**\n * Refresh the workspace token\n */\n const refreshToken = useCallback(async (): Promise<string> => {\n const { currentOrganizationId, currentWorkspaceId } =\n useAuthStore.getState();\n if (!currentOrganizationId) {\n throw new Error(\"No organization ID in store. Please login first.\");\n }\n return issueToken(currentOrganizationId, currentWorkspaceId ?? undefined);\n }, [issueToken]);\n\n /**\n * Decode a JWT token to get its payload (client-side only, no verification)\n */\n const decodeToken = useCallback((token: string): WorkspaceTokenPayload => {\n try {\n const base64Url = token.split(\".\")[1];\n const base64 = base64Url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split(\"\")\n .map((c) => \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2))\n .join(\"\"),\n );\n return JSON.parse(jsonPayload);\n } catch {\n throw new Error(\"Failed to decode token\");\n }\n }, []);\n\n /**\n * Check if a token is expired (client-side check)\n */\n const isTokenExpired = useCallback(\n (token?: string): boolean => {\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToCheck = token || workspaceToken;\n\n if (!tokenToCheck) return true;\n\n const payload = decodeToken(tokenToCheck);\n const now = Math.floor(Date.now() / 1000);\n\n return payload.exp < now;\n } catch {\n return true;\n }\n },\n [decodeToken],\n );\n\n /**\n * Get time until token expiry in milliseconds\n */\n const getTimeUntilExpiry = useCallback(\n (token?: string): number => {\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToCheck = token || workspaceToken;\n\n if (!tokenToCheck) return 0;\n\n const payload = decodeToken(tokenToCheck);\n const now = Math.floor(Date.now() / 1000);\n const timeUntilExpiry = (payload.exp - now) * 1000; // Convert to ms\n\n return Math.max(0, timeUntilExpiry);\n } catch {\n return 0;\n }\n },\n [decodeToken],\n );\n\n return {\n issueToken,\n refreshToken,\n verifyToken,\n decodeToken,\n isTokenExpired,\n getTimeUntilExpiry,\n isLoading,\n error,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useWorkspaceToken.js","names":["useWorkspaceToken","isLoading","setIsLoading","useState","error","setError","issueToken","useCallback","accessToken","useAuthStore","result","graphqlFetch","workspaceId","projectId","scopes","errorMsg","err","errorMessage","verifyToken","tokenToVerify","token","workspaceToken","refreshToken","currentOrganizationId","currentWorkspaceId","decodeToken","base64","jsonPayload","c","tokenToCheck","payload","now","timeUntilExpiry"],"sources":["../../../src/auth/hooks/useWorkspaceToken.ts"],"sourcesContent":["import { useState, useCallback } from \"react\";\nimport { graphqlFetch } from \"@burdenoff/fe-libs/shared/graphql/fetch\";\nimport { useAuthStore } from \"../store/authStore\";\n\n/**\n * Workspace token management hook\n *\n * Provides functions to issue, refresh, and verify workspace tokens.\n * Automatically updates the auth store with new tokens.\n *\n * API Flow:\n * - Calls workspace gateway (https://gw-wspace.local.burdenoff.com/workspaces/graphql)\n * - Gateway federates wspace-auth-svc which handles token operations\n * - Auth token (from global-auth-svc) goes in Authorization header\n *\n * Usage:\n * ```tsx\n * const { issueToken, refreshToken, verifyToken, isLoading, error } = useWorkspaceToken({\n * workspaceGatewayUrl: 'https://gw-wspace.local.burdenoff.com/workspaces/graphql'\n * });\n *\n * // After login and organization/workspace selection\n * await issueToken(organizationId, workspaceId);\n *\n * // Refresh token before expiry (uses stored org/workspace IDs)\n * await refreshToken();\n * ```\n */\nexport interface UseWorkspaceTokenOptions {\n /**\n * URL of workspace gateway GraphQL endpoint\n * Default: https://gw-wspace.local.burdenoff.com/workspaces/graphql\n *\n * Note: This should point to the workspace gateway, NOT directly to wspace-auth-svc.\n * The gateway federates wspace-auth-svc and handles routing.\n */\n workspaceGatewayUrl?: string;\n}\n\nexport interface WorkspaceTokenPayload {\n sub: string; // User ID\n workspaceId: string;\n tenantId?: string;\n email?: string;\n username?: string;\n role?: string;\n scopes: string[];\n iat: number;\n exp: number;\n}\n\nexport function useWorkspaceToken(_options: UseWorkspaceTokenOptions = {}) {\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n /**\n * Issue a new workspace token for the given workspace\n *\n * @param organizationId - Organization ID (required)\n * @param workspaceId - Workspace ID (optional, auto-resolves to first accessible)\n * @param projectId - Project ID (optional, auto-resolves to first accessible)\n * @param scopes - Requested scopes (optional, defaults to full access)\n */\n const issueToken = useCallback(\n async (\n organizationId: string,\n workspaceId?: string,\n projectId?: string,\n scopes?: string[],\n ): Promise<string> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const accessToken = useAuthStore.getState().accessToken;\n\n if (!accessToken) {\n throw new Error(\"No access token available. Please login first.\");\n }\n\n const response = await graphqlFetch<{\n issueWorkspaceToken: {\n success: boolean;\n token: string;\n expiresAt: string;\n organizationId: string;\n workspaceId: string;\n projectId?: string;\n scopes: string[];\n error?: { code?: string; message?: string };\n };\n }>({\n gateway: \"workspace\",\n query: `\n mutation IssueWorkspaceToken($input: IssueWorkspaceTokenInput!) {\n issueWorkspaceToken(input: $input) {\n success\n token\n expiresAt\n organizationId\n workspaceId\n projectId\n scopes\n error {\n code\n message\n }\n }\n }\n `,\n variables: {\n input: {\n organizationId,\n ...(workspaceId && { workspaceId }),\n ...(projectId && { projectId }),\n ...(scopes && { scopes }),\n },\n },\n authToken: accessToken,\n });\n\n const result = response.data?.issueWorkspaceToken;\n\n if (!result?.success || !result?.token) {\n const errorMsg =\n typeof result?.error?.message === \"string\"\n ? result.error.message\n : typeof result?.error === \"string\"\n ? result.error\n : \"Failed to issue workspace token\";\n throw new Error(errorMsg);\n }\n\n useAuthStore.getState().setWorkspaceToken(result.token);\n useAuthStore.getState().setCurrentWorkspaceId(result.workspaceId);\n\n setIsLoading(false);\n return result.token;\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Unknown error occurred\";\n console.error(\"[useWorkspaceToken] Error issuing token:\", errorMessage);\n setError(errorMessage);\n setIsLoading(false);\n throw err;\n }\n },\n [],\n );\n\n /**\n * Verify a workspace token\n *\n * @param token - Optional token to verify (uses stored token if not provided)\n */\n const verifyToken = useCallback(\n async (token?: string): Promise<WorkspaceTokenPayload> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToVerify = token || workspaceToken;\n\n if (!tokenToVerify) {\n throw new Error(\"No workspace token available to verify\");\n }\n\n const response = await graphqlFetch<{\n verifyWorkspaceToken: {\n valid: boolean;\n payload?: WorkspaceTokenPayload;\n error?: { code?: string; message?: string };\n };\n }>({\n gateway: \"workspace\",\n query: `\n query VerifyWorkspaceToken($token: String!) {\n verifyWorkspaceToken(token: $token) {\n valid\n payload {\n sub\n workspaceId\n tenantId\n email\n username\n role\n scopes\n iat\n exp\n }\n error {\n code\n message\n }\n }\n }\n `,\n variables: { token: tokenToVerify },\n });\n\n const result = response.data?.verifyWorkspaceToken;\n\n if (!result?.valid) {\n const errorMsg =\n typeof result?.error?.message === \"string\"\n ? result.error.message\n : typeof result?.error === \"string\"\n ? result.error\n : \"Token verification failed\";\n throw new Error(errorMsg);\n }\n\n setIsLoading(false);\n return result.payload!;\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Unknown error occurred\";\n console.error(\n \"[useWorkspaceToken] Error verifying token:\",\n errorMessage,\n );\n setError(errorMessage);\n setIsLoading(false);\n throw err;\n }\n },\n [],\n );\n\n /**\n * Refresh the workspace token\n */\n const refreshToken = useCallback(async (): Promise<string> => {\n const { currentOrganizationId, currentWorkspaceId } =\n useAuthStore.getState();\n if (!currentOrganizationId) {\n throw new Error(\"No organization ID in store. Please login first.\");\n }\n return issueToken(currentOrganizationId, currentWorkspaceId ?? undefined);\n }, [issueToken]);\n\n /**\n * Decode a JWT token to get its payload (client-side only, no verification)\n */\n const decodeToken = useCallback((token: string): WorkspaceTokenPayload => {\n try {\n const base64Url = token.split(\".\")[1];\n const base64 = base64Url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split(\"\")\n .map((c) => \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2))\n .join(\"\"),\n );\n return JSON.parse(jsonPayload);\n } catch {\n throw new Error(\"Failed to decode token\");\n }\n }, []);\n\n /**\n * Check if a token is expired (client-side check)\n */\n const isTokenExpired = useCallback(\n (token?: string): boolean => {\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToCheck = token || workspaceToken;\n\n if (!tokenToCheck) return true;\n\n const payload = decodeToken(tokenToCheck);\n const now = Math.floor(Date.now() / 1000);\n\n return payload.exp < now;\n } catch {\n return true;\n }\n },\n [decodeToken],\n );\n\n /**\n * Get time until token expiry in milliseconds\n */\n const getTimeUntilExpiry = useCallback(\n (token?: string): number => {\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToCheck = token || workspaceToken;\n\n if (!tokenToCheck) return 0;\n\n const payload = decodeToken(tokenToCheck);\n const now = Math.floor(Date.now() / 1000);\n const timeUntilExpiry = (payload.exp - now) * 1000; // Convert to ms\n\n return Math.max(0, timeUntilExpiry);\n } catch {\n return 0;\n }\n },\n [decodeToken],\n );\n\n return {\n issueToken,\n refreshToken,\n verifyToken,\n decodeToken,\n isTokenExpired,\n getTimeUntilExpiry,\n isLoading,\n error,\n };\n}\n"],"mappings":"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"}
@@ -1,19 +1,20 @@
1
- import "./providers/AuthProvider.js";
2
- import "./store/authStore.js";
3
- import "./hooks/useAuthOperations.js";
4
- import "./hooks/useMyWorkspaces.js";
5
- import "./pages/SSORelayPage.js";
6
- import "./pages/SSORelayCallbackPage.js";
7
- import "./AuthRoot.js";
8
- import "./hooks/useMfaOperations.js";
9
- import "./hooks/usePasswordOperations.js";
10
- import "./hooks/useSessionOperations.js";
11
- import "./hooks/useWorkspaceOperations.js";
12
- import "./hooks/useContextSwitcherData.js";
13
- import "./hooks/useActiveContextBootstrapMirror.js";
14
- import "./hooks/useUserOperations.js";
1
+ import { AuthRoot as e } from "./AuthRoot.js";
2
+ import { useAuthStore as t } from "./store/authStore.js";
3
+ import { useAuthOperations as n } from "./hooks/useAuthOperations.js";
4
+ import { useMyWorkspaces as r } from "./hooks/useMyWorkspaces.js";
5
+ import { useAuth as i } from "./providers/AuthProvider.js";
15
6
  import "./hooks/index.js";
16
- import "./hooks/useWorkspaceToken.js";
17
- import "./hooks/useTokenRefresh.js";
18
- import "./components/workspace/WorkspaceSelector.js";
19
- import "./components/profile/UserProfileMenu.js";
7
+ import { useWorkspaceToken as a } from "./hooks/useWorkspaceToken.js";
8
+ import { useMfaOperations as o } from "./hooks/useMfaOperations.js";
9
+ import { usePasswordOperations as s } from "./hooks/usePasswordOperations.js";
10
+ import { useSessionOperations as c } from "./hooks/useSessionOperations.js";
11
+ import { useWorkspaceOperations as l } from "./hooks/useWorkspaceOperations.js";
12
+ import { useContextSwitcherData as u, useProjectSyncEffect as d, useWorkspaceSyncEffect as f } from "./hooks/useContextSwitcherData.js";
13
+ import { useActiveContextBootstrapMirror as p } from "./hooks/useActiveContextBootstrapMirror.js";
14
+ import { useUserOperations as m } from "./hooks/useUserOperations.js";
15
+ import { useTokenRefresh as h } from "./hooks/useTokenRefresh.js";
16
+ import { WorkspaceSelector as g } from "./components/workspace/WorkspaceSelector.js";
17
+ import { UserProfileMenu as _ } from "./components/profile/UserProfileMenu.js";
18
+ import { SSORelayPage as v } from "./pages/SSORelayPage.js";
19
+ import { SSORelayCallbackPage as y } from "./pages/SSORelayCallbackPage.js";
20
+ export { e as AuthRoot, y as SSORelayCallbackPage, v as SSORelayPage, _ as UserProfileMenu, g as WorkspaceSelector, p as useActiveContextBootstrapMirror, i as useAuth, n as useAuthOperations, t as useAuthStore, u as useContextSwitcherData, o as useMfaOperations, r as useMyWorkspaces, s as usePasswordOperations, d as useProjectSyncEffect, c as useSessionOperations, h as useTokenRefresh, m as useUserOperations, l as useWorkspaceOperations, f as useWorkspaceSyncEffect, a as useWorkspaceToken };
@@ -1,8 +1,8 @@
1
1
  import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { useAuthAuditLogsQuery as t } from "../../generated/global-operations.js";
2
+ import { formatDateTime as t } from "../utils/formatters.js";
3
3
  import { AlertIcon as n, SpinnerIcon as r } from "../components/AuthIcons.js";
4
- import { formatDateTime as i } from "../utils/formatters.js";
5
- import { ConfirmActionDialog as a } from "../components/ConfirmActionDialog.js";
4
+ import { ConfirmActionDialog as i } from "../components/ConfirmActionDialog.js";
5
+ import { useAuthAuditLogsQuery as a } from "../../generated/global-operations.js";
6
6
  import { useCreateAuthExportJobMutation as o } from "../../generated/wspace-operations.js";
7
7
  import { useState as s } from "react";
8
8
  import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
@@ -10,20 +10,20 @@ import { useFeatureFlagDetails as d } from "@burdenoff/fe-libs/shared/feature-fl
10
10
  import { isQuotaExhaustedError as f } from "@burdenoff/fe-libs/shared/utils";
11
11
  import { EmphasisPanel as p, IllustratedEmptyState as m, PagePurpose as h } from "@burdenoff/fe-libs/ui";
12
12
  import { useGatewayClient as g } from "@burdenoff/fe-libs/shared/graphql";
13
- //#region src/auth/pages/AuditLogPage.tsx
13
+ // src/auth/pages/AuditLogPage.tsx
14
14
  var _ = 20;
15
15
  function v() {
16
- let v = e(), [y, b] = s(1), x = (y - 1) * _, S = d("auth.audit-log")?.value === !0, { data: C, loading: w, error: T } = t({
16
+ let v = e(), [y, b] = s(1), x = (y - 1) * _, S = d("auth.audit-log")?.value === !0, { data: C, loading: w, error: T } = a({
17
17
  skip: !S,
18
18
  fetchPolicy: "cache-and-network",
19
19
  variables: { pagination: {
20
20
  limit: _,
21
21
  offset: x
22
22
  } }
23
- }), E = C?.authAuditLogs ?? [], D = E.length === _, [O, { loading: k }] = o({ client: g("workspace") }), [A, j] = s(null), [M, N] = s(!1), P = async () => {
24
- j(null);
23
+ }), E = C?.authAuditLogs ?? [], D = E.length === _, O = g("workspace"), [k, { loading: A }] = o({ client: O }), [j, M] = s(null), [N, P] = s(!1), F = async () => {
24
+ M(null);
25
25
  try {
26
- await O({ variables: { input: {
26
+ await k({ variables: { input: {
27
27
  name: "Audit Log Export",
28
28
  entityTypes: ["permission_audit_logs"],
29
29
  format: "CSV",
@@ -32,12 +32,12 @@ function v() {
32
32
  } } });
33
33
  } catch (e) {
34
34
  if (f(e)) {
35
- N(!0), j(v("auth.auditLog.exportQuotaReached", "You have reached your export limit for this month."));
35
+ P(!0), M(v("auth.auditLog.exportQuotaReached", "You have reached your export limit for this month."));
36
36
  return;
37
37
  }
38
- j(e instanceof Error ? e.message : "Failed to start export");
38
+ M(e instanceof Error ? e.message : "Failed to start export");
39
39
  }
40
- }, F = (e) => {
40
+ }, I = (e) => {
41
41
  switch (e) {
42
42
  case "LOGIN_SUCCESS": return "OK";
43
43
  case "LOGIN_FAILURE": return "ER";
@@ -59,11 +59,11 @@ function v() {
59
59
  children: v("auth.auditLog.title", "Security Audit Log")
60
60
  }), /* @__PURE__ */ l("button", {
61
61
  type: "button",
62
- onClick: () => void P(),
63
- disabled: k,
62
+ onClick: () => void F(),
63
+ disabled: A,
64
64
  "aria-label": v("auth.auditLog.exportAriaLabel", "Export audit log"),
65
65
  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",
66
- children: k ? /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(r, {
66
+ children: A ? /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(r, {
67
67
  className: "size-4 animate-spin",
68
68
  "aria-hidden": "true"
69
69
  }), v("auth.auditLog.exporting", "Exporting...")] }) : /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l("svg", {
@@ -95,7 +95,7 @@ function v() {
95
95
  children: v("auth.auditLog.emphasisBody", "Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.")
96
96
  })]
97
97
  }),
98
- A && /* @__PURE__ */ u("div", {
98
+ j && /* @__PURE__ */ u("div", {
99
99
  role: "alert",
100
100
  className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
101
101
  children: [/* @__PURE__ */ l(n, {
@@ -103,12 +103,12 @@ function v() {
103
103
  "aria-hidden": "true"
104
104
  }), /* @__PURE__ */ l("p", {
105
105
  className: "text-sm text-status-error-text",
106
- children: A
106
+ children: j
107
107
  })]
108
108
  }),
109
- /* @__PURE__ */ l(a, {
110
- open: M,
111
- onOpenChange: N,
109
+ /* @__PURE__ */ l(i, {
110
+ open: N,
111
+ onOpenChange: P,
112
112
  title: v("auth.auditLog.exportQuotaTitle", "Export Limit Reached"),
113
113
  description: v("auth.auditLog.exportQuotaDescription", "You have reached your export limit for this month."),
114
114
  confirmText: v("auth.auditLog.exportQuotaViewBilling", "View Billing"),
@@ -153,7 +153,7 @@ function v() {
153
153
  /* @__PURE__ */ l("div", {
154
154
  className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary",
155
155
  "aria-hidden": "true",
156
- children: F(e.action)
156
+ children: I(e.action)
157
157
  }),
158
158
  /* @__PURE__ */ u("div", {
159
159
  className: "flex-1",
@@ -166,7 +166,7 @@ function v() {
166
166
  className: "text-sm text-text-secondary mb-2",
167
167
  children: /* @__PURE__ */ l("time", {
168
168
  dateTime: e.createdAt,
169
- children: i(e.createdAt)
169
+ children: t(e.createdAt)
170
170
  })
171
171
  }),
172
172
  /* @__PURE__ */ u("p", {
@@ -221,7 +221,6 @@ function v() {
221
221
  })
222
222
  });
223
223
  }
224
- //#endregion
225
224
  export { v as AuditLogPage };
226
225
 
227
226
  //# sourceMappingURL=AuditLogPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuditLogPage.js","names":[],"sources":["../../../src/auth/pages/AuditLogPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n} from \"@burdenoff/fe-libs/ui\";\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 { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\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 tr = useTr();\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 const [showExportQuotaDialog, setShowExportQuotaDialog] = useState(false);\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 if (isQuotaExhaustedError(err)) {\n setShowExportQuotaDialog(true);\n setExportError(\n tr(\n \"auth.auditLog.exportQuotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\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 <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.notAvailable\", \"Page Not Available\")}\n description={tr(\n \"auth.auditLog.notEnabled\",\n \"The Security Audit Log is not enabled for your account.\",\n )}\n />\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 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\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 <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.auditLog.purpose\",\n \"A running record of security-relevant events on your account — sign-ins, password and MFA changes, and anomalies. Scan it to spot anything you didn't do, and export it for your own records or compliance.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: what's tracked — the one highlighted zone */}\n <EmphasisPanel className=\"mb-6\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.auditLog.emphasisTitle\", \"What this log captures\")}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.auditLog.emphasisBody\",\n \"Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.\",\n )}\n </p>\n </EmphasisPanel>\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 <ConfirmActionDialog\n open={showExportQuotaDialog}\n onOpenChange={setShowExportQuotaDialog}\n title={tr(\"auth.auditLog.exportQuotaTitle\", \"Export Limit Reached\")}\n description={tr(\n \"auth.auditLog.exportQuotaDescription\",\n \"You have reached your export limit for this month.\",\n )}\n confirmText={tr(\"auth.auditLog.exportQuotaViewBilling\", \"View Billing\")}\n cancelText={tr(\"auth.auditLog.exportQuotaDismiss\", \"Dismiss\")}\n variant=\"warning\"\n onConfirm={() => window.location.assign(\"/billing\")}\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 role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.error\", \"Unable to load audit events\")}\n description={tr(\n \"auth.auditLog.errorDesc\",\n \"Something went wrong fetching your security events. Please try again in a moment.\",\n )}\n />\n </div>\n ) : auditLogs.length === 0 ? (\n <output className=\"block\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr(\"auth.auditLog.noEvents\", \"No security events recorded\")}\n description={tr(\n \"auth.auditLog.noEventsDesc\",\n \"As you sign in and change security settings, those events will appear here.\",\n )}\n />\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-sunken 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 className=\"px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium\">\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":"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"}
1
+ {"version":3,"file":"AuditLogPage.js","names":["_Fragment","PAGE_SIZE","AuditLogPage","tr","useTr","page","setPage","useState","offset","showAuditLog","useFeatureFlagDetails","useAuthAuditLogsQuery","auditLogs","data","hasMore","wspaceClient","useGatewayClient","createExportJob","isExporting","useCreateAuthExportJobMutation","exportError","setExportError","showExportQuotaDialog","setShowExportQuotaDialog","handleExport","err","isQuotaExhaustedError","getEventMarker","eventType","Fragment","SpinnerIcon","PagePurpose","EmphasisPanel","AlertIcon","ConfirmActionDialog","loading","error","IllustratedEmptyState","event","formatDateTime","prev"],"sources":["../../../src/auth/pages/AuditLogPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n} from \"@burdenoff/fe-libs/ui\";\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 { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\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 tr = useTr();\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 const [showExportQuotaDialog, setShowExportQuotaDialog] = useState(false);\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 if (isQuotaExhaustedError(err)) {\n setShowExportQuotaDialog(true);\n setExportError(\n tr(\n \"auth.auditLog.exportQuotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\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 <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.notAvailable\", \"Page Not Available\")}\n description={tr(\n \"auth.auditLog.notEnabled\",\n \"The Security Audit Log is not enabled for your account.\",\n )}\n />\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 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\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 <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.auditLog.purpose\",\n \"A running record of security-relevant events on your account — sign-ins, password and MFA changes, and anomalies. Scan it to spot anything you didn't do, and export it for your own records or compliance.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: what's tracked — the one highlighted zone */}\n <EmphasisPanel className=\"mb-6\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.auditLog.emphasisTitle\", \"What this log captures\")}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.auditLog.emphasisBody\",\n \"Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.\",\n )}\n </p>\n </EmphasisPanel>\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 <ConfirmActionDialog\n open={showExportQuotaDialog}\n onOpenChange={setShowExportQuotaDialog}\n title={tr(\"auth.auditLog.exportQuotaTitle\", \"Export Limit Reached\")}\n description={tr(\n \"auth.auditLog.exportQuotaDescription\",\n \"You have reached your export limit for this month.\",\n )}\n confirmText={tr(\"auth.auditLog.exportQuotaViewBilling\", \"View Billing\")}\n cancelText={tr(\"auth.auditLog.exportQuotaDismiss\", \"Dismiss\")}\n variant=\"warning\"\n onConfirm={() => window.location.assign(\"/billing\")}\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 role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.error\", \"Unable to load audit events\")}\n description={tr(\n \"auth.auditLog.errorDesc\",\n \"Something went wrong fetching your security events. Please try again in a moment.\",\n )}\n />\n </div>\n ) : auditLogs.length === 0 ? (\n <output className=\"block\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr(\"auth.auditLog.noEvents\", \"No security events recorded\")}\n description={tr(\n \"auth.auditLog.noEventsDesc\",\n \"As you sign in and change security settings, those events will appear here.\",\n )}\n />\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-sunken 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 className=\"px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium\">\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":"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"}
@@ -1,61 +1,68 @@
1
- import { useAuthStore as e } from "../store/authStore.js";
2
- import { useTr as t } from "../../shared/hooks/useTr.js";
3
- import { useAuthOperations as n } from "../hooks/useAuthOperations.js";
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { useAuthOperations as t } from "../hooks/useAuthOperations.js";
3
+ import { useAuthStore as n } from "../store/authStore.js";
4
4
  import { AuthLayout as r } from "../components/AuthLayout.js";
5
5
  import { SpinnerIcon as i } from "../components/AuthIcons.js";
6
6
  import { useCallback as a, useEffect as o, useRef as s, useState as c } from "react";
7
7
  import { useSearchParams as l } from "react-router";
8
8
  import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
9
9
  import { Button as p } from "@burdenoff/fe-libs/button";
10
- //#region src/auth/pages/EmailVerifyPage.tsx
11
- var m = 5;
12
- function h(e) {
10
+ // src/auth/pages/EmailVerifyPage.tsx
11
+ var m = 5, h = /* @__PURE__ */ new Map();
12
+ function g(e) {
13
13
  let t = "/auth/login", n = e ? `${t}?email=${encodeURIComponent(e)}` : t;
14
14
  window.location.href = n;
15
15
  }
16
- function g() {
17
- let g = t(), [_] = l(), v = _.get("token"), y = _.get("email"), { verifyEmail: b, resendVerification: x } = n(), S = e((e) => e.user), [C, w] = c("verifying"), T = s(y || void 0), [E, D] = c(!1), [O, k] = c(!1), [A, j] = c(m), M = s(!1);
16
+ function _() {
17
+ let _ = e(), [v] = l(), y = v.get("token"), b = v.get("email"), { verifyEmail: x, resendVerification: S } = t(), C = n((e) => e.user), [w, T] = c("verifying"), E = s(b || void 0), [D, O] = c(!1), [k, A] = c(!1), [j, M] = c(m), N = s(null);
18
18
  o(() => {
19
- if (!v || M.current) return;
20
- let e = `auth:verifyEmail:inflight:${v}`;
21
- if (!(typeof window < "u" && window.sessionStorage.getItem(e))) {
22
- M.current = !0;
23
- try {
24
- window.sessionStorage.setItem(e, "1");
25
- } catch {}
26
- b(v).then((e) => {
27
- e.success ? (w("success"), e.email && (T.current = e.email)) : w("error");
28
- }).catch(() => w("error"));
19
+ if (!y) return;
20
+ try {
21
+ window.sessionStorage.removeItem(`auth:verifyEmail:inflight:${y}`);
22
+ } catch {}
23
+ if (N.current?.token !== y) {
24
+ let e = h.get(y);
25
+ e || (e = Promise.resolve().then(() => x(y)).finally(() => h.delete(y)), h.set(y, e)), N.current = {
26
+ token: y,
27
+ request: e
28
+ }, T("verifying");
29
29
  }
30
- }, [v, b]), o(() => {
31
- if (C !== "success") return;
30
+ let e = !0;
31
+ return N.current.request.then((t) => {
32
+ e && (t.success ? (T("success"), t.email && (E.current = t.email)) : T("error"));
33
+ }).catch(() => {
34
+ e && T("error");
35
+ }), () => {
36
+ e = !1;
37
+ };
38
+ }, [y, x]), o(() => {
39
+ if (w !== "success") return;
32
40
  let e = setInterval(() => {
33
- j((t) => t <= 1 ? (clearInterval(e), h(T.current), 0) : t - 1);
41
+ M((t) => t <= 1 ? (clearInterval(e), g(E.current), 0) : t - 1);
34
42
  }, 1e3);
35
43
  return () => clearInterval(e);
36
- }, [C]);
37
- let N = async () => {
38
- let e = y || S?.email;
39
- if (!e) {
40
- console.error("No email available for resend");
41
- return;
42
- }
43
- D(!0);
44
- try {
45
- await x(e), k(!0);
46
- } catch (e) {
47
- console.error("Failed to resend verification:", e);
48
- } finally {
49
- D(!1);
44
+ }, [w]);
45
+ let P = async () => {
46
+ let e = b || C?.email;
47
+ if (!e) console.error("No email available for resend");
48
+ else {
49
+ O(!0);
50
+ try {
51
+ await S(e), A(!0);
52
+ } catch (e) {
53
+ console.error("Failed to resend verification:", e);
54
+ } finally {
55
+ O(!1);
56
+ }
50
57
  }
51
- }, P = a(() => {
52
- h(T.current);
53
- }, []), F = a(() => {
54
- h();
58
+ }, F = a(() => {
59
+ g(E.current);
60
+ }, []), I = a(() => {
61
+ g();
55
62
  }, []);
56
- return C === "verifying" ? /* @__PURE__ */ d(r, {
57
- title: g("auth.emailVerify.verifyingTitle", "Verifying your email"),
58
- subtitle: g("auth.emailVerify.verifyingSubtitle", "Please wait while we confirm your email address"),
63
+ return w === "verifying" ? /* @__PURE__ */ d(r, {
64
+ title: _("auth.emailVerify.verifyingTitle", "Verifying your email"),
65
+ subtitle: _("auth.emailVerify.verifyingSubtitle", "Please wait while we confirm your email address"),
59
66
  children: /* @__PURE__ */ f("div", {
60
67
  className: "flex flex-col items-center justify-center py-12",
61
68
  children: [/* @__PURE__ */ d("div", {
@@ -63,12 +70,12 @@ function g() {
63
70
  children: /* @__PURE__ */ d(i, { className: "size-8 text-[var(--color-text-link)] animate-spin" })
64
71
  }), /* @__PURE__ */ d("p", {
65
72
  className: "text-[var(--color-text-secondary)]",
66
- children: g("auth.emailVerify.moment", "This will only take a moment...")
73
+ children: _("auth.emailVerify.moment", "This will only take a moment...")
67
74
  })]
68
75
  })
69
- }) : C === "success" ? /* @__PURE__ */ d(r, {
70
- title: g("auth.emailVerify.verifiedTitle", "Email verified!"),
71
- subtitle: g("auth.emailVerify.verifiedSubtitle", "Your email address has been confirmed"),
76
+ }) : w === "success" ? /* @__PURE__ */ d(r, {
77
+ title: _("auth.emailVerify.verifiedTitle", "Email verified!"),
78
+ subtitle: _("auth.emailVerify.verifiedSubtitle", "Your email address has been confirmed"),
72
79
  children: /* @__PURE__ */ f("div", {
73
80
  className: "flex flex-col items-center justify-center py-6",
74
81
  children: [
@@ -89,34 +96,34 @@ function g() {
89
96
  }),
90
97
  /* @__PURE__ */ d("p", {
91
98
  className: "text-center text-[var(--color-text-secondary)] mb-6",
92
- children: g("auth.emailVerify.successMessage", "Your email has been successfully verified. You can now sign in to access all features.")
99
+ children: _("auth.emailVerify.successMessage", "Your email has been successfully verified. You can now sign in to access all features.")
93
100
  }),
94
101
  /* @__PURE__ */ f("p", {
95
102
  className: "text-center text-sm text-[var(--color-text-tertiary)] mb-6",
96
103
  children: [
97
- g("auth.emailVerify.redirecting", "Redirecting to sign in in"),
104
+ _("auth.emailVerify.redirecting", "Redirecting to sign in in"),
98
105
  " ",
99
106
  /* @__PURE__ */ d("span", {
100
107
  className: "font-semibold text-[var(--color-text-primary)]",
101
- children: A
108
+ children: j
102
109
  }),
103
110
  " ",
104
- g("auth.emailVerify.seconds", "seconds...")
111
+ _("auth.emailVerify.seconds", "seconds...")
105
112
  ]
106
113
  }),
107
114
  /* @__PURE__ */ d(p, {
108
115
  type: "button",
109
- onClick: P,
116
+ onClick: F,
110
117
  variant: "default",
111
118
  size: "lg",
112
119
  className: "w-full font-semibold",
113
- children: g("auth.emailVerify.continueSignIn", "Continue to sign in now")
120
+ children: _("auth.emailVerify.continueSignIn", "Continue to sign in now")
114
121
  })
115
122
  ]
116
123
  })
117
124
  }) : /* @__PURE__ */ d(r, {
118
- title: g("auth.emailVerify.failedTitle", "Verification failed"),
119
- subtitle: g("auth.emailVerify.failedSubtitle", "We couldn't verify your email address"),
125
+ title: _("auth.emailVerify.failedTitle", "Verification failed"),
126
+ subtitle: _("auth.emailVerify.failedSubtitle", "We couldn't verify your email address"),
120
127
  children: /* @__PURE__ */ f("div", {
121
128
  className: "flex flex-col items-center justify-center py-6",
122
129
  children: [
@@ -137,34 +144,33 @@ function g() {
137
144
  }),
138
145
  /* @__PURE__ */ d("p", {
139
146
  className: "text-center text-[var(--color-text-secondary)] mb-8",
140
- children: g("auth.emailVerify.invalidLink", "The verification link is invalid or has expired. Please request a new verification email.")
147
+ children: _("auth.emailVerify.invalidLink", "The verification link is invalid or has expired. Please request a new verification email.")
141
148
  }),
142
- O ? /* @__PURE__ */ d("div", {
149
+ k ? /* @__PURE__ */ d("div", {
143
150
  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
151
  children: /* @__PURE__ */ d("p", {
145
152
  className: "text-sm text-[var(--color-status-success-text)]",
146
- children: g("auth.emailVerify.emailSent", "Verification email sent! Please check your inbox.")
153
+ children: _("auth.emailVerify.emailSent", "Verification email sent! Please check your inbox.")
147
154
  })
148
155
  }) : /* @__PURE__ */ d(p, {
149
156
  type: "button",
150
- onClick: N,
151
- disabled: E,
157
+ onClick: P,
158
+ disabled: D,
152
159
  variant: "default",
153
160
  size: "lg",
154
161
  className: "mb-4 w-full font-semibold",
155
- children: E ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(i, { className: "size-5 animate-spin" }), /* @__PURE__ */ d("span", { children: g("auth.emailVerify.sending", "Sending…") })] }) : g("auth.emailVerify.resend", "Resend verification email")
162
+ children: D ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(i, { className: "size-5 animate-spin" }), /* @__PURE__ */ d("span", { children: _("auth.emailVerify.sending", "Sending…") })] }) : _("auth.emailVerify.resend", "Resend verification email")
156
163
  }),
157
164
  /* @__PURE__ */ d("button", {
158
165
  type: "button",
159
- onClick: F,
166
+ onClick: I,
160
167
  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",
161
- children: g("auth.emailVerify.backToSignIn", "Back to sign in")
168
+ children: _("auth.emailVerify.backToSignIn", "Back to sign in")
162
169
  })
163
170
  ]
164
171
  })
165
172
  });
166
173
  }
167
- //#endregion
168
- export { g as EmailVerifyPage };
174
+ export { _ as EmailVerifyPage };
169
175
 
170
176
  //# sourceMappingURL=EmailVerifyPage.js.map