@burdenoff/microfe-auth 2026.830.1 → 2026.904.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/auth/AuthRoot.js +43 -41
  2. package/dist/auth/AuthRoot.js.map +1 -1
  3. package/dist/auth/AuthRoutes.js +7 -0
  4. package/dist/auth/AuthRoutes.js.map +1 -1
  5. package/dist/auth/components/AuthLayout.js +65 -67
  6. package/dist/auth/components/AuthLayout.js.map +1 -1
  7. package/dist/auth/components/CaptchaV2.js +21 -24
  8. package/dist/auth/components/CaptchaV2.js.map +1 -1
  9. package/dist/auth/components/ConfirmActionDialog.js +33 -32
  10. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  11. package/dist/auth/components/profile/UserProfileMenu.js +44 -46
  12. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  13. package/dist/auth/components/workspace/WorkspaceSelector.js +62 -68
  14. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  15. package/dist/auth/pages/AuditLogPage.js +65 -68
  16. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  17. package/dist/auth/pages/EmailVerifyPage.js +51 -54
  18. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  19. package/dist/auth/pages/LoginPage.js +223 -225
  20. package/dist/auth/pages/LoginPage.js.map +1 -1
  21. package/dist/auth/pages/MagicLinkPage.js +67 -69
  22. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  23. package/dist/auth/pages/MfaSetupPage.js +85 -88
  24. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  25. package/dist/auth/pages/MfaVerifyPage.js +60 -63
  26. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  27. package/dist/auth/pages/OAuthCallbackPage.js +37 -40
  28. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  29. package/dist/auth/pages/PasswordResetPage.js +102 -105
  30. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  31. package/dist/auth/pages/ReactivatePage.js +54 -57
  32. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  33. package/dist/auth/pages/RegisterPage.js +171 -174
  34. package/dist/auth/pages/RegisterPage.js.map +1 -1
  35. package/dist/auth/pages/SSOClearPage.js +21 -20
  36. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  37. package/dist/auth/pages/SSODepositPage.js +27 -26
  38. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  39. package/dist/auth/pages/SSORelayCallbackPage.js +19 -18
  40. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  41. package/dist/auth/pages/SecurityPage.js +247 -250
  42. package/dist/auth/pages/SecurityPage.js.map +1 -1
  43. package/dist/auth/pages/SessionsPage.js +95 -98
  44. package/dist/auth/pages/SessionsPage.js.map +1 -1
  45. package/dist/auth/pages/SsoCallbackPage.js +53 -56
  46. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  47. package/dist/auth/pages/SsoConfigPage.js +296 -299
  48. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  49. package/dist/auth/pages/SsoLoginPage.js +65 -68
  50. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  51. package/dist/auth/pages/UserDetailsPage.js +124 -127
  52. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  53. package/dist/auth/pages/UserProfilePage.js +171 -174
  54. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  55. package/dist/auth/pages/UsersPage.js +322 -325
  56. package/dist/auth/pages/UsersPage.js.map +1 -1
  57. package/dist/index.d.ts +1 -0
  58. package/dist/index.js +2 -1
  59. package/dist/shared/hooks/index.d.ts +1 -6
  60. package/dist/shared/hooks/useTr.d.ts +16 -0
  61. package/dist/shared/hooks/useTr.js +14 -0
  62. package/dist/shared/hooks/useTr.js.map +1 -0
  63. package/dist/translations/en.d.ts +806 -248
  64. package/dist/translations/en.js +870 -0
  65. package/dist/translations/en.js.map +1 -0
  66. package/package.json +1 -1
@@ -1,18 +1,15 @@
1
- import { useEffect as e, useId as t, useMemo as n, useState as r } from "react";
2
- import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell";
1
+ import { useTr as e } from "../../../shared/hooks/useTr.js";
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
4
  //#region 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
- let { t: h } = i(), g = (e, t) => {
8
- let n = h(e);
9
- return n === e ? t : n;
10
- }, _ = t(), v = t(), y = t(), [b, x] = r(null), [S, C] = r(null), [w, T] = r(null), E = n(() => {
7
+ let h = e(), g = n(), _ = n(), v = n(), [y, b] = i(null), [x, S] = i(null), [C, w] = i(null), T = r(() => {
11
8
  let e = /* @__PURE__ */ new Map();
12
9
  return u.forEach((t) => {
13
10
  let n = t.organizationId || "default";
14
11
  e.has(n) || e.set(n, {
15
- name: n === "default" ? "Default" : `Organization ${n.slice(0, 8)}`,
12
+ name: n === "default" ? h("auth.workspaceSelector.defaultOrg", "Default") : `${h("auth.workspaceSelector.organizationPrefix", "Organization")} ${n.slice(0, 8)}`,
16
13
  count: 0
17
14
  }), e.get(n).count++;
18
15
  }), Array.from(e.entries()).map(([e, { name: t, count: n }]) => ({
@@ -20,28 +17,28 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
20
17
  name: t,
21
18
  workspaceCount: n
22
19
  }));
23
- }, [u]), D = n(() => b ? u.filter((e) => (e.organizationId || "default") === b) : u, [u, b]), O = n(() => S ? d.filter((e) => e.workspaceId === S) : [], [d, S]), k = n(() => u.find((e) => e.id === S), [u, S]), A = n(() => d.find((e) => e.id === w), [d, w]);
24
- e(() => {
25
- E.length === 1 && !b && x(E[0].id);
26
- }, [E, b]), e(() => {
27
- D.length === 1 && !S && C(D[0].id);
28
- }, [D, S]);
29
- let j = (e) => {
30
- x(e), C(null), T(null);
20
+ }, [u, h]), E = r(() => y ? u.filter((e) => (e.organizationId || "default") === y) : u, [u, y]), D = r(() => x ? d.filter((e) => e.workspaceId === x) : [], [d, x]), O = r(() => u.find((e) => e.id === x), [u, x]), k = r(() => d.find((e) => e.id === C), [d, C]);
21
+ t(() => {
22
+ T.length === 1 && !y && b(T[0].id);
23
+ }, [T, y]), t(() => {
24
+ E.length === 1 && !x && S(E[0].id);
25
+ }, [E, x]);
26
+ let A = (e) => {
27
+ b(e), S(null), w(null);
28
+ }, j = (e) => {
29
+ S(e), w(null);
31
30
  }, M = (e) => {
32
- C(e), T(null);
33
- }, N = (e) => {
34
- T(e);
31
+ w(e);
32
+ }, N = () => {
33
+ O && f(O, k);
35
34
  }, P = () => {
36
- k && f(k, A);
37
- }, F = () => {
38
- x(null), C(null), T(null), l();
39
- }, I = S !== null;
35
+ b(null), S(null), w(null), l();
36
+ }, F = x !== null;
40
37
  return c ? /* @__PURE__ */ o("div", {
41
38
  className: "fixed inset-0 z-50 flex items-center justify-center",
42
39
  children: [/* @__PURE__ */ a("div", {
43
40
  className: "absolute inset-0 bg-black/50",
44
- onClick: F,
41
+ onClick: P,
45
42
  "aria-hidden": "true"
46
43
  }), /* @__PURE__ */ o("div", {
47
44
  className: "relative bg-bg-surface rounded-2xl shadow-xl max-w-2xl w-full mx-4 p-6 space-y-6",
@@ -50,26 +47,26 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
50
47
  className: "space-y-2",
51
48
  children: [/* @__PURE__ */ a("h2", {
52
49
  className: "text-2xl font-semibold text-text-primary",
53
- children: g("auth.workspaceSelector.title", "Select Your Workspace")
50
+ children: h("auth.workspaceSelector.title", "Select Your Workspace")
54
51
  }), /* @__PURE__ */ a("p", {
55
52
  className: "text-sm text-text-secondary",
56
- children: g("auth.workspaceSelector.subtitle", "Choose the workspace you want to work in. You can switch workspaces later from the header.")
53
+ children: h("auth.workspaceSelector.subtitle", "Choose the workspace you want to work in. You can switch workspaces later from the header.")
57
54
  })]
58
55
  }),
59
56
  m && /* @__PURE__ */ o("div", {
60
57
  className: "rounded-lg bg-status-error-bg-subtle p-4 text-sm text-status-error-text",
61
58
  children: [/* @__PURE__ */ a("p", {
62
59
  className: "font-medium",
63
- children: g("auth.workspaceSelector.error", "Error")
60
+ children: h("auth.workspaceSelector.error", "Error")
64
61
  }), /* @__PURE__ */ a("p", { children: m })]
65
62
  }),
66
63
  /* @__PURE__ */ o("div", {
67
64
  className: "space-y-4",
68
65
  children: [
69
- E.length > 1 && /* @__PURE__ */ o("div", {
66
+ T.length > 1 && /* @__PURE__ */ o("div", {
70
67
  className: "space-y-2",
71
68
  children: [/* @__PURE__ */ a("label", {
72
- htmlFor: _,
69
+ htmlFor: g,
73
70
  className: "block text-sm font-medium text-text-secondary",
74
71
  children: /* @__PURE__ */ o("span", {
75
72
  className: "flex items-center gap-2",
@@ -85,33 +82,30 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
85
82
  strokeWidth: 2,
86
83
  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"
87
84
  })
88
- }), "Organization"]
85
+ }), h("auth.workspaceSelector.organization", "Organization")]
89
86
  })
90
87
  }), /* @__PURE__ */ o("select", {
91
- id: _,
92
- value: b || "",
93
- onChange: (e) => j(e.target.value),
88
+ id: g,
89
+ value: y || "",
90
+ onChange: (e) => A(e.target.value),
94
91
  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",
95
92
  children: [/* @__PURE__ */ a("option", {
96
93
  value: "",
97
- children: g("auth.workspaceSelector.selectOrg", "Select organization...")
98
- }), E.map((e) => /* @__PURE__ */ o("option", {
94
+ children: h("auth.workspaceSelector.selectOrg", "Select organization...")
95
+ }), T.map((e) => /* @__PURE__ */ a("option", {
99
96
  value: e.id,
100
- children: [
101
- e.name,
102
- " (",
103
- e.workspaceCount,
104
- " workspace",
105
- e.workspaceCount === 1 ? "" : "s",
106
- ")"
107
- ]
97
+ children: h("auth.workspaceSelector.orgOption", "{{name}} ({{count}} workspace{{plural}})", {
98
+ name: e.name,
99
+ count: e.workspaceCount,
100
+ plural: e.workspaceCount === 1 ? "" : "s"
101
+ })
108
102
  }, e.id))]
109
103
  })]
110
104
  }),
111
105
  /* @__PURE__ */ o("div", {
112
106
  className: "space-y-2",
113
107
  children: [/* @__PURE__ */ a("label", {
114
- htmlFor: v,
108
+ htmlFor: _,
115
109
  className: "block text-sm font-medium text-text-secondary",
116
110
  children: /* @__PURE__ */ o("span", {
117
111
  className: "flex items-center gap-2",
@@ -127,18 +121,18 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
127
121
  strokeWidth: 2,
128
122
  d: "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
129
123
  })
130
- }), "Workspace"]
124
+ }), h("auth.workspaceSelector.workspace", "Workspace")]
131
125
  })
132
126
  }), /* @__PURE__ */ o("select", {
133
- id: v,
134
- value: S || "",
135
- onChange: (e) => M(e.target.value),
136
- disabled: !b && E.length > 1,
127
+ id: _,
128
+ value: x || "",
129
+ onChange: (e) => j(e.target.value),
130
+ disabled: !y && T.length > 1,
137
131
  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",
138
132
  children: [/* @__PURE__ */ a("option", {
139
133
  value: "",
140
- children: g("auth.workspaceSelector.selectWorkspace", "Select workspace...")
141
- }), D.map((e) => /* @__PURE__ */ o("option", {
134
+ children: h("auth.workspaceSelector.selectWorkspace", "Select workspace...")
135
+ }), E.map((e) => /* @__PURE__ */ o("option", {
142
136
  value: e.id,
143
137
  children: [
144
138
  e.name,
@@ -149,10 +143,10 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
149
143
  }, e.id))]
150
144
  })]
151
145
  }),
152
- O.length > 0 && /* @__PURE__ */ o("div", {
146
+ D.length > 0 && /* @__PURE__ */ o("div", {
153
147
  className: "space-y-2",
154
148
  children: [/* @__PURE__ */ a("label", {
155
- htmlFor: y,
149
+ htmlFor: v,
156
150
  className: "block text-sm font-medium text-text-secondary",
157
151
  children: /* @__PURE__ */ o("span", {
158
152
  className: "flex items-center gap-2",
@@ -168,27 +162,27 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
168
162
  strokeWidth: 2,
169
163
  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"
170
164
  })
171
- }), "Project (Optional)"]
165
+ }), h("auth.workspaceSelector.projectOptional", "Project (Optional)")]
172
166
  })
173
167
  }), /* @__PURE__ */ o("select", {
174
- id: y,
175
- value: w || "",
176
- onChange: (e) => N(e.target.value),
168
+ id: v,
169
+ value: C || "",
170
+ onChange: (e) => M(e.target.value),
177
171
  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",
178
172
  children: [/* @__PURE__ */ a("option", {
179
173
  value: "",
180
- children: g("auth.workspaceSelector.noProject", "No project selected")
181
- }), O.map((e) => /* @__PURE__ */ a("option", {
174
+ children: h("auth.workspaceSelector.noProject", "No project selected")
175
+ }), D.map((e) => /* @__PURE__ */ a("option", {
182
176
  value: e.id,
183
177
  children: e.name
184
178
  }, e.id))]
185
179
  })]
186
180
  }),
187
- k && /* @__PURE__ */ o("div", {
181
+ O && /* @__PURE__ */ o("div", {
188
182
  className: "rounded-lg bg-bg-sunken p-4 space-y-2",
189
183
  children: [/* @__PURE__ */ a("p", {
190
184
  className: "text-sm font-medium text-text-primary",
191
- children: g("auth.workspaceSelector.selected", "Selected:")
185
+ children: h("auth.workspaceSelector.selected", "Selected:")
192
186
  }), /* @__PURE__ */ o("div", {
193
187
  className: "text-sm text-text-secondary space-y-1",
194
188
  children: [/* @__PURE__ */ o("div", {
@@ -204,8 +198,8 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
204
198
  strokeWidth: 2,
205
199
  d: "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
206
200
  })
207
- }), /* @__PURE__ */ a("span", { children: k.name })]
208
- }), A && /* @__PURE__ */ o("div", {
201
+ }), /* @__PURE__ */ a("span", { children: O.name })]
202
+ }), k && /* @__PURE__ */ o("div", {
209
203
  className: "flex items-center gap-2",
210
204
  children: [/* @__PURE__ */ a("svg", {
211
205
  className: "size-3",
@@ -218,7 +212,7 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
218
212
  strokeWidth: 2,
219
213
  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"
220
214
  })
221
- }), /* @__PURE__ */ a("span", { children: A.name })]
215
+ }), /* @__PURE__ */ a("span", { children: k.name })]
222
216
  })]
223
217
  })]
224
218
  })
@@ -228,16 +222,16 @@ function c({ isOpen: c, onClose: l, workspaces: u, projects: d = s, onSelect: f,
228
222
  className: "flex items-center justify-end gap-3 pt-4 border-t border-border-subtle",
229
223
  children: [/* @__PURE__ */ a("button", {
230
224
  type: "button",
231
- onClick: F,
225
+ onClick: P,
232
226
  disabled: p,
233
227
  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",
234
- children: "Cancel"
228
+ children: h("auth.workspaceSelector.cancel", "Cancel")
235
229
  }), /* @__PURE__ */ a("button", {
236
230
  type: "button",
237
- onClick: P,
238
- disabled: !I || p,
231
+ onClick: N,
232
+ disabled: !F || p,
239
233
  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",
240
- children: p ? g("auth.workspaceSelector.loading", "Loading...") : g("auth.workspaceSelector.continue", "Continue")
234
+ children: p ? h("auth.workspaceSelector.loading", "Loading...") : h("auth.workspaceSelector.continue", "Continue")
241
235
  })]
242
236
  })
243
237
  ]
@@ -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 { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\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 { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\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 ? \"Default\"\n : `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]);\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 Organization\n </span>\n </label>\n <select\n id={orgSelectId}\n value={selectedOrgId || \"\"}\n onChange={(e) => handleOrgChange(e.target.value)}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.selectOrg\",\n \"Select organization...\",\n )}\n </option>\n {organizations.map((org) => (\n <option key={org.id} value={org.id}>\n {org.name} ({org.workspaceCount} workspace\n {org.workspaceCount !== 1 ? \"s\" : \"\"})\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 Workspace\n </span>\n </label>\n <select\n id={workspaceSelectId}\n value={selectedWorkspaceId || \"\"}\n onChange={(e) => handleWorkspaceChange(e.target.value)}\n disabled={!selectedOrgId && organizations.length > 1}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.selectWorkspace\",\n \"Select workspace...\",\n )}\n </option>\n {filteredWorkspaces.map((workspace) => (\n <option key={workspace.id} value={workspace.id}>\n {workspace.name} ({workspace.slug})\n </option>\n ))}\n </select>\n </div>\n\n {/* Project selection (optional) */}\n {filteredProjects.length > 0 && (\n <div className=\"space-y-2\">\n <label\n htmlFor={projectSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n Project (Optional)\n </span>\n </label>\n <select\n id={projectSelectId}\n value={selectedProjectId || \"\"}\n onChange={(e) => handleProjectChange(e.target.value)}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.noProject\",\n \"No project selected\",\n )}\n </option>\n {filteredProjects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Selection summary */}\n {selectedWorkspace && (\n <div className=\"rounded-lg bg-bg-sunken p-4 space-y-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.workspaceSelector.selected\", \"Selected:\")}\n </p>\n <div className=\"text-sm text-text-secondary space-y-1\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"\n />\n </svg>\n <span>{selectedWorkspace.name}</span>\n </div>\n {selectedProject && (\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n <span>{selectedProject.name}</span>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleContinue}\n disabled={!canContinue || isLoading}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isLoading\n ? tr(\"auth.workspaceSelector.loading\", \"Loading...\")\n : tr(\"auth.workspaceSelector.continue\", \"Continue\")}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WorkspaceSelector.js","names":[],"sources":["../../../../src/auth/components/workspace/WorkspaceSelector.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, useId } from \"react\";\nimport { useTr } from \"../../../shared/hooks/useTr\";\nimport type { Workspace } from \"../../types\";\n\n/**\n * Organization interface (grouped from workspaces)\n */\nexport interface Organization {\n id: string;\n name: string;\n workspaceCount: number;\n}\n\n/**\n * Project interface (optional, for future use)\n */\nexport interface Project {\n id: string;\n name: string;\n workspaceId: string;\n}\n\nexport interface WorkspaceSelectorProps {\n /**\n * Control modal visibility\n */\n isOpen: boolean;\n\n /**\n * Callback when modal is closed without selection\n */\n onClose: () => void;\n\n /**\n * List of workspaces to choose from\n */\n workspaces: Workspace[];\n\n /**\n * Optional list of projects (filtered by workspace)\n */\n projects?: Project[];\n\n /**\n * Callback when workspace is selected and confirmed\n */\n onSelect: (workspace: Workspace, project?: Project) => void;\n\n /**\n * Loading state while issuing workspace token\n */\n isLoading?: boolean;\n\n /**\n * Error message to display\n */\n error?: string | null;\n}\n\n/**\n * WorkspaceSelector Component\n *\n * Multi-level workspace selection modal for use across all Burdenoff products.\n *\n * Features:\n * - Three-tier selection: Organization → Workspace → Project (optional)\n * - Auto-select if only one option at each level\n * - Loading and error states\n * - Clean, accessible UI\n *\n * Usage:\n * ```tsx\n * import { WorkspaceSelector } from '@burdenoff/microfe-auth';\n *\n * <WorkspaceSelector\n * isOpen={showSelector}\n * onClose={() => setShowSelector(false)}\n * workspaces={workspaces}\n * onSelect={async (workspace) => {\n * await issueWorkspaceToken(workspace.id);\n * navigate('/');\n * }}\n * />\n * ```\n */\nconst EMPTY_PROJECTS: readonly Project[] = [];\n\nexport function WorkspaceSelector({\n isOpen,\n onClose,\n workspaces,\n projects = EMPTY_PROJECTS as Project[],\n onSelect,\n isLoading = false,\n error = null,\n}: WorkspaceSelectorProps) {\n const tr = useTr();\n\n // Generated ids for label-control pairing\n const orgSelectId = useId();\n const workspaceSelectId = useId();\n const projectSelectId = useId();\n // Selection state\n const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string | null>(\n null,\n );\n const [selectedProjectId, setSelectedProjectId] = useState<string | null>(\n null,\n );\n\n // Group workspaces by organization\n const organizations = useMemo<Organization[]>(() => {\n const orgMap = new Map<string, { name: string; count: number }>();\n\n workspaces.forEach((workspace) => {\n const orgId = workspace.organizationId || \"default\";\n if (!orgMap.has(orgId)) {\n // Use org ID for name since workspace doesn't have organizationName\n orgMap.set(orgId, {\n name:\n orgId === \"default\"\n ? tr(\"auth.workspaceSelector.defaultOrg\", \"Default\")\n : `${tr(\"auth.workspaceSelector.organizationPrefix\", \"Organization\")} ${orgId.slice(0, 8)}`,\n count: 0,\n });\n }\n orgMap.get(orgId)!.count++;\n });\n\n return Array.from(orgMap.entries()).map(([id, { name, count }]) => ({\n id,\n name,\n workspaceCount: count,\n }));\n }, [workspaces, tr]);\n\n // Filter workspaces by selected organization\n const filteredWorkspaces = useMemo(() => {\n if (!selectedOrgId) return workspaces;\n return workspaces.filter(\n (w) => (w.organizationId || \"default\") === selectedOrgId,\n );\n }, [workspaces, selectedOrgId]);\n\n // Filter projects by selected workspace\n const filteredProjects = useMemo(() => {\n if (!selectedWorkspaceId) return [];\n return projects.filter((p) => p.workspaceId === selectedWorkspaceId);\n }, [projects, selectedWorkspaceId]);\n\n // Get selected workspace object\n const selectedWorkspace = useMemo(() => {\n return workspaces.find((w) => w.id === selectedWorkspaceId);\n }, [workspaces, selectedWorkspaceId]);\n\n // Get selected project object\n const selectedProject = useMemo(() => {\n return projects.find((p) => p.id === selectedProjectId);\n }, [projects, selectedProjectId]);\n\n // Auto-select if only one organization\n useEffect(() => {\n if (organizations.length === 1 && !selectedOrgId) {\n setSelectedOrgId(organizations[0].id);\n }\n }, [organizations, selectedOrgId]);\n\n // Auto-select if only one workspace\n useEffect(() => {\n if (filteredWorkspaces.length === 1 && !selectedWorkspaceId) {\n setSelectedWorkspaceId(filteredWorkspaces[0].id);\n }\n }, [filteredWorkspaces, selectedWorkspaceId]);\n\n // Handle organization selection\n const handleOrgChange = (orgId: string) => {\n setSelectedOrgId(orgId);\n setSelectedWorkspaceId(null);\n setSelectedProjectId(null);\n };\n\n // Handle workspace selection\n const handleWorkspaceChange = (workspaceId: string) => {\n setSelectedWorkspaceId(workspaceId);\n setSelectedProjectId(null);\n };\n\n // Handle project selection\n const handleProjectChange = (projectId: string) => {\n setSelectedProjectId(projectId);\n };\n\n // Handle continue button\n const handleContinue = () => {\n if (!selectedWorkspace) return;\n onSelect(selectedWorkspace, selectedProject);\n };\n\n // Handle cancel\n const handleCancel = () => {\n setSelectedOrgId(null);\n setSelectedWorkspaceId(null);\n setSelectedProjectId(null);\n onClose();\n };\n\n // Check if continue button should be enabled\n const canContinue = selectedWorkspaceId !== null;\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div\n className=\"absolute inset-0 bg-black/50\"\n onClick={handleCancel}\n aria-hidden=\"true\"\n />\n\n {/* Modal */}\n <div className=\"relative bg-bg-surface rounded-2xl shadow-xl max-w-2xl w-full mx-4 p-6 space-y-6\">\n {/* Header */}\n <div className=\"space-y-2\">\n <h2 className=\"text-2xl font-semibold text-text-primary\">\n {tr(\"auth.workspaceSelector.title\", \"Select Your Workspace\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"auth.workspaceSelector.subtitle\",\n \"Choose the workspace you want to work in. You can switch workspaces later from the header.\",\n )}\n </p>\n </div>\n\n {/* Error display */}\n {error && (\n <div className=\"rounded-lg bg-status-error-bg-subtle p-4 text-sm text-status-error-text\">\n <p className=\"font-medium\">\n {tr(\"auth.workspaceSelector.error\", \"Error\")}\n </p>\n <p>{error}</p>\n </div>\n )}\n\n {/* Form */}\n <div className=\"space-y-4\">\n {/* Organization selection */}\n {organizations.length > 1 && (\n <div className=\"space-y-2\">\n <label\n htmlFor={orgSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n {tr(\"auth.workspaceSelector.organization\", \"Organization\")}\n </span>\n </label>\n <select\n id={orgSelectId}\n value={selectedOrgId || \"\"}\n onChange={(e) => handleOrgChange(e.target.value)}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.selectOrg\",\n \"Select organization...\",\n )}\n </option>\n {organizations.map((org) => (\n <option key={org.id} value={org.id}>\n {tr(\n \"auth.workspaceSelector.orgOption\",\n \"{{name}} ({{count}} workspace{{plural}})\",\n {\n name: org.name,\n count: org.workspaceCount,\n plural: org.workspaceCount !== 1 ? \"s\" : \"\",\n },\n )}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Workspace selection */}\n <div className=\"space-y-2\">\n <label\n htmlFor={workspaceSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"\n />\n </svg>\n {tr(\"auth.workspaceSelector.workspace\", \"Workspace\")}\n </span>\n </label>\n <select\n id={workspaceSelectId}\n value={selectedWorkspaceId || \"\"}\n onChange={(e) => handleWorkspaceChange(e.target.value)}\n disabled={!selectedOrgId && organizations.length > 1}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.selectWorkspace\",\n \"Select workspace...\",\n )}\n </option>\n {filteredWorkspaces.map((workspace) => (\n <option key={workspace.id} value={workspace.id}>\n {workspace.name} ({workspace.slug})\n </option>\n ))}\n </select>\n </div>\n\n {/* Project selection (optional) */}\n {filteredProjects.length > 0 && (\n <div className=\"space-y-2\">\n <label\n htmlFor={projectSelectId}\n className=\"block text-sm font-medium text-text-secondary\"\n >\n <span className=\"flex items-center gap-2\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n {tr(\n \"auth.workspaceSelector.projectOptional\",\n \"Project (Optional)\",\n )}\n </span>\n </label>\n <select\n id={projectSelectId}\n value={selectedProjectId || \"\"}\n onChange={(e) => handleProjectChange(e.target.value)}\n className=\"w-full rounded-lg border border-border-default bg-bg-surface px-4 py-2.5 text-sm text-text-primary focus:border-primary focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"\">\n {tr(\n \"auth.workspaceSelector.noProject\",\n \"No project selected\",\n )}\n </option>\n {filteredProjects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Selection summary */}\n {selectedWorkspace && (\n <div className=\"rounded-lg bg-bg-sunken p-4 space-y-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.workspaceSelector.selected\", \"Selected:\")}\n </p>\n <div className=\"text-sm text-text-secondary space-y-1\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"\n />\n </svg>\n <span>{selectedWorkspace.name}</span>\n </div>\n {selectedProject && (\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-3\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"\n />\n </svg>\n <span>{selectedProject.name}</span>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {tr(\"auth.workspaceSelector.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleContinue}\n disabled={!canContinue || isLoading}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isLoading\n ? tr(\"auth.workspaceSelector.loading\", \"Loading...\")\n : tr(\"auth.workspaceSelector.continue\", \"Continue\")}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}