@burdenoff/microfe-auth 2026.925.1 → 2026.1009.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 (160) hide show
  1. package/README.md +14 -0
  2. package/dist/auth/AuthRoot.d.ts +0 -1
  3. package/dist/auth/AuthRoot.js.map +1 -1
  4. package/dist/auth/AuthRoutes.d.ts +1 -1
  5. package/dist/auth/AuthRoutes.js.map +1 -1
  6. package/dist/auth/components/AuthIcons.d.ts +12 -13
  7. package/dist/auth/components/AuthIcons.js.map +1 -1
  8. package/dist/auth/components/AuthLayout.d.ts +1 -2
  9. package/dist/auth/components/AuthLayout.js.map +1 -1
  10. package/dist/auth/components/CaptchaV2.d.ts +1 -1
  11. package/dist/auth/components/CaptchaV2.js +2 -2
  12. package/dist/auth/components/CaptchaV2.js.map +1 -1
  13. package/dist/auth/components/ConfirmActionDialog.d.ts +1 -1
  14. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  15. package/dist/auth/components/profile/UserProfileMenu.d.ts +1 -1
  16. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  17. package/dist/auth/components/workspace/WorkspaceSelector.d.ts +1 -2
  18. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  19. package/dist/auth/constants/permissions.js.map +1 -1
  20. package/dist/auth/hooks/useAccountOperations.d.ts +0 -1
  21. package/dist/auth/hooks/useAccountOperations.js.map +1 -1
  22. package/dist/auth/hooks/useActiveContextBootstrapMirror.js.map +1 -1
  23. package/dist/auth/hooks/useApiKeyOperations.d.ts +1 -2
  24. package/dist/auth/hooks/useAppOperations.d.ts +1 -2
  25. package/dist/auth/hooks/useAuthOperations.d.ts +0 -1
  26. package/dist/auth/hooks/useAuthOperations.js.map +1 -1
  27. package/dist/auth/hooks/useAuthPermissions.js.map +1 -1
  28. package/dist/auth/hooks/useCaptchaV3.js +1 -1
  29. package/dist/auth/hooks/useCaptchaV3.js.map +1 -1
  30. package/dist/auth/hooks/useContextSwitcherData.d.ts +1 -1
  31. package/dist/auth/hooks/useContextSwitcherData.js +346 -259
  32. package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
  33. package/dist/auth/hooks/useDelegationOperations.d.ts +0 -1
  34. package/dist/auth/hooks/useMfaOperations.d.ts +0 -1
  35. package/dist/auth/hooks/useMfaOperations.js.map +1 -1
  36. package/dist/auth/hooks/useMyWorkspaces.d.ts +0 -1
  37. package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
  38. package/dist/auth/hooks/useOAuthOperations.d.ts +0 -1
  39. package/dist/auth/hooks/useOAuthOperations.js +2 -2
  40. package/dist/auth/hooks/useOAuthOperations.js.map +1 -1
  41. package/dist/auth/hooks/usePasswordOperations.d.ts +0 -1
  42. package/dist/auth/hooks/usePasswordOperations.js.map +1 -1
  43. package/dist/auth/hooks/useSessionOperations.d.ts +0 -1
  44. package/dist/auth/hooks/useSessionOperations.js.map +1 -1
  45. package/dist/auth/hooks/useSignupCaptcha.js +1 -1
  46. package/dist/auth/hooks/useSignupCaptcha.js.map +1 -1
  47. package/dist/auth/hooks/useSsoOperations.d.ts +0 -1
  48. package/dist/auth/hooks/useSsoOperations.js.map +1 -1
  49. package/dist/auth/hooks/useTokenRefresh.js.map +1 -1
  50. package/dist/auth/hooks/useUserOperations.d.ts +0 -1
  51. package/dist/auth/hooks/useUserOperations.js.map +1 -1
  52. package/dist/auth/hooks/useWorkspaceOperations.d.ts +0 -1
  53. package/dist/auth/hooks/useWorkspaceOperations.js.map +1 -1
  54. package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
  55. package/dist/auth/pages/AuditLogPage.d.ts +1 -1
  56. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  57. package/dist/auth/pages/EmailVerifyPage.d.ts +1 -1
  58. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  59. package/dist/auth/pages/LoginPage.d.ts +1 -1
  60. package/dist/auth/pages/LoginPage.js.map +1 -1
  61. package/dist/auth/pages/MagicLinkPage.d.ts +1 -1
  62. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  63. package/dist/auth/pages/MfaSetupPage.d.ts +1 -1
  64. package/dist/auth/pages/MfaSetupPage.js +88 -88
  65. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  66. package/dist/auth/pages/MfaVerifyPage.d.ts +1 -1
  67. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  68. package/dist/auth/pages/OAuthCallbackPage.d.ts +1 -1
  69. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  70. package/dist/auth/pages/PasswordResetPage.d.ts +1 -1
  71. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  72. package/dist/auth/pages/ReactivatePage.d.ts +1 -1
  73. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  74. package/dist/auth/pages/RegisterPage.d.ts +1 -1
  75. package/dist/auth/pages/RegisterPage.js.map +1 -1
  76. package/dist/auth/pages/SSOClearPage.d.ts +1 -1
  77. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  78. package/dist/auth/pages/SSODepositPage.d.ts +1 -1
  79. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  80. package/dist/auth/pages/SSORelayCallbackPage.d.ts +1 -1
  81. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  82. package/dist/auth/pages/SSORelayPage.d.ts +1 -1
  83. package/dist/auth/pages/SSORelayPage.js.map +1 -1
  84. package/dist/auth/pages/SecurityPage.d.ts +1 -1
  85. package/dist/auth/pages/SecurityPage.js.map +1 -1
  86. package/dist/auth/pages/SessionBridgePage.d.ts +1 -1
  87. package/dist/auth/pages/SessionBridgePage.js.map +1 -1
  88. package/dist/auth/pages/SessionsPage.d.ts +1 -1
  89. package/dist/auth/pages/SessionsPage.js.map +1 -1
  90. package/dist/auth/pages/SsoCallbackPage.d.ts +1 -1
  91. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  92. package/dist/auth/pages/SsoConfigPage.d.ts +1 -1
  93. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  94. package/dist/auth/pages/SsoLoginPage.d.ts +1 -1
  95. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  96. package/dist/auth/pages/UserDetailsPage.d.ts +1 -1
  97. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  98. package/dist/auth/pages/UserProfilePage.d.ts +1 -1
  99. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  100. package/dist/auth/pages/UsersPage.d.ts +1 -1
  101. package/dist/auth/pages/UsersPage.js.map +1 -1
  102. package/dist/auth/providers/AuthProvider.d.ts +0 -1
  103. package/dist/auth/providers/AuthProvider.js.map +1 -1
  104. package/dist/auth/providers/CaptchaProvider.d.ts +1 -2
  105. package/dist/auth/providers/CaptchaProvider.js +2 -2
  106. package/dist/auth/providers/CaptchaProvider.js.map +1 -1
  107. package/dist/auth/store/authStore.d.ts +0 -1
  108. package/dist/auth/store/authStore.js.map +1 -1
  109. package/dist/auth/types/index.d.ts +0 -1
  110. package/dist/auth/utils/captchaConfig.js.map +1 -1
  111. package/dist/auth/utils/formatters.js.map +1 -1
  112. package/dist/auth/utils/gatewayBaseUrls.js.map +1 -1
  113. package/dist/auth/utils/legalUrls.js.map +1 -1
  114. package/dist/auth/utils/redirect.js.map +1 -1
  115. package/dist/auth/utils/ssoOrigins.js.map +1 -1
  116. package/dist/auth/utils/validation.d.ts +0 -1
  117. package/dist/auth/utils/validation.js.map +1 -1
  118. package/dist/dev/main.d.ts +0 -1
  119. package/dist/generated/global-operations.js.map +1 -1
  120. package/dist/generated/wspace-operations.js.map +1 -1
  121. package/dist/index.d.ts +0 -1
  122. package/dist/shared/hooks/useTr.js.map +1 -1
  123. package/dist/shared/types/index.d.ts +0 -1
  124. package/dist/translations/en.js.map +1 -1
  125. package/package.json +27 -20
  126. package/dist/_virtual/_rolldown/runtime.js +0 -13
  127. package/dist/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js +0 -70
  128. package/dist/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js.map +0 -1
  129. package/dist/node_modules/object-assign/index.js +0 -41
  130. package/dist/node_modules/object-assign/index.js.map +0 -1
  131. package/dist/node_modules/prop-types/checkPropTypes.js +0 -46
  132. package/dist/node_modules/prop-types/checkPropTypes.js.map +0 -1
  133. package/dist/node_modules/prop-types/factoryWithThrowingShims.js +0 -49
  134. package/dist/node_modules/prop-types/factoryWithThrowingShims.js.map +0 -1
  135. package/dist/node_modules/prop-types/factoryWithTypeCheckers.js +0 -272
  136. package/dist/node_modules/prop-types/factoryWithTypeCheckers.js.map +0 -1
  137. package/dist/node_modules/prop-types/index.js +0 -16
  138. package/dist/node_modules/prop-types/index.js.map +0 -1
  139. package/dist/node_modules/prop-types/lib/ReactPropTypesSecret.js +0 -10
  140. package/dist/node_modules/prop-types/lib/ReactPropTypesSecret.js.map +0 -1
  141. package/dist/node_modules/prop-types/lib/has.js +0 -10
  142. package/dist/node_modules/prop-types/lib/has.js.map +0 -1
  143. package/dist/node_modules/qrcode.react/lib/esm/index.js +0 -959
  144. package/dist/node_modules/qrcode.react/lib/esm/index.js.map +0 -1
  145. package/dist/node_modules/react-async-script/lib/esm/async-script-loader.js +0 -114
  146. package/dist/node_modules/react-async-script/lib/esm/async-script-loader.js.map +0 -1
  147. package/dist/node_modules/react-google-recaptcha/lib/esm/index.js +0 -8
  148. package/dist/node_modules/react-google-recaptcha/lib/esm/index.js.map +0 -1
  149. package/dist/node_modules/react-google-recaptcha/lib/esm/recaptcha-wrapper.js +0 -20
  150. package/dist/node_modules/react-google-recaptcha/lib/esm/recaptcha-wrapper.js.map +0 -1
  151. package/dist/node_modules/react-google-recaptcha/lib/esm/recaptcha.js +0 -148
  152. package/dist/node_modules/react-google-recaptcha/lib/esm/recaptcha.js.map +0 -1
  153. package/dist/node_modules/react-google-recaptcha-v3/dist/react-google-recaptcha-v3.esm.js +0 -445
  154. package/dist/node_modules/react-google-recaptcha-v3/dist/react-google-recaptcha-v3.esm.js.map +0 -1
  155. package/dist/node_modules/react-is/cjs/react-is.development.js +0 -84
  156. package/dist/node_modules/react-is/cjs/react-is.development.js.map +0 -1
  157. package/dist/node_modules/react-is/cjs/react-is.production.min.js +0 -64
  158. package/dist/node_modules/react-is/cjs/react-is.production.min.js.map +0 -1
  159. package/dist/node_modules/react-is/index.js +0 -12
  160. package/dist/node_modules/react-is/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceSelector.js","names":[],"sources":["../../../../src/auth/components/workspace/WorkspaceSelector.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, useId } from \"react\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\nimport type { Workspace } from \"../../types\";\n\n/**\n * Organization interface (grouped from workspaces)\n */\nexport interface Organization {\n id: string;\n name: string;\n workspaceCount: number;\n}\n\n/**\n * Project interface (optional, for future use)\n */\nexport interface Project {\n id: string;\n name: string;\n workspaceId: string;\n}\n\nexport interface WorkspaceSelectorProps {\n /**\n * Control modal visibility\n */\n isOpen: boolean;\n\n /**\n * Callback when modal is closed without selection\n */\n onClose: () => void;\n\n /**\n * List of workspaces to choose from\n */\n workspaces: Workspace[];\n\n /**\n * Optional list of projects (filtered by workspace)\n */\n projects?: Project[];\n\n /**\n * Callback when workspace is selected and confirmed\n */\n onSelect: (workspace: Workspace, project?: Project) => void;\n\n /**\n * Loading state while issuing workspace token\n */\n isLoading?: boolean;\n\n /**\n * Error message to display\n */\n error?: string | null;\n}\n\n/**\n * WorkspaceSelector Component\n *\n * Multi-level workspace selection modal for use across all Burdenoff products.\n *\n * Features:\n * - Three-tier selection: Organization → Workspace → Project (optional)\n * - Auto-select if only one option at each level\n * - Loading and error states\n * - Clean, accessible UI\n *\n * Usage:\n * ```tsx\n * import { WorkspaceSelector } from '@burdenoff/microfe-auth';\n *\n * <WorkspaceSelector\n * isOpen={showSelector}\n * onClose={() => setShowSelector(false)}\n * workspaces={workspaces}\n * onSelect={async (workspace) => {\n * await issueWorkspaceToken(workspace.id);\n * navigate('/');\n * }}\n * />\n * ```\n */\nconst EMPTY_PROJECTS: readonly Project[] = [];\n\nexport function WorkspaceSelector({\n isOpen,\n onClose,\n workspaces,\n projects = EMPTY_PROJECTS as Project[],\n onSelect,\n isLoading = false,\n error = null,\n}: WorkspaceSelectorProps) {\n const tr = useTr();\n\n // Generated ids for label-control pairing\n const orgSelectId = useId();\n const workspaceSelectId = useId();\n const projectSelectId = useId();\n // Selection state\n const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string | null>(\n null,\n );\n const [selectedProjectId, setSelectedProjectId] = useState<string | null>(\n null,\n );\n\n // Group workspaces by organization\n const organizations = useMemo<Organization[]>(() => {\n const orgMap = new Map<string, { name: string; count: number }>();\n\n workspaces.forEach((workspace) => {\n const orgId = workspace.organizationId || \"default\";\n if (!orgMap.has(orgId)) {\n // Use org ID for name since workspace doesn't have organizationName\n orgMap.set(orgId, {\n name:\n orgId === \"default\"\n ? tr(\"auth.workspaceSelector.defaultOrg\", \"Default\")\n : `${tr(\"auth.workspaceSelector.organizationPrefix\", \"Organization\")} ${orgId.slice(0, 8)}`,\n count: 0,\n });\n }\n orgMap.get(orgId)!.count++;\n });\n\n return Array.from(orgMap.entries()).map(([id, { name, count }]) => ({\n id,\n name,\n workspaceCount: count,\n }));\n }, [workspaces, tr]);\n\n // Filter workspaces by selected organization\n const filteredWorkspaces = useMemo(() => {\n if (!selectedOrgId) return workspaces;\n return workspaces.filter(\n (w) => (w.organizationId || \"default\") === selectedOrgId,\n );\n }, [workspaces, selectedOrgId]);\n\n // Filter projects by selected workspace\n const filteredProjects = useMemo(() => {\n if (!selectedWorkspaceId) return [];\n return projects.filter((p) => p.workspaceId === selectedWorkspaceId);\n }, [projects, selectedWorkspaceId]);\n\n // Get selected workspace object\n const selectedWorkspace = useMemo(() => {\n return workspaces.find((w) => w.id === selectedWorkspaceId);\n }, [workspaces, selectedWorkspaceId]);\n\n // Get selected project object\n const selectedProject = useMemo(() => {\n return projects.find((p) => p.id === selectedProjectId);\n }, [projects, selectedProjectId]);\n\n // Auto-select if only one organization\n useEffect(() => {\n if (organizations.length === 1 && !selectedOrgId) {\n setSelectedOrgId(organizations[0].id);\n }\n }, [organizations, selectedOrgId]);\n\n // Auto-select if only one workspace\n useEffect(() => {\n if (filteredWorkspaces.length === 1 && !selectedWorkspaceId) {\n setSelectedWorkspaceId(filteredWorkspaces[0].id);\n }\n }, [filteredWorkspaces, selectedWorkspaceId]);\n\n // Handle organization selection\n const handleOrgChange = (orgId: string) => {\n setSelectedOrgId(orgId);\n setSelectedWorkspaceId(null);\n setSelectedProjectId(null);\n };\n\n // Handle workspace selection\n const handleWorkspaceChange = (workspaceId: string) => {\n setSelectedWorkspaceId(workspaceId);\n setSelectedProjectId(null);\n };\n\n // Handle project selection\n const handleProjectChange = (projectId: string) => {\n setSelectedProjectId(projectId);\n };\n\n // Handle continue button\n const handleContinue = () => {\n if (!selectedWorkspace) return;\n onSelect(selectedWorkspace, selectedProject);\n };\n\n // Handle cancel\n const handleCancel = () => {\n setSelectedOrgId(null);\n setSelectedWorkspaceId(null);\n setSelectedProjectId(null);\n onClose();\n };\n\n // Check if continue button should be enabled\n const canContinue = selectedWorkspaceId !== null;\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div\n className=\"absolute inset-0 bg-black/50\"\n onClick={handleCancel}\n aria-hidden=\"true\"\n />\n\n {/* Modal */}\n <div className=\"relative bg-bg-surface rounded-2xl shadow-xl max-w-2xl w-full mx-4 p-6 space-y-6\">\n {/* Header */}\n <div className=\"space-y-2\">\n <h2 className=\"text-2xl font-semibold text-text-primary\">\n {tr(\"auth.workspaceSelector.title\", \"Select Your Workspace\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.workspaceSelector.subtitle\",\n \"Choose the workspace you want to work in. You can switch workspaces later from the header.\",\n )}\n </p>\n </div>\n\n {/* Error display */}\n {error && (\n <div className=\"rounded-lg bg-status-error-bg-subtle p-4 text-sm text-status-error-text\">\n <p className=\"font-medium\">\n {tr(\"auth.workspaceSelector.error\", \"Error\")}\n </p>\n <p>{error}</p>\n </div>\n )}\n\n {/* Form */}\n <div className=\"space-y-4\">\n {/* Organization selection */}\n {organizations.length > 1 && (\n <div className=\"space-y-2\">\n <label\n htmlFor={orgSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n {tr(\"auth.workspaceSelector.organization\", \"Organization\")}\n </span>\n </label>\n <select\n id={orgSelectId}\n value={selectedOrgId || \"\"}\n onChange={(e) => handleOrgChange(e.target.value)}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.selectOrg\",\n \"Select organization...\",\n )}\n </option>\n {organizations.map((org) => (\n <option key={org.id} value={org.id}>\n {tr(\n \"auth.workspaceSelector.orgOption\",\n \"{{name}} ({{count}} workspace{{plural}})\",\n {\n name: org.name,\n count: org.workspaceCount,\n plural: org.workspaceCount !== 1 ? \"s\" : \"\",\n },\n )}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Workspace selection */}\n <div className=\"space-y-2\">\n <label\n htmlFor={workspaceSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"\n />\n </svg>\n {tr(\"auth.workspaceSelector.workspace\", \"Workspace\")}\n </span>\n </label>\n <select\n id={workspaceSelectId}\n value={selectedWorkspaceId || \"\"}\n onChange={(e) => handleWorkspaceChange(e.target.value)}\n disabled={!selectedOrgId && organizations.length > 1}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.selectWorkspace\",\n \"Select workspace...\",\n )}\n </option>\n {filteredWorkspaces.map((workspace) => (\n <option key={workspace.id} value={workspace.id}>\n {workspace.name} ({workspace.slug})\n </option>\n ))}\n </select>\n </div>\n\n {/* Project selection (optional) */}\n {filteredProjects.length > 0 && (\n <div className=\"space-y-2\">\n <label\n htmlFor={projectSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n {tr(\n \"auth.workspaceSelector.projectOptional\",\n \"Project (Optional)\",\n )}\n </span>\n </label>\n <select\n id={projectSelectId}\n value={selectedProjectId || \"\"}\n onChange={(e) => handleProjectChange(e.target.value)}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.noProject\",\n \"No project selected\",\n )}\n </option>\n {filteredProjects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Selection summary */}\n {selectedWorkspace && (\n <div className=\"rounded-lg bg-bg-sunken p-4 space-y-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.workspaceSelector.selected\", \"Selected:\")}\n </p>\n <div className=\"text-sm text-text-secondary space-y-1\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"\n />\n </svg>\n <span>{selectedWorkspace.name}</span>\n </div>\n {selectedProject && (\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n <span>{selectedProject.name}</span>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {tr(\"auth.workspaceSelector.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleContinue}\n disabled={!canContinue || isLoading}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isLoading\n ? tr(\"auth.workspaceSelector.loading\", \"Loading...\")\n : tr(\"auth.workspaceSelector.continue\", \"Continue\")}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WorkspaceSelector.js","names":[],"sources":["../../../../src/auth/components/workspace/WorkspaceSelector.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, useId } from \"react\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\nimport type { Workspace } from \"../../types\";\n\n/**\n * Organization interface (grouped from workspaces)\n */\nexport interface Organization {\n id: string;\n name: string;\n workspaceCount: number;\n}\n\n/**\n * Project interface (optional, for future use)\n */\nexport interface Project {\n id: string;\n name: string;\n workspaceId: string;\n}\n\nexport interface WorkspaceSelectorProps {\n /**\n * Control modal visibility\n */\n isOpen: boolean;\n\n /**\n * Callback when modal is closed without selection\n */\n onClose: () => void;\n\n /**\n * List of workspaces to choose from\n */\n workspaces: Workspace[];\n\n /**\n * Optional list of projects (filtered by workspace)\n */\n projects?: Project[];\n\n /**\n * Callback when workspace is selected and confirmed\n */\n onSelect: (workspace: Workspace, project?: Project) => void;\n\n /**\n * Loading state while issuing workspace token\n */\n isLoading?: boolean;\n\n /**\n * Error message to display\n */\n error?: string | null;\n}\n\n/**\n * WorkspaceSelector Component\n *\n * Multi-level workspace selection modal for use across all Burdenoff products.\n *\n * Features:\n * - Three-tier selection: Organization → Workspace → Project (optional)\n * - Auto-select if only one option at each level\n * - Loading and error states\n * - Clean, accessible UI\n *\n * Usage:\n * ```tsx\n * import { WorkspaceSelector } from '@burdenoff/microfe-auth';\n *\n * <WorkspaceSelector\n * isOpen={showSelector}\n * onClose={() => setShowSelector(false)}\n * workspaces={workspaces}\n * onSelect={async (workspace) => {\n * await issueWorkspaceToken(workspace.id);\n * navigate('/');\n * }}\n * />\n * ```\n */\nconst EMPTY_PROJECTS: readonly Project[] = [];\n\nexport function WorkspaceSelector({\n isOpen,\n onClose,\n workspaces,\n projects = EMPTY_PROJECTS as Project[],\n onSelect,\n isLoading = false,\n error = null,\n}: WorkspaceSelectorProps) {\n const tr = useTr();\n\n // Generated ids for label-control pairing\n const orgSelectId = useId();\n const workspaceSelectId = useId();\n const projectSelectId = useId();\n // Selection state\n const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string | null>(\n null,\n );\n const [selectedProjectId, setSelectedProjectId] = useState<string | null>(\n null,\n );\n\n // Group workspaces by organization\n const organizations = useMemo<Organization[]>(() => {\n const orgMap = new Map<string, { name: string; count: number }>();\n\n workspaces.forEach((workspace) => {\n const orgId = workspace.organizationId || \"default\";\n if (!orgMap.has(orgId)) {\n // Use org ID for name since workspace doesn't have organizationName\n orgMap.set(orgId, {\n name:\n orgId === \"default\"\n ? tr(\"auth.workspaceSelector.defaultOrg\", \"Default\")\n : `${tr(\"auth.workspaceSelector.organizationPrefix\", \"Organization\")} ${orgId.slice(0, 8)}`,\n count: 0,\n });\n }\n orgMap.get(orgId)!.count++;\n });\n\n return Array.from(orgMap.entries()).map(([id, { name, count }]) => ({\n id,\n name,\n workspaceCount: count,\n }));\n }, [workspaces, tr]);\n\n // Filter workspaces by selected organization\n const filteredWorkspaces = useMemo(() => {\n if (!selectedOrgId) return workspaces;\n return workspaces.filter(\n (w) => (w.organizationId || \"default\") === selectedOrgId,\n );\n }, [workspaces, selectedOrgId]);\n\n // Filter projects by selected workspace\n const filteredProjects = useMemo(() => {\n if (!selectedWorkspaceId) return [];\n return projects.filter((p) => p.workspaceId === selectedWorkspaceId);\n }, [projects, selectedWorkspaceId]);\n\n // Get selected workspace object\n const selectedWorkspace = useMemo(() => {\n return workspaces.find((w) => w.id === selectedWorkspaceId);\n }, [workspaces, selectedWorkspaceId]);\n\n // Get selected project object\n const selectedProject = useMemo(() => {\n return projects.find((p) => p.id === selectedProjectId);\n }, [projects, selectedProjectId]);\n\n // Auto-select if only one organization\n useEffect(() => {\n if (organizations.length === 1 && !selectedOrgId) {\n setSelectedOrgId(organizations[0].id);\n }\n }, [organizations, selectedOrgId]);\n\n // Auto-select if only one workspace\n useEffect(() => {\n if (filteredWorkspaces.length === 1 && !selectedWorkspaceId) {\n setSelectedWorkspaceId(filteredWorkspaces[0].id);\n }\n }, [filteredWorkspaces, selectedWorkspaceId]);\n\n // Handle organization selection\n const handleOrgChange = (orgId: string) => {\n setSelectedOrgId(orgId);\n setSelectedWorkspaceId(null);\n setSelectedProjectId(null);\n };\n\n // Handle workspace selection\n const handleWorkspaceChange = (workspaceId: string) => {\n setSelectedWorkspaceId(workspaceId);\n setSelectedProjectId(null);\n };\n\n // Handle project selection\n const handleProjectChange = (projectId: string) => {\n setSelectedProjectId(projectId);\n };\n\n // Handle continue button\n const handleContinue = () => {\n if (!selectedWorkspace) return;\n onSelect(selectedWorkspace, selectedProject);\n };\n\n // Handle cancel\n const handleCancel = () => {\n setSelectedOrgId(null);\n setSelectedWorkspaceId(null);\n setSelectedProjectId(null);\n onClose();\n };\n\n // Check if continue button should be enabled\n const canContinue = selectedWorkspaceId !== null;\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div\n className=\"absolute inset-0 bg-black/50\"\n onClick={handleCancel}\n aria-hidden=\"true\"\n />\n\n {/* Modal */}\n <div className=\"relative bg-bg-surface rounded-2xl shadow-xl max-w-2xl w-full mx-4 p-6 space-y-6\">\n {/* Header */}\n <div className=\"space-y-2\">\n <h2 className=\"text-2xl font-semibold text-text-primary\">\n {tr(\"auth.workspaceSelector.title\", \"Select Your Workspace\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.workspaceSelector.subtitle\",\n \"Choose the workspace you want to work in. You can switch workspaces later from the header.\",\n )}\n </p>\n </div>\n\n {/* Error display */}\n {error && (\n <div className=\"rounded-lg bg-status-error-bg-subtle p-4 text-sm text-status-error-text\">\n <p className=\"font-medium\">\n {tr(\"auth.workspaceSelector.error\", \"Error\")}\n </p>\n <p>{error}</p>\n </div>\n )}\n\n {/* Form */}\n <div className=\"space-y-4\">\n {/* Organization selection */}\n {organizations.length > 1 && (\n <div className=\"space-y-2\">\n <label\n htmlFor={orgSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n {tr(\"auth.workspaceSelector.organization\", \"Organization\")}\n </span>\n </label>\n <select\n id={orgSelectId}\n value={selectedOrgId || \"\"}\n onChange={(e) => handleOrgChange(e.target.value)}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.selectOrg\",\n \"Select organization...\",\n )}\n </option>\n {organizations.map((org) => (\n <option key={org.id} value={org.id}>\n {tr(\n \"auth.workspaceSelector.orgOption\",\n \"{{name}} ({{count}} workspace{{plural}})\",\n {\n name: org.name,\n count: org.workspaceCount,\n plural: org.workspaceCount !== 1 ? \"s\" : \"\",\n },\n )}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Workspace selection */}\n <div className=\"space-y-2\">\n <label\n htmlFor={workspaceSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"\n />\n </svg>\n {tr(\"auth.workspaceSelector.workspace\", \"Workspace\")}\n </span>\n </label>\n <select\n id={workspaceSelectId}\n value={selectedWorkspaceId || \"\"}\n onChange={(e) => handleWorkspaceChange(e.target.value)}\n disabled={!selectedOrgId && organizations.length > 1}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.selectWorkspace\",\n \"Select workspace...\",\n )}\n </option>\n {filteredWorkspaces.map((workspace) => (\n <option key={workspace.id} value={workspace.id}>\n {workspace.name} ({workspace.slug})\n </option>\n ))}\n </select>\n </div>\n\n {/* Project selection (optional) */}\n {filteredProjects.length > 0 && (\n <div className=\"space-y-2\">\n <label\n htmlFor={projectSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n {tr(\n \"auth.workspaceSelector.projectOptional\",\n \"Project (Optional)\",\n )}\n </span>\n </label>\n <select\n id={projectSelectId}\n value={selectedProjectId || \"\"}\n onChange={(e) => handleProjectChange(e.target.value)}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.noProject\",\n \"No project selected\",\n )}\n </option>\n {filteredProjects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Selection summary */}\n {selectedWorkspace && (\n <div className=\"rounded-lg bg-bg-sunken p-4 space-y-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.workspaceSelector.selected\", \"Selected:\")}\n </p>\n <div className=\"text-sm text-text-secondary space-y-1\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"\n />\n </svg>\n <span>{selectedWorkspace.name}</span>\n </div>\n {selectedProject && (\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n <span>{selectedProject.name}</span>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {tr(\"auth.workspaceSelector.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleContinue}\n disabled={!canContinue || isLoading}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isLoading\n ? tr(\"auth.workspaceSelector.loading\", \"Loading...\")\n : tr(\"auth.workspaceSelector.continue\", \"Continue\")}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","names":[],"sources":["../../../src/auth/constants/permissions.ts"],"sourcesContent":["/**\n * Auth Permission Constants\n *\n * Permission strings matching the @rbac directives on global-auth-svc.\n * Only covers admin-facing operations — public auth flows (login, signup,\n * password reset, MFA verify, OAuth, SSO) don't need RBAC.\n * Self-service operations (own profile, sessions, API keys) are identity-scoped.\n *\n * Resource types and actions from global-rbac-svc seed:\n * user: read, update, delete\n * app: read, create, update\n * sso-config: manage, delete\n * oidc-config: manage, delete\n * auth-method: update\n */\n\nexport const AUTH_PERMISSIONS = {\n // User management (admin)\n USER_READ: \"user:read\",\n USER_UPDATE: \"user:update\",\n USER_DELETE: \"user:delete\",\n\n // OAuth app management\n APP_READ: \"app:read\",\n APP_CREATE: \"app:create\",\n APP_UPDATE: \"app:update\",\n\n // SSO configuration (admin)\n SSO_CONFIG_MANAGE: \"sso-config:manage\",\n SSO_CONFIG_DELETE: \"sso-config:delete\",\n\n // OIDC configuration (admin)\n OIDC_CONFIG_MANAGE: \"oidc-config:manage\",\n OIDC_CONFIG_DELETE: \"oidc-config:delete\",\n\n // Auth method configuration (admin)\n AUTH_METHOD_UPDATE: \"auth-method:update\",\n} as const;\n\nexport type AuthPermission =\n (typeof AUTH_PERMISSIONS)[keyof typeof AUTH_PERMISSIONS];\n"],"mappings":";AAgBA,IAAa,IAAmB;CAE9B,WAAW;CACX,aAAa;CACb,aAAa;CAGb,UAAU;CACV,YAAY;CACZ,YAAY;CAGZ,mBAAmB;CACnB,mBAAmB;CAGnB,oBAAoB;CACpB,oBAAoB;CAGpB,oBAAoB;CACrB"}
1
+ {"version":3,"file":"permissions.js","names":[],"sources":["../../../src/auth/constants/permissions.ts"],"sourcesContent":["/**\n * Auth Permission Constants\n *\n * Permission strings matching the @rbac directives on global-auth-svc.\n * Only covers admin-facing operations — public auth flows (login, signup,\n * password reset, MFA verify, OAuth, SSO) don't need RBAC.\n * Self-service operations (own profile, sessions, API keys) are identity-scoped.\n *\n * Resource types and actions from global-rbac-svc seed:\n * user: read, update, delete\n * app: read, create, update\n * sso-config: manage, delete\n * oidc-config: manage, delete\n * auth-method: update\n */\n\nexport const AUTH_PERMISSIONS = {\n // User management (admin)\n USER_READ: \"user:read\",\n USER_UPDATE: \"user:update\",\n USER_DELETE: \"user:delete\",\n\n // OAuth app management\n APP_READ: \"app:read\",\n APP_CREATE: \"app:create\",\n APP_UPDATE: \"app:update\",\n\n // SSO configuration (admin)\n SSO_CONFIG_MANAGE: \"sso-config:manage\",\n SSO_CONFIG_DELETE: \"sso-config:delete\",\n\n // OIDC configuration (admin)\n OIDC_CONFIG_MANAGE: \"oidc-config:manage\",\n OIDC_CONFIG_DELETE: \"oidc-config:delete\",\n\n // Auth method configuration (admin)\n AUTH_METHOD_UPDATE: \"auth-method:update\",\n} as const;\n\nexport type AuthPermission =\n (typeof AUTH_PERMISSIONS)[keyof typeof AUTH_PERMISSIONS];\n"],"mappings":";AAgBA,IAAa,IAAmB;CAE9B,WAAW;CACX,aAAa;CACb,aAAa;CAGb,UAAU;CACV,YAAY;CACZ,YAAY;CAGZ,mBAAmB;CACnB,mBAAmB;CAGnB,oBAAoB;CACpB,oBAAoB;CAGpB,oBAAoB;AACtB"}
@@ -1,5 +1,4 @@
1
1
  import { AuthResult } from '../types';
2
-
3
2
  export interface AccountActionResult {
4
3
  success: boolean;
5
4
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"useAccountOperations.js","names":[],"sources":["../../../src/auth/hooks/useAccountOperations.ts"],"sourcesContent":["import { useMutation } from \"@apollo/client/react\";\nimport { useAuthStore } from \"../store/authStore\";\nimport type { AuthResult } from \"../types\";\n\nimport {\n DeactivateAccountDocument,\n DeleteAccountDocument,\n ReactivateAccountDocument,\n} from \"../../generated/global-operations\";\n// =============================================================================\n// Response Types (Apollo Client 4.x)\n// =============================================================================\n\ninterface DeactivateAccountResponse {\n deactivateAccount: {\n success: boolean;\n message: string;\n };\n}\n\ninterface DeleteAccountResponse {\n deleteAccount: {\n success: boolean;\n message: string;\n };\n}\n\ninterface ReactivateAccountResponse {\n reactivateAccount: AuthResult;\n}\n\n// =============================================================================\n// GraphQL Operations\n// =============================================================================\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AccountActionResult {\n success: boolean;\n message: string;\n}\n\n// =============================================================================\n// Hook\n// =============================================================================\n\n/**\n * Hook for account management operations (deactivate, delete, reactivate)\n */\nexport function useAccountOperations() {\n const [deactivateAccountMutation, { loading: deactivating }] = useMutation<\n DeactivateAccountResponse,\n { password: string }\n >(DeactivateAccountDocument);\n const [deleteAccountMutation, { loading: deleting }] = useMutation<\n DeleteAccountResponse,\n { password: string; confirmPhrase: string }\n >(DeleteAccountDocument);\n const [reactivateAccountMutation, { loading: reactivating }] = useMutation<\n ReactivateAccountResponse,\n { email: string; password: string }\n >(ReactivateAccountDocument);\n\n const store = useAuthStore.getState();\n\n /**\n * Deactivate account (soft disable - can be reactivated)\n * Requires password confirmation\n */\n const deactivateAccount = async (\n password: string,\n ): Promise<AccountActionResult> => {\n try {\n const { data } = await deactivateAccountMutation({\n variables: { password },\n });\n\n const result = data!.deactivateAccount;\n\n if (result.success) {\n // Clear auth state after deactivation\n store.clearAuth();\n }\n\n return result;\n } catch (error) {\n console.error(\"Failed to deactivate account:\", error);\n throw error;\n }\n };\n\n /**\n * Delete account permanently\n * Requires password AND confirmation phrase \"DELETE MY ACCOUNT\"\n */\n const deleteAccount = async (\n password: string,\n confirmPhrase: string,\n ): Promise<AccountActionResult> => {\n // Validate confirmation phrase\n if (confirmPhrase !== \"DELETE MY ACCOUNT\") {\n throw new Error('Please type \"DELETE MY ACCOUNT\" to confirm deletion');\n }\n\n try {\n const { data } = await deleteAccountMutation({\n variables: { password, confirmPhrase },\n });\n\n const result = data!.deleteAccount;\n\n if (result.success) {\n // Clear auth state after deletion\n store.clearAuth();\n }\n\n return result;\n } catch (error) {\n console.error(\"Failed to delete account:\", error);\n throw error;\n }\n };\n\n /**\n * Reactivate a deactivated account\n * Returns new auth tokens\n */\n const reactivateAccount = async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n try {\n store.setLoading(true);\n store.setError(null);\n\n const { data } = await reactivateAccountMutation({\n variables: { email, password },\n });\n\n const result = data!.reactivateAccount;\n\n if (result.success && result.user && result.token) {\n store.setUser(result.user);\n store.setAuthTokens(result.token, result.refreshToken || \"\");\n store.setAuthenticated(true);\n } else if (result.error) {\n store.setError(result.error);\n }\n\n return result;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"Failed to reactivate account\";\n store.setError(message);\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n return {\n deactivateAccount,\n deleteAccount,\n reactivateAccount,\n isDeactivating: deactivating,\n isDeleting: deleting,\n isReactivating: reactivating,\n isLoading: deactivating || deleting || reactivating,\n };\n}\n"],"mappings":";;;;AAmDA,SAAgB,IAAuB;CACrC,IAAM,CAAC,GAA2B,EAAE,SAAS,OAAkB,EAG7D,EAA0B,EACtB,CAAC,GAAuB,EAAE,SAAS,OAAc,EAGrD,EAAsB,EAClB,CAAC,GAA2B,EAAE,SAAS,OAAkB,EAG7D,EAA0B,EAEtB,IAAQ,EAAa,UAAU;AAiGrC,QAAO;EACL,mBA5FwB,OACxB,MACiC;AACjC,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAA0B,EAC/C,WAAW,EAAE,aAAU,EACxB,CAAC,EAEI,IAAS,EAAM;AAOrB,WALI,EAAO,WAET,EAAM,WAAW,EAGZ;YACA,GAAO;AAEd,UADA,QAAQ,MAAM,iCAAiC,EAAM,EAC/C;;;EA2ER,eAnEoB,OACpB,GACA,MACiC;AAEjC,OAAI,MAAkB,oBACpB,OAAU,MAAM,wDAAsD;AAGxE,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAsB,EAC3C,WAAW;KAAE;KAAU;KAAe,EACvC,CAAC,EAEI,IAAS,EAAM;AAOrB,WALI,EAAO,WAET,EAAM,WAAW,EAGZ;YACA,GAAO;AAEd,UADA,QAAQ,MAAM,6BAA6B,EAAM,EAC3C;;;EA4CR,mBApCwB,OACxB,GACA,MACwB;AACxB,OAAI;AAEF,IADA,EAAM,WAAW,GAAK,EACtB,EAAM,SAAS,KAAK;IAEpB,IAAM,EAAE,YAAS,MAAM,EAA0B,EAC/C,WAAW;KAAE;KAAO;KAAU,EAC/B,CAAC,EAEI,IAAS,EAAM;AAUrB,WARI,EAAO,WAAW,EAAO,QAAQ,EAAO,SAC1C,EAAM,QAAQ,EAAO,KAAK,EAC1B,EAAM,cAAc,EAAO,OAAO,EAAO,gBAAgB,GAAG,EAC5D,EAAM,iBAAiB,GAAK,IACnB,EAAO,SAChB,EAAM,SAAS,EAAO,MAAM,EAGvB;YACA,GAAO;IACd,IAAM,IACJ,aAAiB,QAAQ,EAAM,UAAU;AAE3C,UADA,EAAM,SAAS,EAAQ,EACjB;aACE;AACR,MAAM,WAAW,GAAM;;;EAQzB,gBAAgB;EAChB,YAAY;EACZ,gBAAgB;EAChB,WAAW,KAAgB,KAAY;EACxC"}
1
+ {"version":3,"file":"useAccountOperations.js","names":[],"sources":["../../../src/auth/hooks/useAccountOperations.ts"],"sourcesContent":["import { useMutation } from \"@apollo/client/react\";\nimport { useAuthStore } from \"../store/authStore\";\nimport type { AuthResult } from \"../types\";\n\nimport {\n DeactivateAccountDocument,\n DeleteAccountDocument,\n ReactivateAccountDocument,\n} from \"../../generated/global-operations\";\n// =============================================================================\n// Response Types (Apollo Client 4.x)\n// =============================================================================\n\ninterface DeactivateAccountResponse {\n deactivateAccount: {\n success: boolean;\n message: string;\n };\n}\n\ninterface DeleteAccountResponse {\n deleteAccount: {\n success: boolean;\n message: string;\n };\n}\n\ninterface ReactivateAccountResponse {\n reactivateAccount: AuthResult;\n}\n\n// =============================================================================\n// GraphQL Operations\n// =============================================================================\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AccountActionResult {\n success: boolean;\n message: string;\n}\n\n// =============================================================================\n// Hook\n// =============================================================================\n\n/**\n * Hook for account management operations (deactivate, delete, reactivate)\n */\nexport function useAccountOperations() {\n const [deactivateAccountMutation, { loading: deactivating }] = useMutation<\n DeactivateAccountResponse,\n { password: string }\n >(DeactivateAccountDocument);\n const [deleteAccountMutation, { loading: deleting }] = useMutation<\n DeleteAccountResponse,\n { password: string; confirmPhrase: string }\n >(DeleteAccountDocument);\n const [reactivateAccountMutation, { loading: reactivating }] = useMutation<\n ReactivateAccountResponse,\n { email: string; password: string }\n >(ReactivateAccountDocument);\n\n const store = useAuthStore.getState();\n\n /**\n * Deactivate account (soft disable - can be reactivated)\n * Requires password confirmation\n */\n const deactivateAccount = async (\n password: string,\n ): Promise<AccountActionResult> => {\n try {\n const { data } = await deactivateAccountMutation({\n variables: { password },\n });\n\n const result = data!.deactivateAccount;\n\n if (result.success) {\n // Clear auth state after deactivation\n store.clearAuth();\n }\n\n return result;\n } catch (error) {\n console.error(\"Failed to deactivate account:\", error);\n throw error;\n }\n };\n\n /**\n * Delete account permanently\n * Requires password AND confirmation phrase \"DELETE MY ACCOUNT\"\n */\n const deleteAccount = async (\n password: string,\n confirmPhrase: string,\n ): Promise<AccountActionResult> => {\n // Validate confirmation phrase\n if (confirmPhrase !== \"DELETE MY ACCOUNT\") {\n throw new Error('Please type \"DELETE MY ACCOUNT\" to confirm deletion');\n }\n\n try {\n const { data } = await deleteAccountMutation({\n variables: { password, confirmPhrase },\n });\n\n const result = data!.deleteAccount;\n\n if (result.success) {\n // Clear auth state after deletion\n store.clearAuth();\n }\n\n return result;\n } catch (error) {\n console.error(\"Failed to delete account:\", error);\n throw error;\n }\n };\n\n /**\n * Reactivate a deactivated account\n * Returns new auth tokens\n */\n const reactivateAccount = async (\n email: string,\n password: string,\n ): Promise<AuthResult> => {\n try {\n store.setLoading(true);\n store.setError(null);\n\n const { data } = await reactivateAccountMutation({\n variables: { email, password },\n });\n\n const result = data!.reactivateAccount;\n\n if (result.success && result.user && result.token) {\n store.setUser(result.user);\n store.setAuthTokens(result.token, result.refreshToken || \"\");\n store.setAuthenticated(true);\n } else if (result.error) {\n store.setError(result.error);\n }\n\n return result;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"Failed to reactivate account\";\n store.setError(message);\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n return {\n deactivateAccount,\n deleteAccount,\n reactivateAccount,\n isDeactivating: deactivating,\n isDeleting: deleting,\n isReactivating: reactivating,\n isLoading: deactivating || deleting || reactivating,\n };\n}\n"],"mappings":";;;;AAmDA,SAAgB,IAAuB;CACrC,IAAM,CAAC,GAA2B,EAAE,SAAS,OAAkB,EAG7D,CAAyB,GACrB,CAAC,GAAuB,EAAE,SAAS,OAAc,EAGrD,CAAqB,GACjB,CAAC,GAA2B,EAAE,SAAS,OAAkB,EAG7D,CAAyB,GAErB,IAAQ,EAAa,SAAS;CAiGpC,OAAO;EACL,0BA3FA,MACiC;GACjC,IAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAA0B,EAC/C,WAAW,EAAE,YAAS,EACxB,CAAC,GAEK,IAAS,EAAM;IAOrB,OALI,EAAO,WAET,EAAM,UAAU,GAGX;GACT,SAAS,GAAO;IAEd,MADA,QAAQ,MAAM,iCAAiC,CAAK,GAC9C;GACR;EACF;EAyEE,sBAlEA,GACA,MACiC;GAEjC,IAAI,MAAkB,qBACpB,MAAU,MAAM,uDAAqD;GAGvE,IAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAsB,EAC3C,WAAW;KAAE;KAAU;IAAc,EACvC,CAAC,GAEK,IAAS,EAAM;IAOrB,OALI,EAAO,WAET,EAAM,UAAU,GAGX;GACT,SAAS,GAAO;IAEd,MADA,QAAQ,MAAM,6BAA6B,CAAK,GAC1C;GACR;EACF;EA0CE,0BAnCA,GACA,MACwB;GACxB,IAAI;IAEF,AADA,EAAM,WAAW,EAAI,GACrB,EAAM,SAAS,IAAI;IAEnB,IAAM,EAAE,YAAS,MAAM,EAA0B,EAC/C,WAAW;KAAE;KAAO;IAAS,EAC/B,CAAC,GAEK,IAAS,EAAM;IAUrB,OARI,EAAO,WAAW,EAAO,QAAQ,EAAO,SAC1C,EAAM,QAAQ,EAAO,IAAI,GACzB,EAAM,cAAc,EAAO,OAAO,EAAO,gBAAgB,EAAE,GAC3D,EAAM,iBAAiB,EAAI,KAClB,EAAO,SAChB,EAAM,SAAS,EAAO,KAAK,GAGtB;GACT,SAAS,GAAO;IACd,IAAM,IACJ,aAAiB,QAAQ,EAAM,UAAU;IAE3C,MADA,EAAM,SAAS,CAAO,GAChB;GACR,UAAU;IACR,EAAM,WAAW,EAAK;GACxB;EACF;EAME,gBAAgB;EAChB,YAAY;EACZ,gBAAgB;EAChB,WAAW,KAAgB,KAAY;CACzC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useActiveContextBootstrapMirror.js","names":[],"sources":["../../../src/auth/hooks/useActiveContextBootstrapMirror.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\ntype ContextId = string | null | undefined;\n\ninterface MirrorWorkspace {\n id: string;\n organizationId?: string | null;\n}\n\ninterface MirrorProject {\n id: string;\n workspaceId?: string | null;\n}\n\nexport interface ActiveContextBootstrapMirrorArgs {\n /** Auth-store `currentOrganizationId` */\n storeOrganizationId: ContextId;\n /** Auth-store `currentWorkspaceId` */\n storeWorkspaceId: ContextId;\n /** Auth-store `currentProjectId` */\n storeProjectId: ContextId;\n /** ActiveContext `organizationId` */\n activeOrganizationId: ContextId;\n /** ActiveContext `workspaceId` */\n activeWorkspaceId: ContextId;\n /** ActiveContext `projectId` */\n activeProjectId: ContextId;\n /**\n * The auth store's loaded `workspaces`. Used to verify that\n * `storeWorkspaceId` actually belongs to the organization being mirrored\n * before it is filled. If omitted, the workspace axis is never mirrored\n * (safe default — better no workspace than one from the wrong org).\n */\n storeWorkspaces?: ReadonlyArray<MirrorWorkspace>;\n /**\n * The auth store's loaded `projects`. Used to verify that `storeProjectId`\n * actually belongs to the workspace being mirrored before it is filled. If\n * omitted, the project axis is never mirrored.\n */\n storeProjects?: ReadonlyArray<MirrorProject>;\n /**\n * ActiveContext setters. Must be **stable references** (they are, coming\n * from `ActiveContextProvider`'s memoized value) — passing inline arrows\n * makes this effect re-run every render. Likewise prefer stable references\n * for `storeWorkspaces` / `storeProjects` (e.g. a shallow-equal Zustand\n * selector or `useMemo`); a new array reference each render re-runs the\n * effect. Re-runs are harmless (the fills are idempotent), just wasted work.\n */\n setOrganization: (organizationId: string | null) => void;\n setWorkspace: (workspaceId: string | null) => void;\n setProject: (projectId: string | null) => void;\n}\n\n/**\n * One-way, **fill-only** mirror of the auth store's org / workspace / project\n * ids into ActiveContext, for app-shell headers / context switchers.\n *\n * Some context ids are derived programmatically in the Zustand auth store\n * without a paired ActiveContext write:\n * - the bootstrap in `useContextSwitcherData` auto-selects an organization\n * (`setCurrentOrganizationId`) and, on the persisted-workspace path, a\n * workspace (`setCurrentWorkspaceId`) without calling `setActiveContext`;\n * - `useProjectSyncEffect` / `selectWorkspace` auto-select a project.\n *\n * ActiveContext is the source of truth for the URL + storage + the props passed\n * to workspace MFEs and the workspace-token context, so those store-only ids\n * must reach it — otherwise e.g. a fresh `defaultWorkspaceId` login leaves\n * `organizationId` undefined and the token / MFE context break until the user\n * reselects.\n *\n * The rule is **fill, never override**: copy a store id into ActiveContext only\n * when ActiveContext has no value for that axis yet. We must NOT overwrite a\n * non-null ActiveContext value, because:\n * 1. it would clobber an explicit selection — a switch handler's write, or a\n * deep-link / browser-back `?workspace=`/`?project=` that the router already\n * applied; and\n * 2. overriding is exactly what turns this into a two-store ping-pong — the\n * store→AC and AC→store effects each read the other's pre-commit value and\n * swap the ids every commit until React throws \"Maximum update depth\n * exceeded\" (error #185). Filling only nulls is self-terminating: once the\n * axis is set the guard is false forever, so it can never alternate.\n *\n * Cross-context safety: a value is filled only when it provably belongs to the\n * parent context being mirrored, validated against the loaded store metadata —\n * the **workspace** must belong to the target organization (`storeWorkspaces`),\n * and the **project** must belong to the target workspace (`storeProjects`).\n * The store can transiently hold a stale child id after its parent advanced\n * (e.g. `useContextSwitcherData` copies a new org into the store before the new\n * org's — unpersisted — workspace list loads, leaving `currentWorkspaceId`\n * pointing at the previous org; or the workspace advances to B while the\n * project still points at A). Gating on loaded metadata means such a stale id\n * isn't in the list yet, so it is not mirrored until it is genuinely valid.\n *\n * See BOFF-2805.\n */\nexport function useActiveContextBootstrapMirror({\n storeOrganizationId,\n storeWorkspaceId,\n storeProjectId,\n activeOrganizationId,\n activeWorkspaceId,\n activeProjectId,\n storeWorkspaces,\n storeProjects,\n setOrganization,\n setWorkspace,\n setProject,\n}: ActiveContextBootstrapMirrorArgs): void {\n useEffect(() => {\n if (storeOrganizationId && !activeOrganizationId) {\n setOrganization(storeOrganizationId);\n }\n\n // Fill the workspace only when it provably belongs to the organization\n // ActiveContext is on (or is being filled this pass). Guards against\n // publishing a (org, workspace) pair from two different orgs while the\n // store's org has advanced but its (unpersisted) workspace list / current\n // workspace id has not caught up.\n const targetOrganizationId = activeOrganizationId || storeOrganizationId;\n const workspaceBelongsToTargetOrganization =\n !!storeWorkspaceId &&\n !!targetOrganizationId &&\n !!storeWorkspaces?.some(\n (workspace) =>\n workspace.id === storeWorkspaceId &&\n workspace.organizationId === targetOrganizationId,\n );\n\n if (\n storeWorkspaceId &&\n !activeWorkspaceId &&\n workspaceBelongsToTargetOrganization\n ) {\n setWorkspace(storeWorkspaceId);\n }\n\n // Fill the project only when it provably belongs to the workspace\n // ActiveContext is on (or is being filled this pass). Prevents a stale\n // project from a previously-selected workspace leaking across a switch.\n // Fall back to the store workspace only when it is being filled this pass;\n // otherwise (e.g. workspace fill skipped because its metadata hasn't\n // loaded) we'd let a project fill without its parent workspace present,\n // an inconsistent (org, null, project) state.\n const targetWorkspaceId =\n activeWorkspaceId ||\n (workspaceBelongsToTargetOrganization ? storeWorkspaceId : null);\n const projectBelongsToTargetWorkspace =\n !!storeProjectId &&\n !!targetWorkspaceId &&\n !!storeProjects?.some(\n (project) =>\n project.id === storeProjectId &&\n project.workspaceId === targetWorkspaceId,\n );\n\n if (storeProjectId && !activeProjectId && projectBelongsToTargetWorkspace) {\n setProject(storeProjectId);\n }\n }, [\n storeOrganizationId,\n storeWorkspaceId,\n storeProjectId,\n activeOrganizationId,\n activeWorkspaceId,\n activeProjectId,\n storeWorkspaces,\n storeProjects,\n setOrganization,\n setWorkspace,\n setProject,\n ]);\n}\n"],"mappings":";;AA+FA,SAAgB,EAAgC,EAC9C,wBACA,qBACA,mBACA,yBACA,sBACA,oBACA,oBACA,kBACA,oBACA,iBACA,iBACyC;AACzC,SAAgB;AACd,EAAI,KAAuB,CAAC,KAC1B,EAAgB,EAAoB;EAQtC,IAAM,IAAuB,KAAwB,GAC/C,IACJ,CAAC,CAAC,KACF,CAAC,CAAC,KACF,CAAC,CAAC,GAAiB,MAChB,MACC,EAAU,OAAO,KACjB,EAAU,mBAAmB,EAChC;AAEH,EACE,KACA,CAAC,KACD,KAEA,EAAa,EAAiB;EAUhC,IAAM,IACJ,MACC,IAAuC,IAAmB,OACvD,IACJ,CAAC,CAAC,KACF,CAAC,CAAC,KACF,CAAC,CAAC,GAAe,MACd,MACC,EAAQ,OAAO,KACf,EAAQ,gBAAgB,EAC3B;AAEH,EAAI,KAAkB,CAAC,KAAmB,KACxC,EAAW,EAAe;IAE3B;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC"}
1
+ {"version":3,"file":"useActiveContextBootstrapMirror.js","names":[],"sources":["../../../src/auth/hooks/useActiveContextBootstrapMirror.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\ntype ContextId = string | null | undefined;\n\ninterface MirrorWorkspace {\n id: string;\n organizationId?: string | null;\n}\n\ninterface MirrorProject {\n id: string;\n workspaceId?: string | null;\n}\n\nexport interface ActiveContextBootstrapMirrorArgs {\n /** Auth-store `currentOrganizationId` */\n storeOrganizationId: ContextId;\n /** Auth-store `currentWorkspaceId` */\n storeWorkspaceId: ContextId;\n /** Auth-store `currentProjectId` */\n storeProjectId: ContextId;\n /** ActiveContext `organizationId` */\n activeOrganizationId: ContextId;\n /** ActiveContext `workspaceId` */\n activeWorkspaceId: ContextId;\n /** ActiveContext `projectId` */\n activeProjectId: ContextId;\n /**\n * The auth store's loaded `workspaces`. Used to verify that\n * `storeWorkspaceId` actually belongs to the organization being mirrored\n * before it is filled. If omitted, the workspace axis is never mirrored\n * (safe default — better no workspace than one from the wrong org).\n */\n storeWorkspaces?: ReadonlyArray<MirrorWorkspace>;\n /**\n * The auth store's loaded `projects`. Used to verify that `storeProjectId`\n * actually belongs to the workspace being mirrored before it is filled. If\n * omitted, the project axis is never mirrored.\n */\n storeProjects?: ReadonlyArray<MirrorProject>;\n /**\n * ActiveContext setters. Must be **stable references** (they are, coming\n * from `ActiveContextProvider`'s memoized value) — passing inline arrows\n * makes this effect re-run every render. Likewise prefer stable references\n * for `storeWorkspaces` / `storeProjects` (e.g. a shallow-equal Zustand\n * selector or `useMemo`); a new array reference each render re-runs the\n * effect. Re-runs are harmless (the fills are idempotent), just wasted work.\n */\n setOrganization: (organizationId: string | null) => void;\n setWorkspace: (workspaceId: string | null) => void;\n setProject: (projectId: string | null) => void;\n}\n\n/**\n * One-way, **fill-only** mirror of the auth store's org / workspace / project\n * ids into ActiveContext, for app-shell headers / context switchers.\n *\n * Some context ids are derived programmatically in the Zustand auth store\n * without a paired ActiveContext write:\n * - the bootstrap in `useContextSwitcherData` auto-selects an organization\n * (`setCurrentOrganizationId`) and, on the persisted-workspace path, a\n * workspace (`setCurrentWorkspaceId`) without calling `setActiveContext`;\n * - `useProjectSyncEffect` / `selectWorkspace` auto-select a project.\n *\n * ActiveContext is the source of truth for the URL + storage + the props passed\n * to workspace MFEs and the workspace-token context, so those store-only ids\n * must reach it — otherwise e.g. a fresh `defaultWorkspaceId` login leaves\n * `organizationId` undefined and the token / MFE context break until the user\n * reselects.\n *\n * The rule is **fill, never override**: copy a store id into ActiveContext only\n * when ActiveContext has no value for that axis yet. We must NOT overwrite a\n * non-null ActiveContext value, because:\n * 1. it would clobber an explicit selection — a switch handler's write, or a\n * deep-link / browser-back `?workspace=`/`?project=` that the router already\n * applied; and\n * 2. overriding is exactly what turns this into a two-store ping-pong — the\n * store→AC and AC→store effects each read the other's pre-commit value and\n * swap the ids every commit until React throws \"Maximum update depth\n * exceeded\" (error #185). Filling only nulls is self-terminating: once the\n * axis is set the guard is false forever, so it can never alternate.\n *\n * Cross-context safety: a value is filled only when it provably belongs to the\n * parent context being mirrored, validated against the loaded store metadata —\n * the **workspace** must belong to the target organization (`storeWorkspaces`),\n * and the **project** must belong to the target workspace (`storeProjects`).\n * The store can transiently hold a stale child id after its parent advanced\n * (e.g. `useContextSwitcherData` copies a new org into the store before the new\n * org's — unpersisted — workspace list loads, leaving `currentWorkspaceId`\n * pointing at the previous org; or the workspace advances to B while the\n * project still points at A). Gating on loaded metadata means such a stale id\n * isn't in the list yet, so it is not mirrored until it is genuinely valid.\n *\n * See BOFF-2805.\n */\nexport function useActiveContextBootstrapMirror({\n storeOrganizationId,\n storeWorkspaceId,\n storeProjectId,\n activeOrganizationId,\n activeWorkspaceId,\n activeProjectId,\n storeWorkspaces,\n storeProjects,\n setOrganization,\n setWorkspace,\n setProject,\n}: ActiveContextBootstrapMirrorArgs): void {\n useEffect(() => {\n if (storeOrganizationId && !activeOrganizationId) {\n setOrganization(storeOrganizationId);\n }\n\n // Fill the workspace only when it provably belongs to the organization\n // ActiveContext is on (or is being filled this pass). Guards against\n // publishing a (org, workspace) pair from two different orgs while the\n // store's org has advanced but its (unpersisted) workspace list / current\n // workspace id has not caught up.\n const targetOrganizationId = activeOrganizationId || storeOrganizationId;\n const workspaceBelongsToTargetOrganization =\n !!storeWorkspaceId &&\n !!targetOrganizationId &&\n !!storeWorkspaces?.some(\n (workspace) =>\n workspace.id === storeWorkspaceId &&\n workspace.organizationId === targetOrganizationId,\n );\n\n if (\n storeWorkspaceId &&\n !activeWorkspaceId &&\n workspaceBelongsToTargetOrganization\n ) {\n setWorkspace(storeWorkspaceId);\n }\n\n // Fill the project only when it provably belongs to the workspace\n // ActiveContext is on (or is being filled this pass). Prevents a stale\n // project from a previously-selected workspace leaking across a switch.\n // Fall back to the store workspace only when it is being filled this pass;\n // otherwise (e.g. workspace fill skipped because its metadata hasn't\n // loaded) we'd let a project fill without its parent workspace present,\n // an inconsistent (org, null, project) state.\n const targetWorkspaceId =\n activeWorkspaceId ||\n (workspaceBelongsToTargetOrganization ? storeWorkspaceId : null);\n const projectBelongsToTargetWorkspace =\n !!storeProjectId &&\n !!targetWorkspaceId &&\n !!storeProjects?.some(\n (project) =>\n project.id === storeProjectId &&\n project.workspaceId === targetWorkspaceId,\n );\n\n if (storeProjectId && !activeProjectId && projectBelongsToTargetWorkspace) {\n setProject(storeProjectId);\n }\n }, [\n storeOrganizationId,\n storeWorkspaceId,\n storeProjectId,\n activeOrganizationId,\n activeWorkspaceId,\n activeProjectId,\n storeWorkspaces,\n storeProjects,\n setOrganization,\n setWorkspace,\n setProject,\n ]);\n}\n"],"mappings":";;AA+FA,SAAgB,EAAgC,EAC9C,wBACA,qBACA,mBACA,yBACA,sBACA,oBACA,oBACA,kBACA,oBACA,iBACA,iBACyC;CACzC,QAAgB;EACd,AAAI,KAAuB,CAAC,KAC1B,EAAgB,CAAmB;EAQrC,IAAM,IAAuB,KAAwB,GAC/C,IACJ,CAAC,CAAC,KACF,CAAC,CAAC,KACF,CAAC,CAAC,GAAiB,MAChB,MACC,EAAU,OAAO,KACjB,EAAU,mBAAmB,CACjC;EAEF,AACE,KACA,CAAC,KACD,KAEA,EAAa,CAAgB;EAU/B,IAAM,IACJ,MACC,IAAuC,IAAmB,OACvD,IACJ,CAAC,CAAC,KACF,CAAC,CAAC,KACF,CAAC,CAAC,GAAe,MACd,MACC,EAAQ,OAAO,KACf,EAAQ,gBAAgB,CAC5B;EAEF,AAAI,KAAkB,CAAC,KAAmB,KACxC,EAAW,CAAc;CAE7B,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;AACH"}
@@ -1,5 +1,4 @@
1
1
  import { GenerateAPIKeyInput, APIKeyResult, APIKeyRotateResult, AuthResult, ApiKey } from '../types';
2
-
3
2
  interface MyApiKeysResponse {
4
3
  myApiKeys: {
5
4
  apiKeys: ApiKey[];
@@ -15,7 +14,7 @@ export declare function useApiKeyOperations(): {
15
14
  revokeAPIKey: (keyId: string) => Promise<boolean>;
16
15
  rotateAPIKey: (keyId: string) => Promise<APIKeyRotateResult>;
17
16
  authenticateAPIKey: (apiKey: string, apiSecret: string) => Promise<AuthResult>;
18
- refetchApiKeys: () => Promise<void> | import("@apollo/client").ObservableQuery.ResultPromise<{
17
+ refetchApiKeys: () => Promise<void> | import('@apollo/client').ObservableQuery.ResultPromise<{
19
18
  data: MyApiKeysResponse | undefined;
20
19
  error?: import('@apollo/client').ErrorLike;
21
20
  }>;
@@ -1,5 +1,4 @@
1
1
  import { App, AppCreateResponse, CreateAppInput, UpdateAppInput, AppTokenResponse, AppAsUserTokenResponse } from '../types';
2
-
3
2
  interface CurrentUserAppsResponse {
4
3
  currentUserApps: {
5
4
  apps: App[];
@@ -12,7 +11,7 @@ interface CurrentUserAppsResponse {
12
11
  */
13
12
  export declare function useAppOperations(): {
14
13
  getApp: (id: string) => Promise<App | null>;
15
- refetchApps: () => Promise<void> | import("@apollo/client").ObservableQuery.ResultPromise<{
14
+ refetchApps: () => Promise<void> | import('@apollo/client').ObservableQuery.ResultPromise<{
16
15
  data: CurrentUserAppsResponse | undefined;
17
16
  error?: import('@apollo/client').ErrorLike;
18
17
  }>;
@@ -1,5 +1,4 @@
1
1
  import { SignInInput, SignUpInput, User, AuthPayload, AuthResult } from '../types';
2
-
3
2
  interface CurrentUserResponse {
4
3
  currentUser: User | null;
5
4
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthOperations.js","names":[],"sources":["../../../src/auth/hooks/useAuthOperations.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useMutation, useQuery, useApolloClient } from \"@apollo/client/react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport {\n loginProfile,\n getProfiles,\n getProfileTokens,\n getProfileUser,\n type ProfileUser,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport {\n SignInDocument,\n SignUpDocument,\n SignOutDocument,\n RefreshTokenDocument,\n CurrentUserDocument,\n VerifyEmailDocument,\n ResendVerificationEmailDocument,\n} from \"../../generated/global-operations\";\nimport type {\n SignInInput,\n SignUpInput,\n User,\n AuthPayload,\n AuthResult,\n} from \"../types\";\n\n// =============================================================================\n// ERROR MAPPING HELPERS\n// =============================================================================\n\n/**\n * Map raw backend error messages to user-friendly descriptions.\n * Keeps a single source of truth for all sign-up error copy.\n */\nfunction mapSignUpError(rawMessage: string, errorCode?: string): string {\n // Non-active accounts on this email — route the user to the right recovery\n // path. RegisterPage reads the errorCode to render the matching CTA.\n if (errorCode === \"ACCOUNT_DEACTIVATED\") {\n return \"This email belongs to a deactivated account. You can reactivate it instead of signing up again.\";\n }\n if (errorCode === \"ACCOUNT_REMOVED\") {\n return \"This account was removed by an administrator. Please contact support to restore it.\";\n }\n if (errorCode === \"ACCOUNT_SUSPENDED\") {\n return \"This account is suspended. Please contact support.\";\n }\n\n // Exact matches from the backend\n if (\n rawMessage.includes(\"email or username already exists\") ||\n errorCode === \"BAD_USER_INPUT\"\n ) {\n return \"An account with this email or username already exists. Please sign in or use a different email.\";\n }\n\n if (errorCode === \"TENANT_CREATION_FAILED\") {\n return \"We couldn't finish setting up your account. Please try again in a few moments. If this persists, contact support.\";\n }\n\n // Network / fetch failures (Apollo wraps these)\n if (\n rawMessage.includes(\"Failed to fetch\") ||\n rawMessage.includes(\"NetworkError\") ||\n rawMessage.includes(\"Load failed\")\n ) {\n return \"Unable to reach the server. Please check your internet connection and try again.\";\n }\n\n // CORS or cross-origin issues\n if (rawMessage.includes(\"CORS\") || rawMessage.includes(\"cross-origin\")) {\n return \"Unable to reach the server. Please try again or contact support.\";\n }\n\n // Generic server errors\n if (errorCode === \"INTERNAL_SERVER_ERROR\") {\n return \"Something went wrong on our end. Please try again in a few moments.\";\n }\n\n // Pass through any already-descriptive message, or fall back\n if (rawMessage && rawMessage !== \"Sign up failed\") {\n return rawMessage;\n }\n\n return \"Sign up failed. Please try again.\";\n}\n\nfunction setShellSsoCookie(userId: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({\n id: userId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n // Best effort only.\n }\n}\n\nfunction toProfileUser(user: User): ProfileUser {\n return { ...user };\n}\n\ninterface GraphQLErrorLike {\n message?: string;\n extensions?: Record<string, unknown>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction asGraphQLErrorArray(value: unknown): GraphQLErrorLike[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is GraphQLErrorLike => isRecord(item));\n}\n\nfunction getGraphQLErrors(error: unknown): GraphQLErrorLike[] {\n if (!isRecord(error)) return [];\n const directErrors = asGraphQLErrorArray(error.errors);\n if (directErrors.length > 0) return directErrors;\n return asGraphQLErrorArray(error.graphQLErrors);\n}\n\nfunction getUnknownErrorMessage(error: unknown): string | undefined {\n if (error instanceof Error) return error.message;\n if (isRecord(error) && typeof error.message === \"string\") {\n return error.message;\n }\n return undefined;\n}\n\nfunction getGraphQLErrorCode(\n error: GraphQLErrorLike | undefined,\n): string | null {\n const code = error?.extensions?.code;\n return typeof code === \"string\" ? code : null;\n}\n\n// =============================================================================\n// GRAPHQL RESPONSE INTERFACES (Apollo Client 4.x)\n// =============================================================================\n\ninterface SignInResponse {\n signIn: AuthPayload;\n}\n\ninterface SignInVariables {\n input: SignInInput;\n}\n\ninterface SignUpResponse {\n signUp: AuthPayload;\n}\n\ninterface SignUpVariables {\n input: SignUpInput;\n}\n\ninterface SignOutResponse {\n signOut: boolean;\n}\n\ninterface RefreshTokenResponse {\n refreshToken: AuthPayload;\n}\n\ninterface RefreshTokenVariables {\n token: string;\n}\n\ninterface CurrentUserResponse {\n currentUser: User | null;\n}\n\ninterface UseAuthOperationsOptions {\n disableCurrentUserQuery?: boolean;\n}\n\ninterface VerifyEmailResponse {\n verifyEmail: {\n success: boolean;\n message: string;\n };\n}\n\ninterface VerifyEmailVariables {\n token: string;\n}\n\ninterface ResendVerificationEmailResponse {\n resendVerificationEmail: {\n success: boolean;\n message: string;\n };\n}\n\ninterface ResendVerificationEmailVariables {\n email: string;\n}\n\n// GraphQL Operations\n\n// SignIn mutation with MFA support\n\n/**\n * Hook for authentication operations\n * Uses Zustand v5 pattern: selectors for state, getState() for actions\n */\nexport function useAuthOperations(options: UseAuthOperationsOptions = {}) {\n // Use selectors for reactive state values\n const isLoading = useAuthStore((state) => state.isLoading);\n const error = useAuthStore((state) => state.error);\n const errorCode = useAuthStore((state) => state.errorCode);\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const user = useAuthStore((state) => state.user);\n const accessToken = useAuthStore((state) => state.accessToken);\n const disableCurrentUserQuery = options.disableCurrentUserQuery ?? false;\n\n // Track whether signIn just completed — skip the CurrentUser refetch\n // because signIn already returns the full user object.\n const loginJustCompletedRef = useRef(false);\n\n const context = useAuth();\n const eventBus = useEventBus();\n const apolloClient = useApolloClient();\n const [signInMutation] = useMutation<SignInResponse, SignInVariables>(\n SignInDocument,\n );\n const [signUpMutation] = useMutation<SignUpResponse, SignUpVariables>(\n SignUpDocument,\n );\n const [signOutMutation] = useMutation<SignOutResponse>(SignOutDocument);\n const [refreshTokenMutation] = useMutation<\n RefreshTokenResponse,\n RefreshTokenVariables\n >(RefreshTokenDocument);\n const [verifyEmailMutation] = useMutation<\n VerifyEmailResponse,\n VerifyEmailVariables\n >(VerifyEmailDocument);\n const [resendVerificationMutation] = useMutation<\n ResendVerificationEmailResponse,\n ResendVerificationEmailVariables\n >(ResendVerificationEmailDocument);\n\n /**\n * Sign in with email/username and password\n * Uses signIn mutation which returns correct token format (with 'sub' field)\n */\n const signIn = async (input: SignInInput): Promise<AuthResult> => {\n const store = useAuthStore.getState();\n\n try {\n store.setLoading(true);\n store.setError(null);\n\n // rememberMe is sent to the backend so the server can issue\n // longer-lived refresh tokens (30 days instead of 7 days)\n\n // Use signIn mutation (returns correct token format with 'sub' field)\n const result = await signInMutation({\n variables: { input },\n // Override client-level errorPolicy to ensure errors are thrown\n // and caught by our catch block for proper error message extraction\n errorPolicy: \"none\",\n });\n\n // If we reach here, the mutation succeeded (no errors thrown)\n // Check if data.signIn is present\n if (!result.data || !result.data.signIn) {\n const errorMessage =\n \"Sign in failed. Please check your credentials and try again.\";\n store.setError(errorMessage);\n return {\n success: false,\n error: errorMessage,\n } as AuthResult;\n }\n\n const data = result.data;\n\n const authResult = data.signIn;\n\n // Check if MFA is required\n if (authResult.requiresMFA && authResult.mfaChallengeId) {\n store.setMfaRequired(true, authResult.mfaChallengeId);\n\n // Return success but with requiresMFA flag\n return {\n success: true,\n requiresMFA: true,\n mfaChallengeId: authResult.mfaChallengeId,\n metadata: {\n mfaMethod: authResult.mfaMethod,\n },\n } as AuthResult;\n }\n\n // Normal login without MFA - store the authentication result\n if (\n authResult.user &&\n authResult.accessToken &&\n authResult.refreshToken\n ) {\n // Signal that user data is fresh from signIn — skip CurrentUser refetch.\n loginJustCompletedRef.current = true;\n store.setUser(authResult.user);\n store.setAuthTokens(authResult.accessToken, authResult.refreshToken);\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n const expiresAt = authResult.expiresIn\n ? Date.now() + authResult.expiresIn * 1000\n : undefined;\n loginProfile(toProfileUser(authResult.user), {\n accessToken: authResult.accessToken,\n refreshToken: authResult.refreshToken,\n expiresAt,\n });\n setShellSsoCookie(authResult.user.id);\n\n eventBus.emit(\n \"auth.signed_in\",\n {\n userId: authResult.user.id,\n tenantId: authResult.user.tenantId,\n workspaceId: authResult.user.defaultWorkspaceId,\n mfaEnabled: authResult.user.mfaEnabled,\n emailVerified: authResult.user.emailVerified,\n },\n { component: \"useAuthOperations\" },\n );\n\n context.onAuthSuccess?.(\n authResult.user,\n authResult.accessToken,\n authResult.refreshToken,\n );\n\n // Convert AuthPayload to AuthResult for consistent return type\n return {\n success: true,\n user: authResult.user,\n token: authResult.accessToken,\n refreshToken: authResult.refreshToken,\n expiresIn: authResult.expiresIn,\n requiresMFA: false,\n } as AuthResult;\n } else {\n const errorMsg = \"Login failed: Invalid response from server\";\n store.setError(errorMsg);\n return {\n success: false,\n error: errorMsg,\n } as AuthResult;\n }\n } catch (err: unknown) {\n // Extract error message and code from GraphQL error responses.\n // The backend sends user-friendly messages in gqlError.message and\n // structured error codes in gqlError.extensions.code (e.g., ACCOUNT_DELETED,\n // ACCOUNT_SUSPENDED, ACCOUNT_INACTIVE, OAUTH_ONLY_ACCOUNT, EMAIL_NOT_VERIFIED,\n // UNAUTHENTICATED, INTERNAL_SERVER_ERROR).\n // IMPORTANT: Never use gqlError.extensions.originalError — it may contain\n // internal details that should not be shown to users.\n let message = \"Sign in failed\";\n let errorCode: string | null = null;\n const graphQLErrors = getGraphQLErrors(err);\n if (graphQLErrors.length > 0) {\n // Apollo v4 CombinedGraphQLErrors\n // Apollo v3 ApolloError — use message only, never originalError\n const gqlError = graphQLErrors[0];\n message = gqlError.message || message;\n errorCode = getGraphQLErrorCode(gqlError);\n } else {\n message = getUnknownErrorMessage(err) ?? message;\n }\n\n console.error(\n \"[SignIn] Error:\",\n message,\n errorCode ? `(${errorCode})` : \"\",\n );\n store.setError(message, errorCode);\n return {\n success: false,\n error: message,\n } as AuthResult;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Sign up new user\n */\n const signUp = async (input: SignUpInput) => {\n const store = useAuthStore.getState();\n\n try {\n store.setLoading(true);\n store.setError(null);\n\n const mutationResult = await signUpMutation({\n variables: { input },\n });\n\n // With errorPolicy: 'all', errors are returned in result, not thrown\n // Check for GraphQL errors first\n const graphQLErrors = getGraphQLErrors(mutationResult);\n if (graphQLErrors.length > 0) {\n const errorMessage = graphQLErrors[0].message ?? \"Sign up failed\";\n console.error(\"[SignUp] GraphQL error:\", errorMessage);\n store.setError(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Check if data.signUp is null (indicates GraphQL error without explicit error)\n if (!mutationResult.data || !mutationResult.data.signUp) {\n const errorMessage = \"Sign up failed: No response from server\";\n console.error(\"[SignUp] No data:\", errorMessage);\n store.setError(errorMessage);\n throw new Error(errorMessage);\n }\n\n const result = mutationResult.data.signUp;\n\n // Check if user was created\n if (!result.user) {\n throw new Error(\"Sign up failed: No user data returned\");\n }\n\n // Check if email verification is required (empty tokens)\n if (\n !result.accessToken ||\n !result.refreshToken ||\n result.accessToken === \"\" ||\n result.refreshToken === \"\"\n ) {\n // User created successfully but needs to verify email\n // Don't set authenticated state, but store the user for display\n store.setUser(result.user);\n store.setAuthenticated(false);\n\n // Return special result indicating email verification needed\n return {\n ...result,\n requiresEmailVerification: true,\n };\n }\n\n // Normal sign up with immediate access (tokens provided)\n store.setUser(result.user);\n store.setAuthTokens(result.accessToken, result.refreshToken);\n store.setAuthenticated(true);\n\n eventBus.emit(\n \"auth.signed_up\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"useAuthOperations\" },\n );\n\n context.onAuthSuccess?.(\n result.user,\n result.accessToken,\n result.refreshToken,\n );\n\n return result;\n } catch (err: unknown) {\n // Extract error message from Apollo error or regular error\n let message = \"Sign up failed. Please try again.\";\n let errorCode: string | undefined;\n\n const graphQLErrors = getGraphQLErrors(err);\n if (graphQLErrors.length > 0) {\n const gqlError = graphQLErrors[0];\n errorCode = getGraphQLErrorCode(gqlError) ?? undefined;\n const rawMessage = gqlError.message ?? \"\";\n\n // Map backend error codes to user-friendly messages\n message = mapSignUpError(rawMessage, errorCode);\n } else {\n const rawMessage = getUnknownErrorMessage(err);\n if (rawMessage) {\n message = mapSignUpError(rawMessage);\n }\n }\n\n store.setError(message, errorCode ?? null);\n throw new Error(message);\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Sign out current session\n */\n const signOut = async () => {\n const store = useAuthStore.getState();\n const signedOutUser = store.user;\n\n try {\n await signOutMutation();\n } catch (err) {\n console.error(\"Sign out failed:\", err);\n }\n\n // Always clear Apollo cache so stale user data doesn't survive re-login\n await apolloClient.clearStore();\n store.clearAuth();\n eventBus.emit(\n \"auth.signed_out\",\n {\n userId: signedOutUser?.id,\n tenantId: signedOutUser?.tenantId,\n workspaceId: signedOutUser?.defaultWorkspaceId,\n },\n { component: \"useAuthOperations\" },\n );\n\n // If other profiles exist after clearing the current one, switch to the first\n // remaining profile instead of fully logging out.\n const remainingProfiles = getProfiles().filter(\n (p) => p.id !== signedOutUser?.id,\n );\n if (remainingProfiles.length > 0) {\n const nextProfile = remainingProfiles[0];\n const nextTokens = getProfileTokens(nextProfile.id);\n const nextUser = getProfileUser(nextProfile.id);\n if (nextTokens?.accessToken && nextUser) {\n // Switch to next profile instead of fully logging out\n store.loadFromProfile(nextProfile.id);\n return;\n }\n }\n\n context.onLogout?.();\n };\n\n /**\n * Refresh access token\n */\n const refreshToken = async () => {\n const store = useAuthStore.getState();\n\n try {\n const token = store.refreshToken;\n if (!token) throw new Error(\"No refresh token available\");\n\n const { data } = await refreshTokenMutation({\n variables: { token },\n });\n\n const result = data!.refreshToken;\n\n // Refresh token should always return valid tokens and user\n if (!result.accessToken || !result.refreshToken || !result.user) {\n throw new Error(\"Token refresh failed: Invalid response from server\");\n }\n\n store.setAuthTokens(result.accessToken, result.refreshToken);\n store.setUser(result.user);\n\n eventBus.emit(\n \"auth.token_refreshed\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"useAuthOperations\" },\n );\n\n return result;\n } catch (err) {\n // Refresh failed, logout user\n eventBus.emit(\n \"auth.token_refresh_failed\",\n {\n userId: store.user?.id,\n tenantId: store.user?.tenantId,\n workspaceId: store.user?.defaultWorkspaceId,\n reason: err instanceof Error ? err.message : \"Token refresh failed\",\n },\n { component: \"useAuthOperations\" },\n );\n store.clearAuth();\n throw err;\n }\n };\n\n /**\n * Get current user.\n * Skip when:\n * - Not authenticated\n * - Login just completed (user data is already fresh from signIn response)\n *\n * The loginJustCompletedRef is cleared after the first successful fetch\n * or on the next render cycle after navigation.\n */\n const { data: currentUserData, refetch: refetchCurrentUser } =\n useQuery<CurrentUserResponse>(CurrentUserDocument, {\n skip:\n disableCurrentUserQuery ||\n !isAuthenticated ||\n loginJustCompletedRef.current,\n fetchPolicy: \"network-only\",\n });\n\n // Handle currentUser data updates (replaces onCompleted callback)\n useEffect(() => {\n if (currentUserData?.currentUser) {\n useAuthStore.getState().setUser(currentUserData.currentUser);\n }\n }, [currentUserData]);\n\n // Clear loginJustCompletedRef after navigation away from login page\n // so subsequent CurrentUser queries work normally (e.g., profile page).\n useEffect(() => {\n if (loginJustCompletedRef.current && isAuthenticated) {\n const timer = setTimeout(() => {\n loginJustCompletedRef.current = false;\n }, 2000);\n return () => clearTimeout(timer);\n }\n }, [isAuthenticated]);\n\n /**\n * Verify email with token\n * Returns { success, message, email } on success\n */\n const verifyEmail = async (\n token: string,\n ): Promise<{ success: boolean; message: string; email?: string }> => {\n const store = useAuthStore.getState();\n\n try {\n store.setLoading(true);\n const { data } = await verifyEmailMutation({ variables: { token } });\n\n if (data!.verifyEmail.success && store.user) {\n store.setUser({ ...store.user, emailVerified: true });\n }\n\n return data!.verifyEmail;\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Email verification failed\";\n store.setError(message);\n throw err;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Resend verification email\n */\n const resendVerification = async (email: string) => {\n try {\n const { data } = await resendVerificationMutation({\n variables: { email },\n });\n return data!.resendVerificationEmail;\n } catch (err) {\n console.error(\"Failed to resend verification email:\", err);\n throw err;\n }\n };\n\n // Legacy aliases for backwards compatibility\n const login = signIn;\n const register = signUp;\n const logout = signOut;\n\n return {\n // New API\n signIn,\n signUp,\n signOut,\n refreshToken,\n verifyEmail,\n resendVerification,\n refetchCurrentUser,\n // Legacy aliases\n login,\n register,\n logout,\n // State (reactive via selectors)\n isLoading,\n error,\n errorCode,\n isAuthenticated,\n user,\n accessToken,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useAuthOperations.js","names":[],"sources":["../../../src/auth/hooks/useAuthOperations.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useMutation, useQuery, useApolloClient } from \"@apollo/client/react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport {\n loginProfile,\n getProfiles,\n getProfileTokens,\n getProfileUser,\n type ProfileUser,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport {\n SignInDocument,\n SignUpDocument,\n SignOutDocument,\n RefreshTokenDocument,\n CurrentUserDocument,\n VerifyEmailDocument,\n ResendVerificationEmailDocument,\n} from \"../../generated/global-operations\";\nimport type {\n SignInInput,\n SignUpInput,\n User,\n AuthPayload,\n AuthResult,\n} from \"../types\";\n\n// =============================================================================\n// ERROR MAPPING HELPERS\n// =============================================================================\n\n/**\n * Map raw backend error messages to user-friendly descriptions.\n * Keeps a single source of truth for all sign-up error copy.\n */\nfunction mapSignUpError(rawMessage: string, errorCode?: string): string {\n // Non-active accounts on this email — route the user to the right recovery\n // path. RegisterPage reads the errorCode to render the matching CTA.\n if (errorCode === \"ACCOUNT_DEACTIVATED\") {\n return \"This email belongs to a deactivated account. You can reactivate it instead of signing up again.\";\n }\n if (errorCode === \"ACCOUNT_REMOVED\") {\n return \"This account was removed by an administrator. Please contact support to restore it.\";\n }\n if (errorCode === \"ACCOUNT_SUSPENDED\") {\n return \"This account is suspended. Please contact support.\";\n }\n\n // Exact matches from the backend\n if (\n rawMessage.includes(\"email or username already exists\") ||\n errorCode === \"BAD_USER_INPUT\"\n ) {\n return \"An account with this email or username already exists. Please sign in or use a different email.\";\n }\n\n if (errorCode === \"TENANT_CREATION_FAILED\") {\n return \"We couldn't finish setting up your account. Please try again in a few moments. If this persists, contact support.\";\n }\n\n // Network / fetch failures (Apollo wraps these)\n if (\n rawMessage.includes(\"Failed to fetch\") ||\n rawMessage.includes(\"NetworkError\") ||\n rawMessage.includes(\"Load failed\")\n ) {\n return \"Unable to reach the server. Please check your internet connection and try again.\";\n }\n\n // CORS or cross-origin issues\n if (rawMessage.includes(\"CORS\") || rawMessage.includes(\"cross-origin\")) {\n return \"Unable to reach the server. Please try again or contact support.\";\n }\n\n // Generic server errors\n if (errorCode === \"INTERNAL_SERVER_ERROR\") {\n return \"Something went wrong on our end. Please try again in a few moments.\";\n }\n\n // Pass through any already-descriptive message, or fall back\n if (rawMessage && rawMessage !== \"Sign up failed\") {\n return rawMessage;\n }\n\n return \"Sign up failed. Please try again.\";\n}\n\nfunction setShellSsoCookie(userId: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({\n id: userId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n // Best effort only.\n }\n}\n\nfunction toProfileUser(user: User): ProfileUser {\n return { ...user };\n}\n\ninterface GraphQLErrorLike {\n message?: string;\n extensions?: Record<string, unknown>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction asGraphQLErrorArray(value: unknown): GraphQLErrorLike[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is GraphQLErrorLike => isRecord(item));\n}\n\nfunction getGraphQLErrors(error: unknown): GraphQLErrorLike[] {\n if (!isRecord(error)) return [];\n const directErrors = asGraphQLErrorArray(error.errors);\n if (directErrors.length > 0) return directErrors;\n return asGraphQLErrorArray(error.graphQLErrors);\n}\n\nfunction getUnknownErrorMessage(error: unknown): string | undefined {\n if (error instanceof Error) return error.message;\n if (isRecord(error) && typeof error.message === \"string\") {\n return error.message;\n }\n return undefined;\n}\n\nfunction getGraphQLErrorCode(\n error: GraphQLErrorLike | undefined,\n): string | null {\n const code = error?.extensions?.code;\n return typeof code === \"string\" ? code : null;\n}\n\n// =============================================================================\n// GRAPHQL RESPONSE INTERFACES (Apollo Client 4.x)\n// =============================================================================\n\ninterface SignInResponse {\n signIn: AuthPayload;\n}\n\ninterface SignInVariables {\n input: SignInInput;\n}\n\ninterface SignUpResponse {\n signUp: AuthPayload;\n}\n\ninterface SignUpVariables {\n input: SignUpInput;\n}\n\ninterface SignOutResponse {\n signOut: boolean;\n}\n\ninterface RefreshTokenResponse {\n refreshToken: AuthPayload;\n}\n\ninterface RefreshTokenVariables {\n token: string;\n}\n\ninterface CurrentUserResponse {\n currentUser: User | null;\n}\n\ninterface UseAuthOperationsOptions {\n disableCurrentUserQuery?: boolean;\n}\n\ninterface VerifyEmailResponse {\n verifyEmail: {\n success: boolean;\n message: string;\n };\n}\n\ninterface VerifyEmailVariables {\n token: string;\n}\n\ninterface ResendVerificationEmailResponse {\n resendVerificationEmail: {\n success: boolean;\n message: string;\n };\n}\n\ninterface ResendVerificationEmailVariables {\n email: string;\n}\n\n// GraphQL Operations\n\n// SignIn mutation with MFA support\n\n/**\n * Hook for authentication operations\n * Uses Zustand v5 pattern: selectors for state, getState() for actions\n */\nexport function useAuthOperations(options: UseAuthOperationsOptions = {}) {\n // Use selectors for reactive state values\n const isLoading = useAuthStore((state) => state.isLoading);\n const error = useAuthStore((state) => state.error);\n const errorCode = useAuthStore((state) => state.errorCode);\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const user = useAuthStore((state) => state.user);\n const accessToken = useAuthStore((state) => state.accessToken);\n const disableCurrentUserQuery = options.disableCurrentUserQuery ?? false;\n\n // Track whether signIn just completed — skip the CurrentUser refetch\n // because signIn already returns the full user object.\n const loginJustCompletedRef = useRef(false);\n\n const context = useAuth();\n const eventBus = useEventBus();\n const apolloClient = useApolloClient();\n const [signInMutation] = useMutation<SignInResponse, SignInVariables>(\n SignInDocument,\n );\n const [signUpMutation] = useMutation<SignUpResponse, SignUpVariables>(\n SignUpDocument,\n );\n const [signOutMutation] = useMutation<SignOutResponse>(SignOutDocument);\n const [refreshTokenMutation] = useMutation<\n RefreshTokenResponse,\n RefreshTokenVariables\n >(RefreshTokenDocument);\n const [verifyEmailMutation] = useMutation<\n VerifyEmailResponse,\n VerifyEmailVariables\n >(VerifyEmailDocument);\n const [resendVerificationMutation] = useMutation<\n ResendVerificationEmailResponse,\n ResendVerificationEmailVariables\n >(ResendVerificationEmailDocument);\n\n /**\n * Sign in with email/username and password\n * Uses signIn mutation which returns correct token format (with 'sub' field)\n */\n const signIn = async (input: SignInInput): Promise<AuthResult> => {\n const store = useAuthStore.getState();\n\n try {\n store.setLoading(true);\n store.setError(null);\n\n // rememberMe is sent to the backend so the server can issue\n // longer-lived refresh tokens (30 days instead of 7 days)\n\n // Use signIn mutation (returns correct token format with 'sub' field)\n const result = await signInMutation({\n variables: { input },\n // Override client-level errorPolicy to ensure errors are thrown\n // and caught by our catch block for proper error message extraction\n errorPolicy: \"none\",\n });\n\n // If we reach here, the mutation succeeded (no errors thrown)\n // Check if data.signIn is present\n if (!result.data || !result.data.signIn) {\n const errorMessage =\n \"Sign in failed. Please check your credentials and try again.\";\n store.setError(errorMessage);\n return {\n success: false,\n error: errorMessage,\n } as AuthResult;\n }\n\n const data = result.data;\n\n const authResult = data.signIn;\n\n // Check if MFA is required\n if (authResult.requiresMFA && authResult.mfaChallengeId) {\n store.setMfaRequired(true, authResult.mfaChallengeId);\n\n // Return success but with requiresMFA flag\n return {\n success: true,\n requiresMFA: true,\n mfaChallengeId: authResult.mfaChallengeId,\n metadata: {\n mfaMethod: authResult.mfaMethod,\n },\n } as AuthResult;\n }\n\n // Normal login without MFA - store the authentication result\n if (\n authResult.user &&\n authResult.accessToken &&\n authResult.refreshToken\n ) {\n // Signal that user data is fresh from signIn — skip CurrentUser refetch.\n loginJustCompletedRef.current = true;\n store.setUser(authResult.user);\n store.setAuthTokens(authResult.accessToken, authResult.refreshToken);\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n const expiresAt = authResult.expiresIn\n ? Date.now() + authResult.expiresIn * 1000\n : undefined;\n loginProfile(toProfileUser(authResult.user), {\n accessToken: authResult.accessToken,\n refreshToken: authResult.refreshToken,\n expiresAt,\n });\n setShellSsoCookie(authResult.user.id);\n\n eventBus.emit(\n \"auth.signed_in\",\n {\n userId: authResult.user.id,\n tenantId: authResult.user.tenantId,\n workspaceId: authResult.user.defaultWorkspaceId,\n mfaEnabled: authResult.user.mfaEnabled,\n emailVerified: authResult.user.emailVerified,\n },\n { component: \"useAuthOperations\" },\n );\n\n context.onAuthSuccess?.(\n authResult.user,\n authResult.accessToken,\n authResult.refreshToken,\n );\n\n // Convert AuthPayload to AuthResult for consistent return type\n return {\n success: true,\n user: authResult.user,\n token: authResult.accessToken,\n refreshToken: authResult.refreshToken,\n expiresIn: authResult.expiresIn,\n requiresMFA: false,\n } as AuthResult;\n } else {\n const errorMsg = \"Login failed: Invalid response from server\";\n store.setError(errorMsg);\n return {\n success: false,\n error: errorMsg,\n } as AuthResult;\n }\n } catch (err: unknown) {\n // Extract error message and code from GraphQL error responses.\n // The backend sends user-friendly messages in gqlError.message and\n // structured error codes in gqlError.extensions.code (e.g., ACCOUNT_DELETED,\n // ACCOUNT_SUSPENDED, ACCOUNT_INACTIVE, OAUTH_ONLY_ACCOUNT, EMAIL_NOT_VERIFIED,\n // UNAUTHENTICATED, INTERNAL_SERVER_ERROR).\n // IMPORTANT: Never use gqlError.extensions.originalError — it may contain\n // internal details that should not be shown to users.\n let message = \"Sign in failed\";\n let errorCode: string | null = null;\n const graphQLErrors = getGraphQLErrors(err);\n if (graphQLErrors.length > 0) {\n // Apollo v4 CombinedGraphQLErrors\n // Apollo v3 ApolloError — use message only, never originalError\n const gqlError = graphQLErrors[0];\n message = gqlError.message || message;\n errorCode = getGraphQLErrorCode(gqlError);\n } else {\n message = getUnknownErrorMessage(err) ?? message;\n }\n\n console.error(\n \"[SignIn] Error:\",\n message,\n errorCode ? `(${errorCode})` : \"\",\n );\n store.setError(message, errorCode);\n return {\n success: false,\n error: message,\n } as AuthResult;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Sign up new user\n */\n const signUp = async (input: SignUpInput) => {\n const store = useAuthStore.getState();\n\n try {\n store.setLoading(true);\n store.setError(null);\n\n const mutationResult = await signUpMutation({\n variables: { input },\n });\n\n // With errorPolicy: 'all', errors are returned in result, not thrown\n // Check for GraphQL errors first\n const graphQLErrors = getGraphQLErrors(mutationResult);\n if (graphQLErrors.length > 0) {\n const errorMessage = graphQLErrors[0].message ?? \"Sign up failed\";\n console.error(\"[SignUp] GraphQL error:\", errorMessage);\n store.setError(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Check if data.signUp is null (indicates GraphQL error without explicit error)\n if (!mutationResult.data || !mutationResult.data.signUp) {\n const errorMessage = \"Sign up failed: No response from server\";\n console.error(\"[SignUp] No data:\", errorMessage);\n store.setError(errorMessage);\n throw new Error(errorMessage);\n }\n\n const result = mutationResult.data.signUp;\n\n // Check if user was created\n if (!result.user) {\n throw new Error(\"Sign up failed: No user data returned\");\n }\n\n // Check if email verification is required (empty tokens)\n if (\n !result.accessToken ||\n !result.refreshToken ||\n result.accessToken === \"\" ||\n result.refreshToken === \"\"\n ) {\n // User created successfully but needs to verify email\n // Don't set authenticated state, but store the user for display\n store.setUser(result.user);\n store.setAuthenticated(false);\n\n // Return special result indicating email verification needed\n return {\n ...result,\n requiresEmailVerification: true,\n };\n }\n\n // Normal sign up with immediate access (tokens provided)\n store.setUser(result.user);\n store.setAuthTokens(result.accessToken, result.refreshToken);\n store.setAuthenticated(true);\n\n eventBus.emit(\n \"auth.signed_up\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"useAuthOperations\" },\n );\n\n context.onAuthSuccess?.(\n result.user,\n result.accessToken,\n result.refreshToken,\n );\n\n return result;\n } catch (err: unknown) {\n // Extract error message from Apollo error or regular error\n let message = \"Sign up failed. Please try again.\";\n let errorCode: string | undefined;\n\n const graphQLErrors = getGraphQLErrors(err);\n if (graphQLErrors.length > 0) {\n const gqlError = graphQLErrors[0];\n errorCode = getGraphQLErrorCode(gqlError) ?? undefined;\n const rawMessage = gqlError.message ?? \"\";\n\n // Map backend error codes to user-friendly messages\n message = mapSignUpError(rawMessage, errorCode);\n } else {\n const rawMessage = getUnknownErrorMessage(err);\n if (rawMessage) {\n message = mapSignUpError(rawMessage);\n }\n }\n\n store.setError(message, errorCode ?? null);\n throw new Error(message);\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Sign out current session\n */\n const signOut = async () => {\n const store = useAuthStore.getState();\n const signedOutUser = store.user;\n\n try {\n await signOutMutation();\n } catch (err) {\n console.error(\"Sign out failed:\", err);\n }\n\n // Always clear Apollo cache so stale user data doesn't survive re-login\n await apolloClient.clearStore();\n store.clearAuth();\n eventBus.emit(\n \"auth.signed_out\",\n {\n userId: signedOutUser?.id,\n tenantId: signedOutUser?.tenantId,\n workspaceId: signedOutUser?.defaultWorkspaceId,\n },\n { component: \"useAuthOperations\" },\n );\n\n // If other profiles exist after clearing the current one, switch to the first\n // remaining profile instead of fully logging out.\n const remainingProfiles = getProfiles().filter(\n (p) => p.id !== signedOutUser?.id,\n );\n if (remainingProfiles.length > 0) {\n const nextProfile = remainingProfiles[0];\n const nextTokens = getProfileTokens(nextProfile.id);\n const nextUser = getProfileUser(nextProfile.id);\n if (nextTokens?.accessToken && nextUser) {\n // Switch to next profile instead of fully logging out\n store.loadFromProfile(nextProfile.id);\n return;\n }\n }\n\n context.onLogout?.();\n };\n\n /**\n * Refresh access token\n */\n const refreshToken = async () => {\n const store = useAuthStore.getState();\n\n try {\n const token = store.refreshToken;\n if (!token) throw new Error(\"No refresh token available\");\n\n const { data } = await refreshTokenMutation({\n variables: { token },\n });\n\n const result = data!.refreshToken;\n\n // Refresh token should always return valid tokens and user\n if (!result.accessToken || !result.refreshToken || !result.user) {\n throw new Error(\"Token refresh failed: Invalid response from server\");\n }\n\n store.setAuthTokens(result.accessToken, result.refreshToken);\n store.setUser(result.user);\n\n eventBus.emit(\n \"auth.token_refreshed\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"useAuthOperations\" },\n );\n\n return result;\n } catch (err) {\n // Refresh failed, logout user\n eventBus.emit(\n \"auth.token_refresh_failed\",\n {\n userId: store.user?.id,\n tenantId: store.user?.tenantId,\n workspaceId: store.user?.defaultWorkspaceId,\n reason: err instanceof Error ? err.message : \"Token refresh failed\",\n },\n { component: \"useAuthOperations\" },\n );\n store.clearAuth();\n throw err;\n }\n };\n\n /**\n * Get current user.\n * Skip when:\n * - Not authenticated\n * - Login just completed (user data is already fresh from signIn response)\n *\n * The loginJustCompletedRef is cleared after the first successful fetch\n * or on the next render cycle after navigation.\n */\n const { data: currentUserData, refetch: refetchCurrentUser } =\n useQuery<CurrentUserResponse>(CurrentUserDocument, {\n skip:\n disableCurrentUserQuery ||\n !isAuthenticated ||\n loginJustCompletedRef.current,\n fetchPolicy: \"network-only\",\n });\n\n // Handle currentUser data updates (replaces onCompleted callback)\n useEffect(() => {\n if (currentUserData?.currentUser) {\n useAuthStore.getState().setUser(currentUserData.currentUser);\n }\n }, [currentUserData]);\n\n // Clear loginJustCompletedRef after navigation away from login page\n // so subsequent CurrentUser queries work normally (e.g., profile page).\n useEffect(() => {\n if (loginJustCompletedRef.current && isAuthenticated) {\n const timer = setTimeout(() => {\n loginJustCompletedRef.current = false;\n }, 2000);\n return () => clearTimeout(timer);\n }\n }, [isAuthenticated]);\n\n /**\n * Verify email with token\n * Returns { success, message, email } on success\n */\n const verifyEmail = async (\n token: string,\n ): Promise<{ success: boolean; message: string; email?: string }> => {\n const store = useAuthStore.getState();\n\n try {\n store.setLoading(true);\n const { data } = await verifyEmailMutation({ variables: { token } });\n\n if (data!.verifyEmail.success && store.user) {\n store.setUser({ ...store.user, emailVerified: true });\n }\n\n return data!.verifyEmail;\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Email verification failed\";\n store.setError(message);\n throw err;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Resend verification email\n */\n const resendVerification = async (email: string) => {\n try {\n const { data } = await resendVerificationMutation({\n variables: { email },\n });\n return data!.resendVerificationEmail;\n } catch (err) {\n console.error(\"Failed to resend verification email:\", err);\n throw err;\n }\n };\n\n // Legacy aliases for backwards compatibility\n const login = signIn;\n const register = signUp;\n const logout = signOut;\n\n return {\n // New API\n signIn,\n signUp,\n signOut,\n refreshToken,\n verifyEmail,\n resendVerification,\n refetchCurrentUser,\n // Legacy aliases\n login,\n register,\n logout,\n // State (reactive via selectors)\n isLoading,\n error,\n errorCode,\n isAuthenticated,\n user,\n accessToken,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthPermissions.js","names":[],"sources":["../../../src/auth/hooks/useAuthPermissions.ts"],"sourcesContent":["import { usePermissions } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { AUTH_PERMISSIONS } from \"../constants/permissions\";\n\n/**\n * Hook for auth admin RBAC permissions.\n *\n * Only covers admin-facing operations. Public auth flows and self-service\n * operations don't need RBAC checks — they're either @rbac(public: true)\n * or scoped to the current user's own data.\n */\nexport function useAuthPermissions() {\n const { hasPermission, isLoading } = usePermissions();\n\n return {\n // User management (admin pages)\n canViewUsers: hasPermission(AUTH_PERMISSIONS.USER_READ),\n canUpdateUsers: hasPermission(AUTH_PERMISSIONS.USER_UPDATE),\n canDeleteUsers: hasPermission(AUTH_PERMISSIONS.USER_DELETE),\n\n // OAuth app management\n canViewApps: hasPermission(AUTH_PERMISSIONS.APP_READ),\n canCreateApps: hasPermission(AUTH_PERMISSIONS.APP_CREATE),\n canUpdateApps: hasPermission(AUTH_PERMISSIONS.APP_UPDATE),\n\n // SSO/OIDC configuration\n canManageSsoConfig: hasPermission(AUTH_PERMISSIONS.SSO_CONFIG_MANAGE),\n canManageOidcConfig: hasPermission(AUTH_PERMISSIONS.OIDC_CONFIG_MANAGE),\n\n // Auth method configuration\n canManageAuthMethods: hasPermission(AUTH_PERMISSIONS.AUTH_METHOD_UPDATE),\n\n isLoading,\n };\n}\n"],"mappings":";;;AAUA,SAAgB,IAAqB;CACnC,IAAM,EAAE,kBAAe,iBAAc,GAAgB;AAErD,QAAO;EAEL,cAAc,EAAc,EAAiB,UAAU;EACvD,gBAAgB,EAAc,EAAiB,YAAY;EAC3D,gBAAgB,EAAc,EAAiB,YAAY;EAG3D,aAAa,EAAc,EAAiB,SAAS;EACrD,eAAe,EAAc,EAAiB,WAAW;EACzD,eAAe,EAAc,EAAiB,WAAW;EAGzD,oBAAoB,EAAc,EAAiB,kBAAkB;EACrE,qBAAqB,EAAc,EAAiB,mBAAmB;EAGvE,sBAAsB,EAAc,EAAiB,mBAAmB;EAExE;EACD"}
1
+ {"version":3,"file":"useAuthPermissions.js","names":[],"sources":["../../../src/auth/hooks/useAuthPermissions.ts"],"sourcesContent":["import { usePermissions } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { AUTH_PERMISSIONS } from \"../constants/permissions\";\n\n/**\n * Hook for auth admin RBAC permissions.\n *\n * Only covers admin-facing operations. Public auth flows and self-service\n * operations don't need RBAC checks — they're either @rbac(public: true)\n * or scoped to the current user's own data.\n */\nexport function useAuthPermissions() {\n const { hasPermission, isLoading } = usePermissions();\n\n return {\n // User management (admin pages)\n canViewUsers: hasPermission(AUTH_PERMISSIONS.USER_READ),\n canUpdateUsers: hasPermission(AUTH_PERMISSIONS.USER_UPDATE),\n canDeleteUsers: hasPermission(AUTH_PERMISSIONS.USER_DELETE),\n\n // OAuth app management\n canViewApps: hasPermission(AUTH_PERMISSIONS.APP_READ),\n canCreateApps: hasPermission(AUTH_PERMISSIONS.APP_CREATE),\n canUpdateApps: hasPermission(AUTH_PERMISSIONS.APP_UPDATE),\n\n // SSO/OIDC configuration\n canManageSsoConfig: hasPermission(AUTH_PERMISSIONS.SSO_CONFIG_MANAGE),\n canManageOidcConfig: hasPermission(AUTH_PERMISSIONS.OIDC_CONFIG_MANAGE),\n\n // Auth method configuration\n canManageAuthMethods: hasPermission(AUTH_PERMISSIONS.AUTH_METHOD_UPDATE),\n\n isLoading,\n };\n}\n"],"mappings":";;;AAUA,SAAgB,IAAqB;CACnC,IAAM,EAAE,kBAAe,iBAAc,EAAe;CAEpD,OAAO;EAEL,cAAc,EAAc,EAAiB,SAAS;EACtD,gBAAgB,EAAc,EAAiB,WAAW;EAC1D,gBAAgB,EAAc,EAAiB,WAAW;EAG1D,aAAa,EAAc,EAAiB,QAAQ;EACpD,eAAe,EAAc,EAAiB,UAAU;EACxD,eAAe,EAAc,EAAiB,UAAU;EAGxD,oBAAoB,EAAc,EAAiB,iBAAiB;EACpE,qBAAqB,EAAc,EAAiB,kBAAkB;EAGtE,sBAAsB,EAAc,EAAiB,kBAAkB;EAEvE;CACF;AACF"}
@@ -3,7 +3,7 @@ import { useCallback as n, useEffect as r } from "react";
3
3
  //#region src/auth/hooks/useCaptchaV3.ts
4
4
  function i() {
5
5
  return r(() => {
6
- e() && import("../../node_modules/react-google-recaptcha-v3/dist/react-google-recaptcha-v3.esm.js").catch(() => {
6
+ e() && import("react-google-recaptcha-v3").catch(() => {
7
7
  console.warn("[captcha-v3] Failed to load react-google-recaptcha-v3");
8
8
  });
9
9
  }, []), { executeRecaptcha: n(async (n) => {
@@ -1 +1 @@
1
- {"version":3,"file":"useCaptchaV3.js","names":[],"sources":["../../../src/auth/hooks/useCaptchaV3.ts"],"sourcesContent":["import { useCallback, useEffect } from \"react\";\nimport {\n isCaptchaEnabled,\n requireRecaptchaSiteKey,\n} from \"../utils/captchaConfig\";\n\n/**\n * Hook for reCAPTCHA v3 invisible verification.\n * Returns a no-op when VITE_CAPTCHA_ENABLED is not \"true\".\n *\n * When enabled, dynamically imports `react-google-recaptcha-v3` and uses\n * the `useGoogleReCaptcha` hook from the `GoogleReCaptchaProvider` context.\n * The provider must be mounted above this hook in the component tree\n * (handled by CaptchaProvider in AuthRoot).\n */\nexport function useCaptchaV3() {\n useEffect(() => {\n if (!isCaptchaEnabled()) return;\n\n // Dynamically import react-google-recaptcha-v3 so the provider's global\n // grecaptcha object is initialized. We don't use the module export\n // directly — execute() below reads from window.grecaptcha instead.\n import(\"react-google-recaptcha-v3\").catch(() => {\n console.warn(\"[captcha-v3] Failed to load react-google-recaptcha-v3\");\n });\n }, []);\n\n const execute = useCallback(\n async (action: string): Promise<string | undefined> => {\n if (!isCaptchaEnabled()) {\n return undefined;\n }\n\n const siteKey = requireRecaptchaSiteKey();\n\n // Use the global grecaptcha object that GoogleReCaptchaProvider sets up\n const grecaptcha = (window as unknown as Record<string, unknown>)[\n \"grecaptcha\"\n ] as\n | {\n execute?: (\n siteKey: string,\n options: { action: string },\n ) => Promise<string>;\n }\n | undefined;\n\n if (grecaptcha?.execute) {\n return grecaptcha.execute(siteKey, { action });\n }\n\n console.warn(\"[captcha-v3] grecaptcha not yet available\");\n return undefined;\n },\n [],\n );\n\n return { executeRecaptcha: execute };\n}\n"],"mappings":";;;AAeA,SAAgB,IAAe;AA0C7B,QAzCA,QAAgB;AACT,KAAkB,IAKvB,OAAO,sFAA6B,YAAY;AAC9C,WAAQ,KAAK,wDAAwD;IACrE;IACD,EAAE,CAAC,EAgCC,EAAE,kBA9BO,EACd,OAAO,MAAgD;AACrD,MAAI,CAAC,GAAkB,CACrB;EAGF,IAAM,IAAU,GAAyB,EAGnC,IAAc,OAClB;AAUF,MAAI,GAAY,QACd,QAAO,EAAW,QAAQ,GAAS,EAAE,WAAQ,CAAC;AAGhD,UAAQ,KAAK,4CAA4C;IAG3D,EAAE,CACH,EAEmC"}
1
+ {"version":3,"file":"useCaptchaV3.js","names":[],"sources":["../../../src/auth/hooks/useCaptchaV3.ts"],"sourcesContent":["import { useCallback, useEffect } from \"react\";\nimport {\n isCaptchaEnabled,\n requireRecaptchaSiteKey,\n} from \"../utils/captchaConfig\";\n\n/**\n * Hook for reCAPTCHA v3 invisible verification.\n * Returns a no-op when VITE_CAPTCHA_ENABLED is not \"true\".\n *\n * When enabled, dynamically imports `react-google-recaptcha-v3` and uses\n * the `useGoogleReCaptcha` hook from the `GoogleReCaptchaProvider` context.\n * The provider must be mounted above this hook in the component tree\n * (handled by CaptchaProvider in AuthRoot).\n */\nexport function useCaptchaV3() {\n useEffect(() => {\n if (!isCaptchaEnabled()) return;\n\n // Dynamically import react-google-recaptcha-v3 so the provider's global\n // grecaptcha object is initialized. We don't use the module export\n // directly — execute() below reads from window.grecaptcha instead.\n import(\"react-google-recaptcha-v3\").catch(() => {\n console.warn(\"[captcha-v3] Failed to load react-google-recaptcha-v3\");\n });\n }, []);\n\n const execute = useCallback(\n async (action: string): Promise<string | undefined> => {\n if (!isCaptchaEnabled()) {\n return undefined;\n }\n\n const siteKey = requireRecaptchaSiteKey();\n\n // Use the global grecaptcha object that GoogleReCaptchaProvider sets up\n const grecaptcha = (window as unknown as Record<string, unknown>)[\n \"grecaptcha\"\n ] as\n | {\n execute?: (\n siteKey: string,\n options: { action: string },\n ) => Promise<string>;\n }\n | undefined;\n\n if (grecaptcha?.execute) {\n return grecaptcha.execute(siteKey, { action });\n }\n\n console.warn(\"[captcha-v3] grecaptcha not yet available\");\n return undefined;\n },\n [],\n );\n\n return { executeRecaptcha: execute };\n}\n"],"mappings":";;;AAeA,SAAgB,IAAe;CA0C7B,OAzCA,QAAgB;EACT,EAAiB,KAKtB,OAAO,6BAA6B,YAAY;GAC9C,QAAQ,KAAK,uDAAuD;EACtE,CAAC;CACH,GAAG,CAAC,CAAC,GAgCE,EAAE,kBA9BO,EACd,OAAO,MAAgD;EACrD,IAAI,CAAC,EAAiB,GACpB;EAGF,IAAM,IAAU,EAAwB,GAGlC,IAAc,OAClB;EAUF,IAAI,GAAY,SACd,OAAO,EAAW,QAAQ,GAAS,EAAE,UAAO,CAAC;EAG/C,QAAQ,KAAK,2CAA2C;CAE1D,GACA,CAAC,CAGwB,EAAQ;AACrC"}
@@ -1,5 +1,4 @@
1
1
  import { Organization, Workspace, Project } from '../types';
2
-
3
2
  export interface UseContextSwitcherDataOptions {
4
3
  /**
5
4
  * Optional Global API Gateway base URL override.
@@ -52,6 +51,7 @@ export declare function useContextSwitcherData(options?: UseContextSwitcherDataO
52
51
  error?: string;
53
52
  }>;
54
53
  selectProject: (project: Project) => void;
54
+ selectWorkspaceWide: () => void;
55
55
  };
56
56
  /**
57
57
  * Hook to reactively ensure workspaces are synced with the current organization.