@burdenoff/microfe-auth 2026.1031.1 → 2026.1101.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/auth/AuthRoot.js +14 -11
  2. package/dist/auth/AuthRoot.js.map +1 -1
  3. package/dist/auth/AuthRoutes.js +1 -2
  4. package/dist/auth/AuthRoutes.js.map +1 -1
  5. package/dist/auth/components/AuthIcons.js +1 -2
  6. package/dist/auth/components/AuthIcons.js.map +1 -1
  7. package/dist/auth/components/AuthLayout.js +1 -2
  8. package/dist/auth/components/AuthLayout.js.map +1 -1
  9. package/dist/auth/components/CaptchaV2.js +9 -10
  10. package/dist/auth/components/CaptchaV2.js.map +1 -1
  11. package/dist/auth/components/ConfirmActionDialog.js +1 -2
  12. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  13. package/dist/auth/components/profile/UserProfileMenu.js +7 -6
  14. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  15. package/dist/auth/components/workspace/WorkspaceSelector.js +1 -2
  16. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  17. package/dist/auth/constants/permissions.js +1 -2
  18. package/dist/auth/constants/permissions.js.map +1 -1
  19. package/dist/auth/constants/sessionKeys.js +1 -2
  20. package/dist/auth/constants/sessionKeys.js.map +1 -1
  21. package/dist/auth/hooks/contextDeepLink.js +1 -2
  22. package/dist/auth/hooks/contextDeepLink.js.map +1 -1
  23. package/dist/auth/hooks/index.js +12 -11
  24. package/dist/auth/hooks/useAccountOperations.js +1 -2
  25. package/dist/auth/hooks/useAccountOperations.js.map +1 -1
  26. package/dist/auth/hooks/useActiveContextBootstrapMirror.js +1 -2
  27. package/dist/auth/hooks/useActiveContextBootstrapMirror.js.map +1 -1
  28. package/dist/auth/hooks/useAuthOperations.js +37 -37
  29. package/dist/auth/hooks/useAuthOperations.js.map +1 -1
  30. package/dist/auth/hooks/useAuthPermissions.js +1 -2
  31. package/dist/auth/hooks/useAuthPermissions.js.map +1 -1
  32. package/dist/auth/hooks/useCaptchaV3.js +1 -2
  33. package/dist/auth/hooks/useCaptchaV3.js.map +1 -1
  34. package/dist/auth/hooks/useContextSwitcherData.js +86 -79
  35. package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
  36. package/dist/auth/hooks/useMfaOperations.js +4 -5
  37. package/dist/auth/hooks/useMfaOperations.js.map +1 -1
  38. package/dist/auth/hooks/useMyWorkspaces.js +4 -5
  39. package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
  40. package/dist/auth/hooks/useOAuthOperations.js +11 -12
  41. package/dist/auth/hooks/useOAuthOperations.js.map +1 -1
  42. package/dist/auth/hooks/usePasswordOperations.js +1 -2
  43. package/dist/auth/hooks/usePasswordOperations.js.map +1 -1
  44. package/dist/auth/hooks/useSessionOperations.js +3 -4
  45. package/dist/auth/hooks/useSessionOperations.js.map +1 -1
  46. package/dist/auth/hooks/useSignupCaptcha.js +1 -2
  47. package/dist/auth/hooks/useSignupCaptcha.js.map +1 -1
  48. package/dist/auth/hooks/useSsoOperations.js +80 -81
  49. package/dist/auth/hooks/useSsoOperations.js.map +1 -1
  50. package/dist/auth/hooks/useTokenRefresh.js +8 -12
  51. package/dist/auth/hooks/useTokenRefresh.js.map +1 -1
  52. package/dist/auth/hooks/useUserOperations.js +1 -2
  53. package/dist/auth/hooks/useUserOperations.js.map +1 -1
  54. package/dist/auth/hooks/useWorkspaceOperations.js +1 -2
  55. package/dist/auth/hooks/useWorkspaceOperations.js.map +1 -1
  56. package/dist/auth/hooks/useWorkspaceToken.js +1 -2
  57. package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
  58. package/dist/auth/index.js +19 -18
  59. package/dist/auth/pages/AuditLogPage.js +21 -22
  60. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  61. package/dist/auth/pages/EmailVerifyPage.js +15 -17
  62. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  63. package/dist/auth/pages/LoginPage.js +89 -105
  64. package/dist/auth/pages/LoginPage.js.map +1 -1
  65. package/dist/auth/pages/MagicLinkPage.js +30 -31
  66. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  67. package/dist/auth/pages/MfaSetupPage.js +18 -20
  68. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  69. package/dist/auth/pages/MfaVerifyPage.js +31 -35
  70. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  71. package/dist/auth/pages/OAuthCallbackPage.js +4 -11
  72. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  73. package/dist/auth/pages/PasswordResetPage.js +20 -23
  74. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  75. package/dist/auth/pages/ReactivatePage.js +14 -18
  76. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  77. package/dist/auth/pages/RegisterPage.js +180 -181
  78. package/dist/auth/pages/RegisterPage.js.map +1 -1
  79. package/dist/auth/pages/SSOClearPage.js +9 -10
  80. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  81. package/dist/auth/pages/SSODepositPage.js +9 -13
  82. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  83. package/dist/auth/pages/SSORelayCallbackPage.js +3 -7
  84. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  85. package/dist/auth/pages/SSORelayPage.js +11 -10
  86. package/dist/auth/pages/SSORelayPage.js.map +1 -1
  87. package/dist/auth/pages/SecurityPage.js +19 -21
  88. package/dist/auth/pages/SecurityPage.js.map +1 -1
  89. package/dist/auth/pages/SessionBridgePage.js +9 -16
  90. package/dist/auth/pages/SessionBridgePage.js.map +1 -1
  91. package/dist/auth/pages/SessionsPage.js +17 -18
  92. package/dist/auth/pages/SessionsPage.js.map +1 -1
  93. package/dist/auth/pages/SsoCallbackPage.js +17 -22
  94. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  95. package/dist/auth/pages/SsoConfigPage.js +69 -69
  96. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  97. package/dist/auth/pages/SsoLoginPage.js +9 -10
  98. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  99. package/dist/auth/pages/UserDetailsPage.js +16 -23
  100. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  101. package/dist/auth/pages/UserProfilePage.js +140 -141
  102. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  103. package/dist/auth/pages/UsersPage.js +446 -453
  104. package/dist/auth/pages/UsersPage.js.map +1 -1
  105. package/dist/auth/providers/AuthProvider.js +1 -2
  106. package/dist/auth/providers/AuthProvider.js.map +1 -1
  107. package/dist/auth/providers/CaptchaProvider.js +1 -2
  108. package/dist/auth/providers/CaptchaProvider.js.map +1 -1
  109. package/dist/auth/store/authStore.js +5 -12
  110. package/dist/auth/store/authStore.js.map +1 -1
  111. package/dist/auth/utils/captchaConfig.js +5 -8
  112. package/dist/auth/utils/captchaConfig.js.map +1 -1
  113. package/dist/auth/utils/formatters.js +1 -2
  114. package/dist/auth/utils/formatters.js.map +1 -1
  115. package/dist/auth/utils/gatewayBaseUrls.js +1 -2
  116. package/dist/auth/utils/gatewayBaseUrls.js.map +1 -1
  117. package/dist/auth/utils/legalUrls.js +2 -3
  118. package/dist/auth/utils/legalUrls.js.map +1 -1
  119. package/dist/auth/utils/redirect.js +1 -2
  120. package/dist/auth/utils/redirect.js.map +1 -1
  121. package/dist/auth/utils/ssoOrigins.js +2 -3
  122. package/dist/auth/utils/ssoOrigins.js.map +1 -1
  123. package/dist/auth/utils/validation.js +34 -36
  124. package/dist/auth/utils/validation.js.map +1 -1
  125. package/dist/generated/global-operations.js +2 -3
  126. package/dist/generated/global-operations.js.map +1 -1
  127. package/dist/generated/wspace-operations.js +2 -3
  128. package/dist/generated/wspace-operations.js.map +1 -1
  129. package/dist/microfe-auth.css +1 -1
  130. package/dist/shared/hooks/useTr.js +1 -2
  131. package/dist/shared/hooks/useTr.js.map +1 -1
  132. package/dist/translations/en.js +1 -2
  133. package/dist/translations/en.js.map +1 -1
  134. package/dist/utils/nativeBridge.js +1 -2
  135. package/package.json +7 -3
@@ -1,12 +1,12 @@
1
- import { useAuthStore as e } from "../../store/authStore.js";
2
- import { useTr as t } from "../../../shared/hooks/useTr.js";
1
+ import { useTr as e } from "../../../shared/hooks/useTr.js";
2
+ import { useAuthStore as t } from "../../store/authStore.js";
3
3
  import { useEffect as n, useRef as r, useState as i } from "react";
4
4
  import { useNavigate as a } from "react-router";
5
5
  import { Building2 as o, ChevronDown as s, LogOut as c, UserPlus as l } from "lucide-react";
6
6
  import { jsx as u, jsxs as d } from "react/jsx-runtime";
7
- //#region src/auth/components/profile/UserProfileMenu.tsx
7
+ // src/auth/components/profile/UserProfileMenu.tsx
8
8
  function f({ onSwitchWorkspace: f, onLogout: p, position: m = "right" }) {
9
- let h = t(), [g, _] = i(!1), v = r(null), y = a(), b = e((e) => e.user), x = e((e) => e.currentWorkspaceId);
9
+ let h = e(), [g, _] = i(!1), v = r(null), y = a(), b = t((e) => e.user), x = t((e) => e.currentWorkspaceId);
10
10
  n(() => {
11
11
  function e(e) {
12
12
  v.current && !v.current.contains(e.target) && _(!1);
@@ -86,7 +86,9 @@ function f({ onSwitchWorkspace: f, onLogout: p, position: m = "right" }) {
86
86
  /* @__PURE__ */ d("button", {
87
87
  type: "button",
88
88
  onClick: () => {
89
- _(!1), y(`/auth/login?mode=add-account&returnTo=${encodeURIComponent(window.location.pathname + window.location.search)}`);
89
+ _(!1);
90
+ let e = encodeURIComponent(window.location.pathname + window.location.search);
91
+ y(`/auth/login?mode=add-account&returnTo=${e}`);
90
92
  },
91
93
  className: "w-full flex items-center gap-3 px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken transition-colors",
92
94
  children: [/* @__PURE__ */ u(l, { className: "size-4" }), /* @__PURE__ */ u("span", { children: h("auth.profileMenu.addAccount", "Add account") })]
@@ -102,7 +104,6 @@ function f({ onSwitchWorkspace: f, onLogout: p, position: m = "right" }) {
102
104
  })]
103
105
  });
104
106
  }
105
- //#endregion
106
107
  export { f as UserProfileMenu };
107
108
 
108
109
  //# sourceMappingURL=UserProfileMenu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"UserProfileMenu.js","names":[],"sources":["../../../../src/auth/components/profile/UserProfileMenu.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\nimport { LogOut, ChevronDown, Building2, UserPlus } from \"lucide-react\";\nimport { useNavigate } from \"react-router\";\nimport { useAuthStore } from \"../../store/authStore\";\n\nexport interface UserProfileMenuProps {\n /**\n * Callback when workspace switcher should be shown\n */\n onSwitchWorkspace?: () => void;\n\n /**\n * Logout handler (REQUIRED)\n * Should clear auth state and redirect to login\n */\n onLogout: () => void;\n\n /**\n * Position of the dropdown menu\n * @default 'right'\n */\n position?: \"left\" | \"right\";\n}\n\n/**\n * User Profile Menu Component\n *\n * Displays user avatar/icon with dropdown menu containing:\n * - User information\n * - Switch Workspace option\n * - Logout button\n *\n * @example\n * ```tsx\n * import { UserProfileMenu } from '@burdenoff/microfe-auth';\n *\n * function AppHeader() {\n * return (\n * <div className=\"header\">\n * <UserProfileMenu\n * onSwitchWorkspace={() => setShowWorkspaceSwitcher(true)}\n * position=\"right\"\n * />\n * </div>\n * );\n * }\n * ```\n */\nexport function UserProfileMenu({\n onSwitchWorkspace,\n onLogout,\n position = \"right\",\n}: UserProfileMenuProps) {\n const tr = useTr();\n\n const [isOpen, setIsOpen] = useState(false);\n const menuRef = useRef<HTMLDivElement>(null);\n const navigate = useNavigate();\n\n const user = useAuthStore((state) => state.user);\n const currentWorkspaceId = useAuthStore((state) => state.currentWorkspaceId);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (menuRef.current && !menuRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n }\n\n if (isOpen) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () =>\n document.removeEventListener(\"mousedown\", handleClickOutside);\n }\n }, [isOpen]);\n\n const handleLogout = () => {\n setIsOpen(false);\n onLogout();\n };\n\n const handleSwitchWorkspace = () => {\n setIsOpen(false);\n onSwitchWorkspace?.();\n };\n\n if (!user) {\n return null;\n }\n\n // Get user initials for avatar\n const getInitials = () => {\n if (user.firstName && user.lastName) {\n return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase();\n }\n if (user.name) {\n const parts = user.name.split(\" \");\n if (parts.length >= 2) {\n return `${parts[0][0]}${parts[1][0]}`.toUpperCase();\n }\n return user.name.substring(0, 2).toUpperCase();\n }\n if (user.username) {\n return user.username.substring(0, 2).toUpperCase();\n }\n return user.email.substring(0, 2).toUpperCase();\n };\n\n const displayName =\n user.name ||\n `${user.firstName || \"\"} ${user.lastName || \"\"}`.trim() ||\n user.username;\n\n return (\n <div className=\"relative\" ref={menuRef}>\n {/* Profile Button */}\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n className=\"flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\"auth.profileMenu.userMenu\", \"User menu\")}\n aria-expanded={isOpen}\n >\n {/* Avatar */}\n <div className=\"size-8 rounded-full bg-accent-blue flex items-center justify-center text-white text-sm font-medium overflow-hidden\">\n {user.avatar ? (\n <img\n src={user.avatar}\n alt={tr(\"auth.profileMenu.avatarAlt\", \"Profile picture\")}\n className=\"size-full object-cover\"\n />\n ) : (\n getInitials()\n )}\n </div>\n\n {/* User Name (optional, hide on small screens) */}\n <span className=\"hidden sm:block text-sm font-medium text-text-secondary\">\n {displayName}\n </span>\n\n {/* Chevron */}\n <ChevronDown\n className={`size-4 text-text-secondary transition-transform ${\n isOpen ? \"rotate-180\" : \"\"\n }`}\n />\n </button>\n\n {/* Dropdown Menu */}\n {isOpen && (\n <div\n className={`absolute top-full mt-2 w-64 bg-bg-surface rounded-lg shadow-lg border border-border-subtle py-2 z-50 ${\n position === \"right\" ? \"right-0\" : \"left-0\"\n }`}\n >\n {/* User Info Section */}\n <div className=\"px-4 py-3 border-b border-border-subtle\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {displayName}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{user.email}</p>\n {currentWorkspaceId && (\n <p className=\"text-xs text-text-tertiary mt-1 truncate\">\n {tr(\"auth.profileMenu.workspaceLabel\", \"Workspace:\")}{\" \"}\n {currentWorkspaceId.substring(0, 8)}...\n </p>\n )}\n </div>\n\n {/* Menu Items */}\n <div className=\"py-1\">\n {/* Switch Workspace */}\n {onSwitchWorkspace && (\n <button\n type=\"button\"\n onClick={handleSwitchWorkspace}\n className=\"w-full flex items-center gap-3 px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken transition-colors\"\n >\n <Building2 className=\"size-4\" />\n <span>\n {tr(\"auth.profileMenu.switchWorkspace\", \"Switch Workspace\")}\n </span>\n </button>\n )}\n\n {/* Add Account */}\n <button\n type=\"button\"\n onClick={() => {\n setIsOpen(false);\n const returnTo = encodeURIComponent(\n window.location.pathname + window.location.search,\n );\n navigate(`/auth/login?mode=add-account&returnTo=${returnTo}`);\n }}\n className=\"w-full flex items-center gap-3 px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken transition-colors\"\n >\n <UserPlus className=\"size-4\" />\n <span>{tr(\"auth.profileMenu.addAccount\", \"Add account\")}</span>\n </button>\n\n {/* Logout */}\n <button\n type=\"button\"\n onClick={handleLogout}\n className=\"w-full flex items-center gap-3 px-4 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n >\n <LogOut className=\"size-4\" />\n <span>{tr(\"auth.profileMenu.logout\", \"Logout\")}</span>\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"UserProfileMenu.js","names":["UserProfileMenu","tr","useTr","isOpen","setIsOpen","useState","menuRef","useRef","navigate","useNavigate","user","useAuthStore","state","currentWorkspaceId","handleClickOutside","event","handleLogout","onLogout","handleSwitchWorkspace","onSwitchWorkspace","getInitials","parts","displayName","ChevronDown","position","Building2","returnTo","UserPlus","LogOut"],"sources":["../../../../src/auth/components/profile/UserProfileMenu.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\nimport { LogOut, ChevronDown, Building2, UserPlus } from \"lucide-react\";\nimport { useNavigate } from \"react-router\";\nimport { useAuthStore } from \"../../store/authStore\";\n\nexport interface UserProfileMenuProps {\n /**\n * Callback when workspace switcher should be shown\n */\n onSwitchWorkspace?: () => void;\n\n /**\n * Logout handler (REQUIRED)\n * Should clear auth state and redirect to login\n */\n onLogout: () => void;\n\n /**\n * Position of the dropdown menu\n * @default 'right'\n */\n position?: \"left\" | \"right\";\n}\n\n/**\n * User Profile Menu Component\n *\n * Displays user avatar/icon with dropdown menu containing:\n * - User information\n * - Switch Workspace option\n * - Logout button\n *\n * @example\n * ```tsx\n * import { UserProfileMenu } from '@burdenoff/microfe-auth';\n *\n * function AppHeader() {\n * return (\n * <div className=\"header\">\n * <UserProfileMenu\n * onSwitchWorkspace={() => setShowWorkspaceSwitcher(true)}\n * position=\"right\"\n * />\n * </div>\n * );\n * }\n * ```\n */\nexport function UserProfileMenu({\n onSwitchWorkspace,\n onLogout,\n position = \"right\",\n}: UserProfileMenuProps) {\n const tr = useTr();\n\n const [isOpen, setIsOpen] = useState(false);\n const menuRef = useRef<HTMLDivElement>(null);\n const navigate = useNavigate();\n\n const user = useAuthStore((state) => state.user);\n const currentWorkspaceId = useAuthStore((state) => state.currentWorkspaceId);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (menuRef.current && !menuRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n }\n\n if (isOpen) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () =>\n document.removeEventListener(\"mousedown\", handleClickOutside);\n }\n }, [isOpen]);\n\n const handleLogout = () => {\n setIsOpen(false);\n onLogout();\n };\n\n const handleSwitchWorkspace = () => {\n setIsOpen(false);\n onSwitchWorkspace?.();\n };\n\n if (!user) {\n return null;\n }\n\n // Get user initials for avatar\n const getInitials = () => {\n if (user.firstName && user.lastName) {\n return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase();\n }\n if (user.name) {\n const parts = user.name.split(\" \");\n if (parts.length >= 2) {\n return `${parts[0][0]}${parts[1][0]}`.toUpperCase();\n }\n return user.name.substring(0, 2).toUpperCase();\n }\n if (user.username) {\n return user.username.substring(0, 2).toUpperCase();\n }\n return user.email.substring(0, 2).toUpperCase();\n };\n\n const displayName =\n user.name ||\n `${user.firstName || \"\"} ${user.lastName || \"\"}`.trim() ||\n user.username;\n\n return (\n <div className=\"relative\" ref={menuRef}>\n {/* Profile Button */}\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n className=\"flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\"auth.profileMenu.userMenu\", \"User menu\")}\n aria-expanded={isOpen}\n >\n {/* Avatar */}\n <div className=\"size-8 rounded-full bg-accent-blue flex items-center justify-center text-white text-sm font-medium overflow-hidden\">\n {user.avatar ? (\n <img\n src={user.avatar}\n alt={tr(\"auth.profileMenu.avatarAlt\", \"Profile picture\")}\n className=\"size-full object-cover\"\n />\n ) : (\n getInitials()\n )}\n </div>\n\n {/* User Name (optional, hide on small screens) */}\n <span className=\"hidden sm:block text-sm font-medium text-text-secondary\">\n {displayName}\n </span>\n\n {/* Chevron */}\n <ChevronDown\n className={`size-4 text-text-secondary transition-transform ${\n isOpen ? \"rotate-180\" : \"\"\n }`}\n />\n </button>\n\n {/* Dropdown Menu */}\n {isOpen && (\n <div\n className={`absolute top-full mt-2 w-64 bg-bg-surface rounded-lg shadow-lg border border-border-subtle py-2 z-50 ${\n position === \"right\" ? \"right-0\" : \"left-0\"\n }`}\n >\n {/* User Info Section */}\n <div className=\"px-4 py-3 border-b border-border-subtle\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {displayName}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{user.email}</p>\n {currentWorkspaceId && (\n <p className=\"text-xs text-text-tertiary mt-1 truncate\">\n {tr(\"auth.profileMenu.workspaceLabel\", \"Workspace:\")}{\" \"}\n {currentWorkspaceId.substring(0, 8)}...\n </p>\n )}\n </div>\n\n {/* Menu Items */}\n <div className=\"py-1\">\n {/* Switch Workspace */}\n {onSwitchWorkspace && (\n <button\n type=\"button\"\n onClick={handleSwitchWorkspace}\n className=\"w-full flex items-center gap-3 px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken transition-colors\"\n >\n <Building2 className=\"size-4\" />\n <span>\n {tr(\"auth.profileMenu.switchWorkspace\", \"Switch Workspace\")}\n </span>\n </button>\n )}\n\n {/* Add Account */}\n <button\n type=\"button\"\n onClick={() => {\n setIsOpen(false);\n const returnTo = encodeURIComponent(\n window.location.pathname + window.location.search,\n );\n navigate(`/auth/login?mode=add-account&returnTo=${returnTo}`);\n }}\n className=\"w-full flex items-center gap-3 px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken transition-colors\"\n >\n <UserPlus className=\"size-4\" />\n <span>{tr(\"auth.profileMenu.addAccount\", \"Add account\")}</span>\n </button>\n\n {/* Logout */}\n <button\n type=\"button\"\n onClick={handleLogout}\n className=\"w-full flex items-center gap-3 px-4 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n >\n <LogOut className=\"size-4\" />\n <span>{tr(\"auth.profileMenu.logout\", \"Logout\")}</span>\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { useTr as e } from "../../../shared/hooks/useTr.js";
2
2
  import { useEffect as t, useId as n, useMemo as r, useState as i } from "react";
3
3
  import { jsx as a, jsxs as o } from "react/jsx-runtime";
4
- //#region src/auth/components/workspace/WorkspaceSelector.tsx
4
+ // src/auth/components/workspace/WorkspaceSelector.tsx
5
5
  var s = [];
6
6
  function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f, isLoading: p = !1, error: m = null }) {
7
7
  let h = e(), g = n(), _ = n(), v = n(), [y, b] = i(null), [x, S] = i(null), [C, w] = i(null), T = r(() => {
@@ -238,7 +238,6 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
238
238
  })]
239
239
  }) : null;
240
240
  }
241
- //#endregion
242
241
  export { c as WorkspaceSelector };
243
242
 
244
243
  //# sourceMappingURL=WorkspaceSelector.js.map
@@ -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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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":["EMPTY_PROJECTS","WorkspaceSelector","tr","useTr","orgSelectId","useId","workspaceSelectId","projectSelectId","selectedOrgId","setSelectedOrgId","useState","selectedWorkspaceId","setSelectedWorkspaceId","selectedProjectId","setSelectedProjectId","organizations","useMemo","orgMap","workspaces","orgId","workspace","id","count","filteredWorkspaces","w","filteredProjects","projects","p","selectedWorkspace","selectedProject","useEffect","handleOrgChange","handleWorkspaceChange","workspaceId","handleProjectChange","projectId","handleContinue","onSelect","handleCancel","onClose","canContinue","isOpen","error","org","project","isLoading"],"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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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":";;;;AAqFA,IAAMA,IAAqC,CAAC;AAE5C,SAAgBC,EAAkB,EAChC,WACA,YACA,eACA,cAAWD,GACX,aACA,eAAY,IACZ,WAAQ,QACiB;CACzB,IAAME,IAAKC,EAAM,GAGXC,IAAcC,EAAM,GACpBC,IAAoBD,EAAM,GAC1BE,IAAkBF,EAAM,GAExB,CAACG,GAAeC,KAAoBC,EAAwB,IAAI,GAChE,CAACC,GAAqBC,KAA0BF,EACpD,IACF,GACM,CAACG,GAAmBC,KAAwBJ,EAChD,IACF,GAGMK,IAAgBC,QAA8B;EAClD,IAAMC,oBAAS,IAAI,IAA6C;EAiBhE,OAfAC,EAAW,SAAS,MAAc;GAChC,IAAMC,IAAQC,EAAU,kBAAkB;GAW1C,AAVKH,EAAO,IAAIE,CAAK,KAEnBF,EAAO,IAAIE,GAAO;IAChB,MACEA,MAAU,YACNjB,EAAG,qCAAqC,SAAS,IACjD,GAAGA,EAAG,6CAA6C,cAAc,EAAE,GAAGiB,EAAM,MAAM,GAAG,CAAC;IAC5F,OAAO;GACT,CAAC,GAEH,EAAO,IAAIA,CAAK,CAAC,CAAE;EACrB,CAAC,GAEM,MAAM,KAAKF,EAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,CAACI,GAAI,EAAE,SAAM,iBAAc;GAClE;GACA;GACA,gBAAgBC;EAClB,EAAE;CACJ,GAAG,CAACJ,GAAYhB,CAAE,CAAC,GAGbqB,IAAqBP,QACpBR,IACEU,EAAW,QACf,OAAOM,EAAE,kBAAkB,eAAehB,CAC7C,IAH2BU,GAI1B,CAACA,GAAYV,CAAa,CAAC,GAGxBiB,IAAmBT,QAClBL,IACEe,EAAS,QAAQ,MAAMC,EAAE,gBAAgBhB,CAAmB,IADlC,CAAC,GAEjC,CAACe,GAAUf,CAAmB,CAAC,GAG5BiB,IAAoBZ,QACjBE,EAAW,MAAM,MAAMM,EAAE,OAAOb,CAAmB,GACzD,CAACO,GAAYP,CAAmB,CAAC,GAG9BkB,IAAkBb,QACfU,EAAS,MAAM,MAAMC,EAAE,OAAOd,CAAiB,GACrD,CAACa,GAAUb,CAAiB,CAAC;CAUhC,AAPAiB,QAAgB;EACd,AAAIf,EAAc,WAAW,KAAK,CAACP,KACjCC,EAAiBM,EAAc,EAAE,CAAC,EAAE;CAExC,GAAG,CAACA,GAAeP,CAAa,CAAC,GAGjCsB,QAAgB;EACd,AAAIP,EAAmB,WAAW,KAAK,CAACZ,KACtCC,EAAuBW,EAAmB,EAAE,CAAC,EAAE;CAEnD,GAAG,CAACA,GAAoBZ,CAAmB,CAAC;CAG5C,IAAMoB,KAAmB,MAAkB;EAGzC,AAFAtB,EAAiBU,CAAK,GACtBP,EAAuB,IAAI,GAC3BE,EAAqB,IAAI;CAC3B,GAGMkB,KAAyB,MAAwB;EAErD,AADApB,EAAuBqB,CAAW,GAClCnB,EAAqB,IAAI;CAC3B,GAGMoB,KAAuB,MAAsB;EACjD,EAAqBC,CAAS;CAChC,GAGMC,UAAuB;EAC3B,AAAKR,KACLS,EAAST,GAAmBC,CAAe;CAC7C,GAGMS,UAAqB;EAIzB,AAHA7B,EAAiB,IAAI,GACrBG,EAAuB,IAAI,GAC3BE,EAAqB,IAAI,GACzByB,EAAQ;CACV,GAGMC,IAAc7B,MAAwB;CAI5C,OAFK8B,IAGH,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CAEE,kBAAC,OAAD;GACE,WAAU;GACV,SAASH;GACT,eAAY;EACb,CAAA,GAGD,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IAEE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;MACX,UAAApC,EAAG,gCAAgC,uBAAuB;KACzD,CAAA,GACJ,kBAAC,KAAD;MAAG,WAAU;MACV,UAAAA,EACC,mCACA,4FACF;KACC,CAAA,CACA;;IAGJwC,KACC,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,KAAD;MAAG,WAAU;MACV,UAAAxC,EAAG,gCAAgC,OAAO;KAC1C,CAAA,GACH,kBAAC,KAAD,EAAA,UAAIwC,EAAS,CAAA,CACV;;IAIP,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MAEG3B,EAAc,SAAS,KACtB,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,SAAD;QACE,SAASX;QACT,WAAU;QAEV,UAAA,kBAAC,QAAD;SAAM,WAAU;SAAhB,UAAA,CACE,kBAAC,OAAD;UACE,WAAU;UACV,MAAK;UACL,QAAO;UACP,SAAQ;UACR,eAAY;UAEZ,UAAA,kBAAC,QAAD;WACE,eAAc;WACd,gBAAe;WACf,aAAa;WACb,GAAE;UACH,CAAA;SACE,CAAA,GACJF,EAAG,uCAAuC,cAAc,CACrD;;OACD,CAAA,GACP,kBAAC,UAAD;QACE,IAAIE;QACJ,OAAOI,KAAiB;QACxB,WAAW,MAAMuB,EAAgB,EAAE,OAAO,KAAK;QAC/C,WAAU;QAJZ,UAAA,CAME,kBAAC,UAAD;SAAQ,OAAM;SACX,UAAA7B,EACC,oCACA,wBACF;QACM,CAAA,GACPa,EAAc,KAAK,MAClB,kBAAC,UAAD;SAAqB,OAAO4B,EAAI;SAC7B,UAAAzC,EACC,oCACA,4CACA;UACE,MAAMyC,EAAI;UACV,OAAOA,EAAI;UACX,QAAQA,EAAI,mBAAmB,IAAU,KAAN;SACrC,CACF;QACM,GAVKA,EAAI,EAUT,CACT,CACK;OACL,CAAA,CAAA;;MAIP,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,SAAD;QACE,SAASrC;QACT,WAAU;QAEV,UAAA,kBAAC,QAAD;SAAM,WAAU;SAAhB,UAAA,CACE,kBAAC,OAAD;UACE,WAAU;UACV,MAAK;UACL,QAAO;UACP,SAAQ;UACR,eAAY;UAEZ,UAAA,kBAAC,QAAD;WACE,eAAc;WACd,gBAAe;WACf,aAAa;WACb,GAAE;UACH,CAAA;SACE,CAAA,GACJJ,EAAG,oCAAoC,WAAW,CAC/C;;OACD,CAAA,GACP,kBAAC,UAAD;QACE,IAAII;QACJ,OAAOK,KAAuB;QAC9B,WAAW,MAAMqB,EAAsB,EAAE,OAAO,KAAK;QACrD,UAAU,CAACxB,KAAiBO,EAAc,SAAS;QACnD,WAAU;QALZ,UAAA,CAOE,kBAAC,UAAD;SAAQ,OAAM;SACX,UAAAb,EACC,0CACA,qBACF;QACM,CAAA,GACPqB,EAAmB,KAAK,MACvB,kBAAC,UAAD;SAA2B,OAAOH,EAAU;SAA5C,UAAA;UACGA,EAAU;UAAK;UAAGA,EAAU;UAAK;SAC5B;QAFK,GAAAA,EAAU,EAEf,CACT,CACK;OACL,CAAA,CAAA;;MAGJK,EAAiB,SAAS,KACzB,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,SAAD;QACE,SAASlB;QACT,WAAU;QAEV,UAAA,kBAAC,QAAD;SAAM,WAAU;SAAhB,UAAA,CACE,kBAAC,OAAD;UACE,WAAU;UACV,MAAK;UACL,QAAO;UACP,SAAQ;UACR,eAAY;UAEZ,UAAA,kBAAC,QAAD;WACE,eAAc;WACd,gBAAe;WACf,aAAa;WACb,GAAE;UACH,CAAA;SACE,CAAA,GACJL,EACC,0CACA,oBACF,CACI;;OACD,CAAA,GACP,kBAAC,UAAD;QACE,IAAIK;QACJ,OAAOM,KAAqB;QAC5B,WAAW,MAAMqB,EAAoB,EAAE,OAAO,KAAK;QACnD,WAAU;QAJZ,UAAA,CAME,kBAAC,UAAD;SAAQ,OAAM;SACX,UAAAhC,EACC,oCACA,qBACF;QACM,CAAA,GACPuB,EAAiB,KAAK,MACrB,kBAAC,UAAD;SAAyB,OAAOmB,EAAQ;SACrC,UAAAA,EAAQ;QACH,GAFKA,EAAQ,EAEb,CACT,CACK;OACL,CAAA,CAAA;;MAINhB,KACC,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;QACV,UAAA1B,EAAG,mCAAmC,WAAW;OACjD,CAAA,GACH,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,OAAD;UACE,WAAU;UACV,MAAK;UACL,QAAO;UACP,SAAQ;UAER,UAAA,kBAAC,QAAD;WACE,eAAc;WACd,gBAAe;WACf,aAAa;WACb,GAAE;UACH,CAAA;SACE,CAAA,GACL,kBAAC,QAAD,EAAA,UAAO0B,EAAkB,KAAW,CAAA,CACjC;QACJ,CAAA,GAAAC,KACC,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,OAAD;UACE,WAAU;UACV,MAAK;UACL,QAAO;UACP,SAAQ;UAER,UAAA,kBAAC,QAAD;WACE,eAAc;WACd,gBAAe;WACf,aAAa;WACb,GAAE;UACH,CAAA;SACE,CAAA,GACL,kBAAC,QAAD,EAAA,UAAOA,EAAgB,KAAW,CAAA,CAC/B;QAEJ,CAAA,CAAA;OACF,CAAA,CAAA;;KAEJ;;IAGL,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAASS;MACT,UAAUO;MACV,WAAU;MAET,UAAA3C,EAAG,iCAAiC,QAAQ;KACvC,CAAA,GACR,kBAAC,UAAD;MACE,MAAK;MACL,SAASkC;MACT,UAAU,CAACI,KAAeK;MAC1B,WAAU;MAET,UAAAA,IACG3C,EAAG,kCAAkC,YAAY,IACjDA,EAAG,mCAAmC,UAAU;KAC9C,CAAA,CACL;;GACF;EACF,CAAA,CAAA;C,KA9Pa;AAgQtB"}
@@ -1,4 +1,4 @@
1
- //#region src/auth/constants/permissions.ts
1
+ // src/auth/constants/permissions.ts
2
2
  var e = {
3
3
  USER_READ: "user:read",
4
4
  USER_UPDATE: "user:update",
@@ -12,7 +12,6 @@ var e = {
12
12
  OIDC_CONFIG_DELETE: "oidc-config:delete",
13
13
  AUTH_METHOD_UPDATE: "auth-method:update"
14
14
  };
15
- //#endregion
16
15
  export { e as AUTH_PERMISSIONS };
17
16
 
18
17
  //# sourceMappingURL=permissions.js.map
@@ -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;AACtB"}
1
+ {"version":3,"file":"permissions.js","names":["AUTH_PERMISSIONS"],"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,IAAaA,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,6 +1,5 @@
1
- //#region src/auth/constants/sessionKeys.ts
1
+ // src/auth/constants/sessionKeys.ts
2
2
  var e = "mfa_challenge_id", t = "mfa_pending_token", n = "mfa_pending_refresh", r = "mfa_redirect", i = "oidc_code_verifier", a = "oidc_state", o = "oidc_nonce", s = "oidc_tenant_id", c = "oidc_flow", l = "oidc_redirect";
3
- //#endregion
4
3
  export { e as MFA_CHALLENGE_ID, n as MFA_PENDING_REFRESH, t as MFA_PENDING_TOKEN, r as MFA_REDIRECT, i as OIDC_CODE_VERIFIER, c as OIDC_FLOW, o as OIDC_NONCE, l as OIDC_REDIRECT, a as OIDC_STATE, s as OIDC_TENANT_ID };
5
4
 
6
5
  //# sourceMappingURL=sessionKeys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessionKeys.js","names":[],"sources":["../../../src/auth/constants/sessionKeys.ts"],"sourcesContent":["/**\n * sessionStorage key constants for auth flows.\n * Centralizing these avoids silent key mismatches across files.\n */\n\n// MFA verification flow\nexport const MFA_CHALLENGE_ID = \"mfa_challenge_id\";\nexport const MFA_PENDING_TOKEN = \"mfa_pending_token\";\nexport const MFA_PENDING_REFRESH = \"mfa_pending_refresh\";\nexport const MFA_REDIRECT = \"mfa_redirect\";\n\n// OIDC / SSO flow\nexport const OIDC_CODE_VERIFIER = \"oidc_code_verifier\";\nexport const OIDC_STATE = \"oidc_state\";\nexport const OIDC_NONCE = \"oidc_nonce\";\nexport const OIDC_TENANT_ID = \"oidc_tenant_id\";\nexport const OIDC_FLOW = \"oidc_flow\";\nexport const OIDC_REDIRECT = \"oidc_redirect\";\nexport const OIDC_CLIENT_ID = \"oidc_client_id\";\n\n// MFA key sets for bulk operations\nexport const MFA_SESSION_KEYS = [\n MFA_CHALLENGE_ID,\n MFA_PENDING_TOKEN,\n MFA_PENDING_REFRESH,\n MFA_REDIRECT,\n] as const;\n\nexport const OIDC_SESSION_KEYS = [\n OIDC_CODE_VERIFIER,\n OIDC_STATE,\n OIDC_NONCE,\n OIDC_TENANT_ID,\n OIDC_FLOW,\n OIDC_REDIRECT,\n] as const;\n"],"mappings":";AAMA,IAAa,IAAmB,oBACnB,IAAoB,qBACpB,IAAsB,uBACtB,IAAe,gBAGf,IAAqB,sBACrB,IAAa,cACb,IAAa,cACb,IAAiB,kBACjB,IAAY,aACZ,IAAgB"}
1
+ {"version":3,"file":"sessionKeys.js","names":["MFA_CHALLENGE_ID","MFA_PENDING_TOKEN","MFA_PENDING_REFRESH","MFA_REDIRECT","OIDC_CODE_VERIFIER","OIDC_STATE","OIDC_NONCE","OIDC_TENANT_ID","OIDC_FLOW","OIDC_REDIRECT"],"sources":["../../../src/auth/constants/sessionKeys.ts"],"sourcesContent":["/**\n * sessionStorage key constants for auth flows.\n * Centralizing these avoids silent key mismatches across files.\n */\n\n// MFA verification flow\nexport const MFA_CHALLENGE_ID = \"mfa_challenge_id\";\nexport const MFA_PENDING_TOKEN = \"mfa_pending_token\";\nexport const MFA_PENDING_REFRESH = \"mfa_pending_refresh\";\nexport const MFA_REDIRECT = \"mfa_redirect\";\n\n// OIDC / SSO flow\nexport const OIDC_CODE_VERIFIER = \"oidc_code_verifier\";\nexport const OIDC_STATE = \"oidc_state\";\nexport const OIDC_NONCE = \"oidc_nonce\";\nexport const OIDC_TENANT_ID = \"oidc_tenant_id\";\nexport const OIDC_FLOW = \"oidc_flow\";\nexport const OIDC_REDIRECT = \"oidc_redirect\";\nexport const OIDC_CLIENT_ID = \"oidc_client_id\";\n\n// MFA key sets for bulk operations\nexport const MFA_SESSION_KEYS = [\n MFA_CHALLENGE_ID,\n MFA_PENDING_TOKEN,\n MFA_PENDING_REFRESH,\n MFA_REDIRECT,\n] as const;\n\nexport const OIDC_SESSION_KEYS = [\n OIDC_CODE_VERIFIER,\n OIDC_STATE,\n OIDC_NONCE,\n OIDC_TENANT_ID,\n OIDC_FLOW,\n OIDC_REDIRECT,\n] as const;\n"],"mappings":";AAMA,IAAaA,IAAmB,oBACnBC,IAAoB,qBACpBC,IAAsB,uBACtBC,IAAe,gBAGfC,IAAqB,sBACrBC,IAAa,cACbC,IAAa,cACbC,IAAiB,kBACjBC,IAAY,aACZC,IAAgB"}
@@ -1,4 +1,4 @@
1
- //#region src/auth/hooks/contextDeepLink.ts
1
+ // src/auth/hooks/contextDeepLink.ts
2
2
  function e(e) {
3
3
  let { requestedWorkspaceId: t, workspaces: n } = e;
4
4
  if (!t || !n) return { kind: "none" };
@@ -24,7 +24,6 @@ function t(e) {
24
24
  function n(e) {
25
25
  return e ? typeof e.total != "number" || e.total <= e.items.length : !1;
26
26
  }
27
- //#endregion
28
27
  export { t as decideOrganizationDeepLink, e as decideWorkspaceDeepLink, n as isCompleteListing };
29
28
 
30
29
  //# sourceMappingURL=contextDeepLink.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contextDeepLink.js","names":[],"sources":["../../../src/auth/hooks/contextDeepLink.ts"],"sourcesContent":["/**\n * Deep links to a workspace (`?workspace=<id>`, with or without `?org=`).\n *\n * ★★★ The bug this fixes (vc-fg-parity round 3): a link such as\n * `/workflows/runs?workspace=<id>` without an `org` param, to a workspace in a\n * DIFFERENT organization than the one saved in the session, was rewritten\n * ~2.6s after load to the saved organization's first workspace, with no\n * message. The bootstrap kept the saved organization, filtered `myWorkspaces`\n * by it, did not find the linked workspace, and fell back silently.\n *\n * A workspace names its organization, so the workspace in the link wins:\n *\n * - linked workspace is one of the caller's (`myWorkspaces`) and its\n * organization is accessible → switch organization AND workspace to it;\n * - linked workspace is not one of the caller's (and the list was read in\n * full) → record a denial the shell shows as a clear \"not available\" state\n * instead of a silent redirect;\n * - otherwise (no link, or an incomplete read) → nothing changes here.\n *\n * Only a workspace that came from the URL is a deep link. A stale id restored\n * from session storage (an earlier account, a deleted workspace) still falls\n * back quietly, as it always has.\n *\n * Pure so it can be unit-tested; `useContextSwitcherData` applies it.\n */\nimport type { Organization, Workspace } from \"../types\";\n\n/** A deep-linked organization / workspace the caller cannot open. */\nexport interface ContextLinkDenial {\n kind: \"workspace\" | \"organization\";\n /** The id the link named. */\n id: string;\n}\n\nexport type WorkspaceDeepLinkDecision =\n | { kind: \"none\" }\n | { kind: \"switch\"; workspace: Workspace }\n | { kind: \"denied\"; denial: ContextLinkDenial };\n\nexport interface WorkspaceDeepLinkInput {\n /** The active workspace id when the bootstrap started (URL first). */\n requestedWorkspaceId: string | null;\n /** True when that id is the `?workspace=` param of the current URL. */\n fromUrl: boolean;\n /** The organization the bootstrap is about to enter. */\n targetOrgId: string | null;\n /** The caller's organizations, as `myOrganizations` returned them. */\n organizations: ReadonlyArray<Pick<Organization, \"id\">>;\n /** The caller's workspaces across organizations, or null when not read. */\n workspaces: ReadonlyArray<Workspace> | null;\n /** False when `workspaces` is only a page of a longer list. */\n workspacesComplete: boolean;\n}\n\nexport function decideWorkspaceDeepLink(\n input: WorkspaceDeepLinkInput,\n): WorkspaceDeepLinkDecision {\n const { requestedWorkspaceId, workspaces } = input;\n if (!requestedWorkspaceId || !workspaces) return { kind: \"none\" };\n\n const linked = workspaces.find((w) => w.id === requestedWorkspaceId);\n if (linked) {\n const orgAccessible = input.organizations.some(\n (org) => org.id === linked.organizationId,\n );\n if (!orgAccessible || linked.organizationId === input.targetOrgId) {\n return { kind: \"none\" };\n }\n return { kind: \"switch\", workspace: linked };\n }\n\n if (input.fromUrl && input.workspacesComplete) {\n return {\n kind: \"denied\",\n denial: { kind: \"workspace\", id: requestedWorkspaceId },\n };\n }\n return { kind: \"none\" };\n}\n\n/**\n * The denial for a deep-linked organization the caller is not a member of.\n * Null when the organization did not come from the URL (a stale saved id) or\n * the caller's organization list is empty (nothing was read).\n */\nexport function decideOrganizationDeepLink(input: {\n requestedOrgId: string | null;\n fromUrl: boolean;\n organizations: ReadonlyArray<Pick<Organization, \"id\">>;\n}): ContextLinkDenial | null {\n const { requestedOrgId, fromUrl, organizations } = input;\n if (!requestedOrgId || !fromUrl || organizations.length === 0) return null;\n if (organizations.some((org) => org.id === requestedOrgId)) return null;\n return { kind: \"organization\", id: requestedOrgId };\n}\n\n/** True when a `{ total, items }` page holds the whole list. */\nexport function isCompleteListing(\n listing: { total?: number | null; items: ReadonlyArray<unknown> } | null,\n): boolean {\n if (!listing) return false;\n return (\n typeof listing.total !== \"number\" || listing.total <= listing.items.length\n );\n}\n"],"mappings":";AAsDA,SAAgB,EACd,GAC2B;CAC3B,IAAM,EAAE,yBAAsB,kBAAe;CAC7C,IAAI,CAAC,KAAwB,CAAC,GAAY,OAAO,EAAE,MAAM,OAAO;CAEhE,IAAM,IAAS,EAAW,MAAM,MAAM,EAAE,OAAO,CAAoB;CAiBnE,OAhBI,IAIE,CAHkB,EAAM,cAAc,MACvC,MAAQ,EAAI,OAAO,EAAO,cAExB,KAAiB,EAAO,mBAAmB,EAAM,cAC7C,EAAE,MAAM,OAAO,IAEjB;EAAE,MAAM;EAAU,WAAW;CAAO,IAGzC,EAAM,WAAW,EAAM,qBAClB;EACL,MAAM;EACN,QAAQ;GAAE,MAAM;GAAa,IAAI;EAAqB;CACxD,IAEK,EAAE,MAAM,OAAO;AACxB;AAOA,SAAgB,EAA2B,GAId;CAC3B,IAAM,EAAE,mBAAgB,YAAS,qBAAkB;CAGnD,OAFI,CAAC,KAAkB,CAAC,KAAW,EAAc,WAAW,KACxD,EAAc,MAAM,MAAQ,EAAI,OAAO,CAAc,IAAU,OAC5D;EAAE,MAAM;EAAgB,IAAI;CAAe;AACpD;AAGA,SAAgB,EACd,GACS;CAET,OADK,IAEH,OAAO,EAAQ,SAAU,YAAY,EAAQ,SAAS,EAAQ,MAAM,SAFjD;AAIvB"}
1
+ {"version":3,"file":"contextDeepLink.js","names":["decideWorkspaceDeepLink","input","requestedWorkspaceId","workspaces","linked","w","org","decideOrganizationDeepLink","requestedOrgId","fromUrl","organizations","isCompleteListing","listing"],"sources":["../../../src/auth/hooks/contextDeepLink.ts"],"sourcesContent":["/**\n * Deep links to a workspace (`?workspace=<id>`, with or without `?org=`).\n *\n * ★★★ The bug this fixes (vc-fg-parity round 3): a link such as\n * `/workflows/runs?workspace=<id>` without an `org` param, to a workspace in a\n * DIFFERENT organization than the one saved in the session, was rewritten\n * ~2.6s after load to the saved organization's first workspace, with no\n * message. The bootstrap kept the saved organization, filtered `myWorkspaces`\n * by it, did not find the linked workspace, and fell back silently.\n *\n * A workspace names its organization, so the workspace in the link wins:\n *\n * - linked workspace is one of the caller's (`myWorkspaces`) and its\n * organization is accessible → switch organization AND workspace to it;\n * - linked workspace is not one of the caller's (and the list was read in\n * full) → record a denial the shell shows as a clear \"not available\" state\n * instead of a silent redirect;\n * - otherwise (no link, or an incomplete read) → nothing changes here.\n *\n * Only a workspace that came from the URL is a deep link. A stale id restored\n * from session storage (an earlier account, a deleted workspace) still falls\n * back quietly, as it always has.\n *\n * Pure so it can be unit-tested; `useContextSwitcherData` applies it.\n */\nimport type { Organization, Workspace } from \"../types\";\n\n/** A deep-linked organization / workspace the caller cannot open. */\nexport interface ContextLinkDenial {\n kind: \"workspace\" | \"organization\";\n /** The id the link named. */\n id: string;\n}\n\nexport type WorkspaceDeepLinkDecision =\n | { kind: \"none\" }\n | { kind: \"switch\"; workspace: Workspace }\n | { kind: \"denied\"; denial: ContextLinkDenial };\n\nexport interface WorkspaceDeepLinkInput {\n /** The active workspace id when the bootstrap started (URL first). */\n requestedWorkspaceId: string | null;\n /** True when that id is the `?workspace=` param of the current URL. */\n fromUrl: boolean;\n /** The organization the bootstrap is about to enter. */\n targetOrgId: string | null;\n /** The caller's organizations, as `myOrganizations` returned them. */\n organizations: ReadonlyArray<Pick<Organization, \"id\">>;\n /** The caller's workspaces across organizations, or null when not read. */\n workspaces: ReadonlyArray<Workspace> | null;\n /** False when `workspaces` is only a page of a longer list. */\n workspacesComplete: boolean;\n}\n\nexport function decideWorkspaceDeepLink(\n input: WorkspaceDeepLinkInput,\n): WorkspaceDeepLinkDecision {\n const { requestedWorkspaceId, workspaces } = input;\n if (!requestedWorkspaceId || !workspaces) return { kind: \"none\" };\n\n const linked = workspaces.find((w) => w.id === requestedWorkspaceId);\n if (linked) {\n const orgAccessible = input.organizations.some(\n (org) => org.id === linked.organizationId,\n );\n if (!orgAccessible || linked.organizationId === input.targetOrgId) {\n return { kind: \"none\" };\n }\n return { kind: \"switch\", workspace: linked };\n }\n\n if (input.fromUrl && input.workspacesComplete) {\n return {\n kind: \"denied\",\n denial: { kind: \"workspace\", id: requestedWorkspaceId },\n };\n }\n return { kind: \"none\" };\n}\n\n/**\n * The denial for a deep-linked organization the caller is not a member of.\n * Null when the organization did not come from the URL (a stale saved id) or\n * the caller's organization list is empty (nothing was read).\n */\nexport function decideOrganizationDeepLink(input: {\n requestedOrgId: string | null;\n fromUrl: boolean;\n organizations: ReadonlyArray<Pick<Organization, \"id\">>;\n}): ContextLinkDenial | null {\n const { requestedOrgId, fromUrl, organizations } = input;\n if (!requestedOrgId || !fromUrl || organizations.length === 0) return null;\n if (organizations.some((org) => org.id === requestedOrgId)) return null;\n return { kind: \"organization\", id: requestedOrgId };\n}\n\n/** True when a `{ total, items }` page holds the whole list. */\nexport function isCompleteListing(\n listing: { total?: number | null; items: ReadonlyArray<unknown> } | null,\n): boolean {\n if (!listing) return false;\n return (\n typeof listing.total !== \"number\" || listing.total <= listing.items.length\n );\n}\n"],"mappings":";AAsDA,SAAgBA,EACd,GAC2B;CAC3B,IAAM,EAAE,yBAAsB,kBAAeC;CAC7C,IAAI,CAACC,KAAwB,CAACC,GAAY,OAAO,EAAE,MAAM,OAAO;CAEhE,IAAMC,IAASD,EAAW,MAAM,MAAME,EAAE,OAAOH,CAAoB;CAiBnE,OAhBIE,IAIE,CAHkBH,EAAM,cAAc,MACvC,MAAQK,EAAI,OAAOF,EAAO,cAExB,KAAiBA,EAAO,mBAAmBH,EAAM,cAC7C,EAAE,MAAM,OAAO,IAEjB;EAAE,MAAM;EAAU,WAAWG;CAAO,IAGzCH,EAAM,WAAWA,EAAM,qBAClB;EACL,MAAM;EACN,QAAQ;GAAE,MAAM;GAAa,IAAIC;EAAqB;CACxD,IAEK,EAAE,MAAM,OAAO;AACxB;AAOA,SAAgBK,EAA2B,GAId;CAC3B,IAAM,EAAE,mBAAgB,YAAS,qBAAkBN;CAGnD,OAFI,CAACO,KAAkB,CAACC,KAAWC,EAAc,WAAW,KACxDA,EAAc,MAAM,MAAQJ,EAAI,OAAOE,CAAc,IAAU,OAC5D;EAAE,MAAM;EAAgB,IAAIA;CAAe;AACpD;AAGA,SAAgBG,EACd,GACS;CAET,OADKC,IAEH,OAAOA,EAAQ,SAAU,YAAYA,EAAQ,SAASA,EAAQ,MAAM,SAFjD;AAIvB"}
@@ -1,17 +1,18 @@
1
- import "../store/authStore.js";
2
- import "./useAuthOperations.js";
3
- import "./useMyWorkspaces.js";
4
- import "./useOAuthOperations.js";
5
- import "./useMfaOperations.js";
6
- import "./usePasswordOperations.js";
7
- import "./useSessionOperations.js";
1
+ import { useAuthOperations as e } from "./useAuthOperations.js";
2
+ import { useMfaOperations as t } from "./useMfaOperations.js";
3
+ import { usePasswordOperations as n } from "./usePasswordOperations.js";
4
+ import { useSessionOperations as r } from "./useSessionOperations.js";
8
5
  import "./useApiKeyOperations.js";
9
6
  import "./useAppOperations.js";
10
- import "./useWorkspaceOperations.js";
11
- import "./useContextSwitcherData.js";
12
- import "./useActiveContextBootstrapMirror.js";
7
+ import { useWorkspaceOperations as i } from "./useWorkspaceOperations.js";
8
+ import { useMyWorkspaces as a } from "./useMyWorkspaces.js";
9
+ import { useContextSwitcherData as o, useProjectSyncEffect as s, useWorkspaceSyncEffect as c } from "./useContextSwitcherData.js";
10
+ import { useActiveContextBootstrapMirror as l } from "./useActiveContextBootstrapMirror.js";
13
11
  import "./useDelegationOperations.js";
14
- import "./useUserOperations.js";
12
+ import { useUserOperations as u } from "./useUserOperations.js";
15
13
  import "./useSsoOperations.js";
14
+ import "./useOAuthOperations.js";
16
15
  import "./useAccountOperations.js";
17
16
  import "./useAuthPermissions.js";
17
+ import { useAuthStore as d } from "../store/authStore.js";
18
+ export { l as useActiveContextBootstrapMirror, e as useAuthOperations, d as useAuthStore, o as useContextSwitcherData, t as useMfaOperations, a as useMyWorkspaces, n as usePasswordOperations, s as useProjectSyncEffect, r as useSessionOperations, u as useUserOperations, i as useWorkspaceOperations, c as useWorkspaceSyncEffect };
@@ -1,7 +1,7 @@
1
1
  import { useAuthStore as e } from "../store/authStore.js";
2
2
  import { DeactivateAccountDocument as t, DeleteAccountDocument as n, ReactivateAccountDocument as r } from "../../generated/global-operations.js";
3
3
  import { useMutation as i } from "@apollo/client/react";
4
- //#region src/auth/hooks/useAccountOperations.ts
4
+ // src/auth/hooks/useAccountOperations.ts
5
5
  function a() {
6
6
  let [a, { loading: o }] = i(t), [s, { loading: c }] = i(n), [l, { loading: u }] = i(r), d = e.getState();
7
7
  return {
@@ -46,7 +46,6 @@ function a() {
46
46
  isLoading: o || c || u
47
47
  };
48
48
  }
49
- //#endregion
50
49
  export { a as useAccountOperations };
51
50
 
52
51
  //# sourceMappingURL=useAccountOperations.js.map
@@ -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,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
+ {"version":3,"file":"useAccountOperations.js","names":["useAccountOperations","deactivateAccountMutation","deactivating","useMutation","DeactivateAccountDocument","deleteAccountMutation","deleting","DeleteAccountDocument","reactivateAccountMutation","reactivating","ReactivateAccountDocument","store","useAuthStore","result","data","error","confirmPhrase","message"],"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,SAAgBA,IAAuB;CACrC,IAAM,CAACC,GAA2B,EAAE,SAASC,OAAkBC,EAG7DC,CAAyB,GACrB,CAACC,GAAuB,EAAE,SAASC,OAAcH,EAGrDI,CAAqB,GACjB,CAACC,GAA2B,EAAE,SAASC,OAAkBN,EAG7DO,CAAyB,GAErBC,IAAQC,EAAa,SAAS;CAiGpC,OAAO;EACL,0BA3FA,MACiC;GACjC,IAAI;IACF,IAAM,EAAE,YAAS,MAAMX,EAA0B,EAC/C,WAAW,EAAE,YAAS,EACxB,CAAC,GAEKY,IAASC,EAAM;IAOrB,OALID,EAAO,WAETF,EAAM,UAAU,GAGXE;GACT,SAASE,GAAO;IAEd,MADA,QAAQ,MAAM,iCAAiCA,CAAK,GAC9CA;GACR;EACF;EAyEE,sBAlEA,GACA,MACiC;GAEjC,IAAIC,MAAkB,qBACpB,MAAU,MAAM,uDAAqD;GAGvE,IAAI;IACF,IAAM,EAAE,YAAS,MAAMX,EAAsB,EAC3C,WAAW;KAAE;KAAU;IAAc,EACvC,CAAC,GAEKQ,IAASC,EAAM;IAOrB,OALID,EAAO,WAETF,EAAM,UAAU,GAGXE;GACT,SAASE,GAAO;IAEd,MADA,QAAQ,MAAM,6BAA6BA,CAAK,GAC1CA;GACR;EACF;EA0CE,0BAnCA,GACA,MACwB;GACxB,IAAI;IAEF,AADAJ,EAAM,WAAW,EAAI,GACrBA,EAAM,SAAS,IAAI;IAEnB,IAAM,EAAE,YAAS,MAAMH,EAA0B,EAC/C,WAAW;KAAE;KAAO;IAAS,EAC/B,CAAC,GAEKK,IAASC,EAAM;IAUrB,OARID,EAAO,WAAWA,EAAO,QAAQA,EAAO,SAC1CF,EAAM,QAAQE,EAAO,IAAI,GACzBF,EAAM,cAAcE,EAAO,OAAOA,EAAO,gBAAgB,EAAE,GAC3DF,EAAM,iBAAiB,EAAI,KAClBE,EAAO,SAChBF,EAAM,SAASE,EAAO,KAAK,GAGtBA;GACT,SAASE,GAAO;IACd,IAAME,IACJF,aAAiB,QAAQA,EAAM,UAAU;IAE3C,MADAJ,EAAM,SAASM,CAAO,GAChBF;GACR,UAAU;IACR,EAAM,WAAW,EAAK;GACxB;EACF;EAME,gBAAgBb;EAChB,YAAYI;EACZ,gBAAgBG;EAChB,WAAWP,KAAgBI,KAAYG;CACzC;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { useEffect as e } from "react";
2
- //#region src/auth/hooks/useActiveContextBootstrapMirror.ts
2
+ // src/auth/hooks/useActiveContextBootstrapMirror.ts
3
3
  function t({ storeOrganizationId: t, storeWorkspaceId: n, storeProjectId: r, activeOrganizationId: i, activeWorkspaceId: a, activeProjectId: o, storeWorkspaces: s, storeProjects: c, setOrganization: l, setWorkspace: u, setProject: d }) {
4
4
  e(() => {
5
5
  t && !i && l(t);
@@ -21,7 +21,6 @@ function t({ storeOrganizationId: t, storeWorkspaceId: n, storeProjectId: r, act
21
21
  d
22
22
  ]);
23
23
  }
24
- //#endregion
25
24
  export { t as useActiveContextBootstrapMirror };
26
25
 
27
26
  //# sourceMappingURL=useActiveContextBootstrapMirror.js.map
@@ -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;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
+ {"version":3,"file":"useActiveContextBootstrapMirror.js","names":["useActiveContextBootstrapMirror","storeOrganizationId","activeOrganizationId","setOrganization","targetOrganizationId","workspaceBelongsToTargetOrganization","storeWorkspaceId","storeWorkspaces","workspace","activeWorkspaceId","setWorkspace","targetWorkspaceId","projectBelongsToTargetWorkspace","storeProjectId","storeProjects","project","activeProjectId","setProject"],"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,SAAgBA,EAAgC,EAC9C,wBACA,qBACA,mBACA,yBACA,sBACA,oBACA,oBACA,kBACA,oBACA,iBACA,iBACyC;CACzC,QAAgB;EACd,AAAIC,KAAuB,CAACC,KAC1BC,EAAgBF,CAAmB;EAQrC,IAAMG,IAAuBF,KAAwBD,GAC/CI,IACJ,CAAC,CAACC,KACF,CAAC,CAACF,KACF,CAAC,CAACG,GAAiB,MAChB,MACCC,EAAU,OAAOF,KACjBE,EAAU,mBAAmBJ,CACjC;EAEF,AACEE,KACA,CAACG,KACDJ,KAEAK,EAAaJ,CAAgB;EAU/B,IAAMK,IACJF,MACCJ,IAAuCC,IAAmB,OACvDM,IACJ,CAAC,CAACC,KACF,CAAC,CAACF,KACF,CAAC,CAACG,GAAe,MACd,MACCC,EAAQ,OAAOF,KACfE,EAAQ,gBAAgBJ,CAC5B;EAEF,AAAIE,KAAkB,CAACG,KAAmBJ,KACxCK,EAAWJ,CAAc;CAE7B,GAAG;EACDZ;EACAK;EACAO;EACAX;EACAO;EACAO;EACAT;EACAO;EACAX;EACAO;EACAO;CACF,CAAC;AACH"}