@burdenoff/microfe-workspaces 2026.608.1 → 2026.623.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.
@@ -1,52 +1,50 @@
1
1
  import { useWorkspacesContext as e } from "../providers/WorkspacesProvider.js";
2
2
  import { tWithFallback as t } from "../utils/i18n.js";
3
- import { useWorkspacesPageView as ee } from "../hooks/useWorkspacesPageView.js";
4
- import { useCreateWorkspace as n } from "../hooks/useWorkspaceMutations.js";
3
+ import { useWorkspacesPageView as n } from "../hooks/useWorkspacesPageView.js";
4
+ import { useCreateWorkspace as ee } from "../hooks/useWorkspaceMutations.js";
5
5
  import { useMyOrganizations as r, useMyTenants as i } from "../hooks/useTenants.js";
6
6
  import { generateSlug as a } from "../utils/slug.js";
7
- import { useOrgsWithPermission as o } from "../hooks/useOrgsWithPermission.js";
8
- import { useEffect as s, useMemo as c } from "react";
9
- import { AlertCircle as l, ArrowLeft as u } from "lucide-react";
10
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
11
- import { Button as p, Card as te, CardContent as m, CardDescription as h, CardHeader as g, CardTitle as _, Skeleton as v } from "@burdenoff/fe-libs/ui";
12
- import { Link as y, useNavigate as b } from "react-router-dom";
13
- import { useUpgradePrompt as x } from "@burdenoff/fe-libs/shared/hooks";
14
- import { AccessDenied as S, UpgradeInlineBanner as C } from "@burdenoff/fe-libs/shared/components";
15
- import { useI18n as w } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
16
- import { useForm as T, z as E } from "@burdenoff/fe-libs/form";
7
+ import { useOrgsWithPermission as te } from "../hooks/useOrgsWithPermission.js";
8
+ import { useEffect as o, useMemo as s } from "react";
9
+ import { AlertCircle as c, ArrowLeft as l } from "lucide-react";
10
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
11
+ import { Button as f, Card as p, CardContent as m, CardDescription as ne, CardHeader as h, CardTitle as g, Skeleton as _ } from "@burdenoff/fe-libs/ui";
12
+ import { Link as v, useNavigate as y } from "react-router-dom";
13
+ import { useUpgradePrompt as b } from "@burdenoff/fe-libs/shared/hooks";
14
+ import { AccessDenied as x, UpgradeInlineBanner as S } from "@burdenoff/fe-libs/shared/components";
15
+ import { useI18n as C } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
16
+ import { useForm as w, z as T } from "@burdenoff/fe-libs/form";
17
17
  //#region src/pages/CreateWorkspacePage.tsx
18
- function D() {
19
- ee("workspaces.create");
20
- let { t: D } = w(), { isFree: O, approachingLimits: k, plan: ne } = x({ resource: "workspaces" }), A = k[0], j = !!A && typeof A.limit == "number" && A.current >= A.limit, re = O && !!A, M = c(() => E.object({
21
- name: E.string().min(2, t(D, "createWorkspace.validation.nameMin", "Name must be at least 2 characters")).max(100),
22
- slug: E.string().min(2, t(D, "createWorkspace.validation.slugMin", "Slug must be at least 2 characters")).max(50).regex(/^[a-z0-9-]+$/, t(D, "createWorkspace.validation.slugFormat", "Slug can only contain lowercase letters, numbers, and hyphens")),
23
- tenantId: E.string().min(1, t(D, "createWorkspace.validation.tenantRequired", "Please select a tenant")),
24
- organizationId: E.string().min(1, t(D, "createWorkspace.validation.organizationRequired", "Please select an organization")),
25
- type: E.enum(["TEAM", "PERSONAL"]).optional()
26
- }), [D]), N = b(), { basePath: P = "/" } = e(), { mutate: F, isPending: I } = n(), { data: L, isLoading: R, error: z } = i(), { data: B, isLoading: V, error: H } = r(), U = c(() => L?.items ?? [], [L]), W = c(() => B?.items ?? [], [B]), G = c(() => U.find((e) => e.type === "SHARED" || e.slug === "burdenoff-shared")?.id ?? U[0]?.id ?? "", [U]), K = T({
18
+ function E() {
19
+ n("workspaces.create");
20
+ let { t: E } = C(), { isFree: D, approachingLimits: O, plan: k } = b({ resource: "workspaces" }), A = O[0], j = !!A && typeof A.limit == "number" && A.current >= A.limit, re = D && !!A, M = s(() => T.object({
21
+ name: T.string().min(2, t(E, "createWorkspace.validation.nameMin", "Name must be at least 2 characters")).max(100),
22
+ slug: T.string().min(2, t(E, "createWorkspace.validation.slugMin", "Slug must be at least 2 characters")).max(50).regex(/^[a-z0-9-]+$/, t(E, "createWorkspace.validation.slugFormat", "Slug can only contain lowercase letters, numbers, and hyphens")),
23
+ tenantId: T.string().min(1, t(E, "createWorkspace.validation.tenantRequired", "Please select a tenant")),
24
+ organizationId: T.string().min(1, t(E, "createWorkspace.validation.organizationRequired", "Please select an organization"))
25
+ }), [E]), N = y(), { basePath: P = "/" } = e(), { mutate: F, isPending: I } = ee(), { data: L, isLoading: R, error: z } = i(), { data: B, isLoading: V, error: H } = r(), U = s(() => L?.items ?? [], [L]), W = s(() => B?.items ?? [], [B]), G = s(() => U.find((e) => e.type === "SHARED" || e.slug === "burdenoff-shared")?.id ?? U[0]?.id ?? "", [U]), K = w({
27
26
  schema: M,
28
27
  defaultValues: {
29
28
  name: "",
30
29
  slug: "",
31
30
  tenantId: "",
32
- organizationId: "",
33
- type: "TEAM"
31
+ organizationId: ""
34
32
  }
35
- }), q = K.watch("tenantId"), J = K.watch("name"), { allowedIds: Y, loading: X, isError: Z } = o(c(() => W.map((e) => e.id), [W]), "workspace", "create"), Q = c(() => q ? W.filter((e) => e.tenantId === q) : [], [q, W]), $ = c(() => Z ? Q : Q.filter((e) => Y.has(e.id)), [
33
+ }), q = K.watch("tenantId"), J = K.watch("name"), { allowedIds: Y, loading: X, isError: Z } = te(s(() => W.map((e) => e.id), [W]), "workspace", "create"), Q = s(() => q ? W.filter((e) => e.tenantId === q) : [], [q, W]), $ = s(() => Z ? Q : Q.filter((e) => Y.has(e.id)), [
36
34
  Q,
37
35
  Y,
38
36
  Z
39
37
  ]);
40
- s(() => {
38
+ o(() => {
41
39
  K.setValue("slug", J ? a(J) : "");
42
- }, [J, K]), s(() => {
40
+ }, [J, K]), o(() => {
43
41
  K.setValue("organizationId", "");
44
- }, [q, K]), s(() => {
42
+ }, [q, K]), o(() => {
45
43
  !K.getValues("tenantId") && G && K.setValue("tenantId", G, {
46
44
  shouldDirty: !1,
47
45
  shouldTouch: !1
48
46
  });
49
- }, [G, K]), s(() => {
47
+ }, [G, K]), o(() => {
50
48
  let e = K.getValues("organizationId");
51
49
  !$.some((t) => t.id === e) && $[0]?.id && K.setValue("organizationId", $[0].id, {
52
50
  shouldDirty: !1,
@@ -58,222 +56,195 @@ function D() {
58
56
  name: e.name,
59
57
  slug: e.slug,
60
58
  tenantId: e.tenantId,
61
- organizationId: e.organizationId,
62
- type: e.type
59
+ organizationId: e.organizationId
63
60
  }, { onSuccess: (e) => {
64
61
  N(`${P}/${e.id}`);
65
62
  } });
66
63
  }, ae = Y.size > 0, oe = R || V || X, se = !z && !H && W.length > 0;
67
- return !oe && !Z && se && !ae ? /* @__PURE__ */ f("div", {
64
+ return !oe && !Z && se && !ae ? /* @__PURE__ */ d("div", {
68
65
  className: "p-6",
69
- children: [/* @__PURE__ */ f(y, {
66
+ children: [/* @__PURE__ */ d(v, {
70
67
  to: `${P}/list`,
71
68
  className: "inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-6",
72
- children: [/* @__PURE__ */ d(u, { size: 20 }), t(D, "createWorkspace.backToWorkspaces", "Back to Workspaces")]
73
- }), /* @__PURE__ */ d(S, {
69
+ children: [/* @__PURE__ */ u(l, { size: 20 }), t(E, "createWorkspace.backToWorkspaces", "Back to Workspaces")]
70
+ }), /* @__PURE__ */ u(x, {
74
71
  variant: "card",
75
- title: t(D, "createWorkspace.permissionRequiredTitle", "Permission Required"),
76
- description: t(D, "createWorkspace.permissionRequiredDescription", "You don't have permission to create workspaces in any of your organizations."),
77
- details: t(D, "createWorkspace.permissionRequiredDetails", "Required: workspace:create"),
72
+ title: t(E, "createWorkspace.permissionRequiredTitle", "Permission Required"),
73
+ description: t(E, "createWorkspace.permissionRequiredDescription", "You don't have permission to create workspaces in any of your organizations."),
74
+ details: t(E, "createWorkspace.permissionRequiredDetails", "Required: workspace:create"),
78
75
  primaryAction: {
79
- label: t(D, "createWorkspace.goBack", "Go Back"),
76
+ label: t(E, "createWorkspace.goBack", "Go Back"),
80
77
  onClick: () => N(`${P}/list`)
81
78
  }
82
79
  })]
83
- }) : /* @__PURE__ */ f("div", {
80
+ }) : /* @__PURE__ */ d("div", {
84
81
  className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
85
82
  children: [
86
- /* @__PURE__ */ f(y, {
83
+ /* @__PURE__ */ d(v, {
87
84
  to: `${P}/list`,
88
85
  className: "inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors",
89
- children: [/* @__PURE__ */ d(u, { size: 20 }), t(D, "createWorkspace.backToWorkspaces", "Back to Workspaces")]
86
+ children: [/* @__PURE__ */ u(l, { size: 20 }), t(E, "createWorkspace.backToWorkspaces", "Back to Workspaces")]
90
87
  }),
91
- re && /* @__PURE__ */ d("div", {
88
+ re && /* @__PURE__ */ u("div", {
92
89
  className: "max-w-2xl mx-auto w-full",
93
- children: /* @__PURE__ */ d(C, {
90
+ children: /* @__PURE__ */ u(S, {
94
91
  resource: "workspaces",
95
92
  current: A.current,
96
93
  limit: A.limit ?? void 0,
97
94
  severity: j ? "critical" : void 0,
98
- message: j ? t(D, "createWorkspace.upgradeCapMessage", `You've used all workspaces in the ${ne?.planName ?? "free"} plan.`) : t(D, "createWorkspace.upgradeApproachingMessage", "Upgrade to create more workspaces.")
95
+ message: j ? t(E, "createWorkspace.upgradeCapMessage", `You've used all workspaces in the ${k?.planName ?? "free"} plan.`) : t(E, "createWorkspace.upgradeApproachingMessage", "Upgrade to create more workspaces.")
99
96
  })
100
97
  }),
101
- /* @__PURE__ */ f(te, {
98
+ /* @__PURE__ */ d(p, {
102
99
  className: "max-w-2xl mx-auto",
103
- children: [/* @__PURE__ */ f(g, { children: [/* @__PURE__ */ d(_, { children: t(D, "createWorkspace.title", "Create New Workspace") }), /* @__PURE__ */ d(h, { children: t(D, "createWorkspace.subtitle", "Set up a new workspace for your team or personal use") })] }), /* @__PURE__ */ f(m, { children: [Z && /* @__PURE__ */ f("div", {
100
+ children: [/* @__PURE__ */ d(h, { children: [/* @__PURE__ */ u(g, { children: t(E, "createWorkspace.title", "Create New Workspace") }), /* @__PURE__ */ u(ne, { children: t(E, "createWorkspace.subtitle", "Set up a new workspace for your team") })] }), /* @__PURE__ */ d(m, { children: [Z && /* @__PURE__ */ d("div", {
104
101
  role: "alert",
105
102
  className: "mb-4 flex items-start gap-2 rounded-md border border-warning/30 bg-warning/10 p-3 text-sm text-foreground",
106
- children: [/* @__PURE__ */ d(l, {
103
+ children: [/* @__PURE__ */ u(c, {
107
104
  size: 16,
108
105
  className: "mt-0.5 shrink-0 text-warning"
109
- }), /* @__PURE__ */ d("span", { children: t(D, "createWorkspace.permissionCheckUnavailable", "We couldn't verify your workspace-create permissions just now, so the organization list isn't filtered. If you submit and don't have access, the request will be rejected and you can retry.") })]
110
- }), /* @__PURE__ */ f("form", {
106
+ }), /* @__PURE__ */ u("span", { children: t(E, "createWorkspace.permissionCheckUnavailable", "We couldn't verify your workspace-create permissions just now, so the organization list isn't filtered. If you submit and don't have access, the request will be rejected and you can retry.") })]
107
+ }), /* @__PURE__ */ d("form", {
111
108
  onSubmit: K.handleSubmit(ie),
112
109
  className: "space-y-6",
113
110
  children: [
114
- /* @__PURE__ */ f("div", {
111
+ /* @__PURE__ */ d("div", {
115
112
  className: "space-y-2",
116
113
  children: [
117
- /* @__PURE__ */ d("label", {
114
+ /* @__PURE__ */ u("label", {
118
115
  htmlFor: "workspace-name",
119
116
  className: "text-sm font-medium text-foreground",
120
- children: t(D, "createWorkspace.nameLabel", "Workspace Name *")
117
+ children: t(E, "createWorkspace.nameLabel", "Workspace Name *")
121
118
  }),
122
- /* @__PURE__ */ d("input", {
119
+ /* @__PURE__ */ u("input", {
123
120
  id: "workspace-name",
124
- placeholder: t(D, "createWorkspace.namePlaceholder", "My Workspace"),
121
+ placeholder: t(E, "createWorkspace.namePlaceholder", "My Workspace"),
125
122
  className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground",
126
123
  ...K.register("name")
127
124
  }),
128
- /* @__PURE__ */ d("p", {
125
+ /* @__PURE__ */ u("p", {
129
126
  className: "text-xs text-muted-foreground",
130
- children: t(D, "createWorkspace.nameHint", "A descriptive name for your workspace")
127
+ children: t(E, "createWorkspace.nameHint", "A descriptive name for your workspace")
131
128
  }),
132
- K.formState.errors.name?.message && /* @__PURE__ */ d("p", {
129
+ K.formState.errors.name?.message && /* @__PURE__ */ u("p", {
133
130
  className: "text-sm text-destructive",
134
131
  children: K.formState.errors.name.message
135
132
  })
136
133
  ]
137
134
  }),
138
- /* @__PURE__ */ f("div", {
135
+ /* @__PURE__ */ d("div", {
139
136
  className: "space-y-2",
140
137
  children: [
141
- /* @__PURE__ */ d("label", {
138
+ /* @__PURE__ */ u("label", {
142
139
  htmlFor: "workspace-slug",
143
140
  className: "text-sm font-medium text-foreground",
144
- children: t(D, "createWorkspace.slugLabel", "Slug *")
141
+ children: t(E, "createWorkspace.slugLabel", "Slug *")
145
142
  }),
146
- /* @__PURE__ */ d("input", {
143
+ /* @__PURE__ */ u("input", {
147
144
  id: "workspace-slug",
148
- placeholder: t(D, "createWorkspace.slugPlaceholder", "my-workspace"),
145
+ placeholder: t(E, "createWorkspace.slugPlaceholder", "my-workspace"),
149
146
  className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground",
150
147
  ...K.register("slug")
151
148
  }),
152
- /* @__PURE__ */ d("p", {
149
+ /* @__PURE__ */ u("p", {
153
150
  className: "text-xs text-muted-foreground",
154
- children: t(D, "createWorkspace.slugHint", "URL-friendly identifier (auto-generated, but you can edit it)")
151
+ children: t(E, "createWorkspace.slugHint", "URL-friendly identifier (auto-generated, but you can edit it)")
155
152
  }),
156
- K.formState.errors.slug?.message && /* @__PURE__ */ d("p", {
153
+ K.formState.errors.slug?.message && /* @__PURE__ */ u("p", {
157
154
  className: "text-sm text-destructive",
158
155
  children: K.formState.errors.slug.message
159
156
  })
160
157
  ]
161
158
  }),
162
- /* @__PURE__ */ f("div", {
159
+ U.length === 1 ? null : /* @__PURE__ */ d("div", {
163
160
  className: "space-y-2",
164
161
  children: [
165
- /* @__PURE__ */ d("label", {
162
+ /* @__PURE__ */ u("label", {
166
163
  htmlFor: "workspace-tenant",
167
164
  className: "text-sm font-medium text-foreground",
168
- children: t(D, "createWorkspace.tenantLabel", "Tenant *")
165
+ children: t(E, "createWorkspace.tenantLabel", "Tenant *")
169
166
  }),
170
- R ? /* @__PURE__ */ d(v, { className: "h-10 w-full" }) : z ? /* @__PURE__ */ f("div", {
167
+ R ? /* @__PURE__ */ u(_, { className: "h-10 w-full" }) : z ? /* @__PURE__ */ d("div", {
171
168
  className: "flex items-center gap-2 text-sm text-destructive",
172
- children: [/* @__PURE__ */ d(l, { size: 16 }), t(D, "createWorkspace.failedToLoadTenants", "Failed to load tenants")]
173
- }) : U.length === 0 ? /* @__PURE__ */ d("div", {
169
+ children: [/* @__PURE__ */ u(c, { size: 16 }), t(E, "createWorkspace.failedToLoadTenants", "Failed to load tenants")]
170
+ }) : U.length === 0 ? /* @__PURE__ */ u("div", {
174
171
  className: "text-sm text-muted-foreground",
175
- children: t(D, "createWorkspace.noTenantsAvailable", "No tenants available. Contact your administrator.")
176
- }) : /* @__PURE__ */ f("select", {
172
+ children: t(E, "createWorkspace.noTenantsAvailable", "No tenants available. Contact your administrator.")
173
+ }) : /* @__PURE__ */ d("select", {
177
174
  id: "workspace-tenant",
178
175
  className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground",
179
176
  ...K.register("tenantId"),
180
- children: [/* @__PURE__ */ d("option", {
177
+ children: [/* @__PURE__ */ u("option", {
181
178
  value: "",
182
- children: t(D, "createWorkspace.selectTenantPlaceholder", "Select a tenant")
183
- }), U.map((e) => /* @__PURE__ */ d("option", {
179
+ children: t(E, "createWorkspace.selectTenantPlaceholder", "Select a tenant")
180
+ }), U.map((e) => /* @__PURE__ */ u("option", {
184
181
  value: e.id,
185
182
  children: e.name
186
183
  }, e.id))]
187
184
  }),
188
- /* @__PURE__ */ d("p", {
185
+ /* @__PURE__ */ u("p", {
189
186
  className: "text-xs text-muted-foreground",
190
- children: t(D, "createWorkspace.tenantHint", "Select which tenant this workspace belongs to")
187
+ children: t(E, "createWorkspace.tenantHint", "Select which tenant this workspace belongs to")
191
188
  }),
192
- K.formState.errors.tenantId?.message && /* @__PURE__ */ d("p", {
189
+ K.formState.errors.tenantId?.message && /* @__PURE__ */ u("p", {
193
190
  className: "text-sm text-destructive",
194
191
  children: K.formState.errors.tenantId.message
195
192
  })
196
193
  ]
197
194
  }),
198
- /* @__PURE__ */ f("div", {
195
+ /* @__PURE__ */ d("div", {
199
196
  className: "space-y-2",
200
197
  children: [
201
- /* @__PURE__ */ d("label", {
198
+ /* @__PURE__ */ u("label", {
202
199
  htmlFor: "workspace-organization",
203
200
  className: "text-sm font-medium text-foreground",
204
- children: t(D, "createWorkspace.organizationLabel", "Organization *")
201
+ children: t(E, "createWorkspace.organizationLabel", "Organization *")
205
202
  }),
206
- V || X ? /* @__PURE__ */ d(v, { className: "h-10 w-full" }) : H ? /* @__PURE__ */ f("div", {
203
+ V || X ? /* @__PURE__ */ u(_, { className: "h-10 w-full" }) : H ? /* @__PURE__ */ d("div", {
207
204
  className: "flex items-center gap-2 text-sm text-destructive",
208
- children: [/* @__PURE__ */ d(l, { size: 16 }), t(D, "createWorkspace.failedToLoadOrganizations", "Failed to load organizations")]
209
- }) : q ? $.length === 0 ? /* @__PURE__ */ d("div", {
205
+ children: [/* @__PURE__ */ u(c, { size: 16 }), t(E, "createWorkspace.failedToLoadOrganizations", "Failed to load organizations")]
206
+ }) : q ? $.length === 0 ? /* @__PURE__ */ u("div", {
210
207
  className: "text-sm text-muted-foreground",
211
- children: t(D, "createWorkspace.noOrganizationsFound", "No organizations found for this tenant")
212
- }) : /* @__PURE__ */ f("select", {
208
+ children: t(E, "createWorkspace.noOrganizationsFound", "No organizations found for this tenant")
209
+ }) : /* @__PURE__ */ d("select", {
213
210
  id: "workspace-organization",
214
211
  className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground",
215
212
  disabled: !q,
216
213
  ...K.register("organizationId"),
217
- children: [/* @__PURE__ */ d("option", {
214
+ children: [/* @__PURE__ */ u("option", {
218
215
  value: "",
219
- children: t(D, "createWorkspace.selectOrganizationPlaceholder", "Select an organization")
220
- }), $.map((e) => /* @__PURE__ */ d("option", {
216
+ children: t(E, "createWorkspace.selectOrganizationPlaceholder", "Select an organization")
217
+ }), $.map((e) => /* @__PURE__ */ u("option", {
221
218
  value: e.id,
222
219
  children: e.name
223
220
  }, e.id))]
224
- }) : /* @__PURE__ */ d("div", {
221
+ }) : /* @__PURE__ */ u("div", {
225
222
  className: "text-sm text-muted-foreground",
226
- children: t(D, "createWorkspace.selectTenantFirst", "Please select a tenant first")
223
+ children: t(E, "createWorkspace.selectTenantFirst", "Please select a tenant first")
227
224
  }),
228
- /* @__PURE__ */ d("p", {
225
+ /* @__PURE__ */ u("p", {
229
226
  className: "text-xs text-muted-foreground",
230
- children: t(D, "createWorkspace.organizationHint", "Select which organization owns this workspace")
227
+ children: t(E, "createWorkspace.organizationHint", "Select which organization owns this workspace")
231
228
  }),
232
- K.formState.errors.organizationId?.message && /* @__PURE__ */ d("p", {
229
+ K.formState.errors.organizationId?.message && /* @__PURE__ */ u("p", {
233
230
  className: "text-sm text-destructive",
234
231
  children: K.formState.errors.organizationId.message
235
232
  })
236
233
  ]
237
234
  }),
238
- /* @__PURE__ */ f("div", {
239
- className: "space-y-2",
240
- children: [
241
- /* @__PURE__ */ d("label", {
242
- htmlFor: "workspace-type",
243
- className: "text-sm font-medium text-foreground",
244
- children: t(D, "createWorkspace.typeLabel", "Workspace Type")
245
- }),
246
- /* @__PURE__ */ f("select", {
247
- id: "workspace-type",
248
- className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground",
249
- ...K.register("type"),
250
- children: [/* @__PURE__ */ d("option", {
251
- value: "TEAM",
252
- children: t(D, "createWorkspace.typeTeam", "Team Workspace")
253
- }), /* @__PURE__ */ d("option", {
254
- value: "PERSONAL",
255
- children: t(D, "createWorkspace.typePersonal", "Personal Workspace")
256
- })]
257
- }),
258
- /* @__PURE__ */ d("p", {
259
- className: "text-xs text-muted-foreground",
260
- children: t(D, "createWorkspace.typeHint", "Choose between a collaborative workspace or a personal one")
261
- })
262
- ]
263
- }),
264
- /* @__PURE__ */ f("div", {
235
+ /* @__PURE__ */ d("div", {
265
236
  className: "flex justify-end gap-3 pt-4",
266
- children: [/* @__PURE__ */ d(p, {
237
+ children: [/* @__PURE__ */ u(f, {
267
238
  type: "button",
268
239
  variant: "outline",
269
240
  onClick: () => N(`${P}/list`),
270
241
  disabled: I,
271
- children: t(D, "createWorkspace.cancel", "Cancel")
272
- }), /* @__PURE__ */ d(p, {
242
+ children: t(E, "createWorkspace.cancel", "Cancel")
243
+ }), /* @__PURE__ */ u(f, {
273
244
  type: "submit",
274
245
  disabled: I || R || V || X || j,
275
- title: j ? t(D, "createWorkspace.disabledAtCap", "Workspace limit reached — upgrade your plan to create more.") : void 0,
276
- children: I ? t(D, "createWorkspace.creating", "Creating...") : t(D, "createWorkspace.submit", "Create Workspace")
246
+ title: j ? t(E, "createWorkspace.disabledAtCap", "Workspace limit reached — upgrade your plan to create more.") : void 0,
247
+ children: I ? t(E, "createWorkspace.creating", "Creating...") : t(E, "createWorkspace.submit", "Create Workspace")
277
248
  })]
278
249
  })
279
250
  ]
@@ -283,6 +254,6 @@ function D() {
283
254
  });
284
255
  }
285
256
  //#endregion
286
- export { D as CreateWorkspacePage };
257
+ export { E as CreateWorkspacePage };
287
258
 
288
259
  //# sourceMappingURL=CreateWorkspacePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CreateWorkspacePage.js","names":[],"sources":["../../src/pages/CreateWorkspacePage.tsx"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { ArrowLeft, AlertCircle } from 'lucide-react';\nimport { Link, useNavigate } from 'react-router-dom';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useForm, z } from '@burdenoff/fe-libs/form';\nimport { AccessDenied, UpgradeInlineBanner } from '@burdenoff/fe-libs/shared/components';\nimport { useUpgradePrompt } from '@burdenoff/fe-libs/shared/hooks';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useCreateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useMyTenants, useMyOrganizations } from '../hooks/useTenants';\nimport { useOrgsWithPermission } from '../hooks/useOrgsWithPermission';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { generateSlug } from '../utils/slug';\nimport { tWithFallback } from '../utils/i18n';\n\ntype CreateWorkspaceFormData = {\n name: string;\n slug: string;\n tenantId: string;\n organizationId: string;\n type?: 'TEAM' | 'PERSONAL';\n};\n\nexport function CreateWorkspacePage() {\n useWorkspacesPageView('workspaces.create');\n const { t } = useI18n();\n const {\n isFree,\n approachingLimits,\n plan: planSnapshot,\n } = useUpgradePrompt({ resource: 'workspaces' });\n const workspacesUsage = approachingLimits[0];\n const atWorkspaceCap =\n !!workspacesUsage &&\n typeof workspacesUsage.limit === 'number' &&\n workspacesUsage.current >= workspacesUsage.limit;\n const showUpgradeBanner = isFree && !!workspacesUsage;\n const createWorkspaceSchema = useMemo(\n () =>\n z.object({\n name: z\n .string()\n .min(\n 2,\n tWithFallback(\n t,\n 'createWorkspace.validation.nameMin',\n 'Name must be at least 2 characters'\n )\n )\n .max(100),\n slug: z\n .string()\n .min(\n 2,\n tWithFallback(\n t,\n 'createWorkspace.validation.slugMin',\n 'Slug must be at least 2 characters'\n )\n )\n .max(50)\n .regex(\n /^[a-z0-9-]+$/,\n tWithFallback(\n t,\n 'createWorkspace.validation.slugFormat',\n 'Slug can only contain lowercase letters, numbers, and hyphens'\n )\n ),\n tenantId: z\n .string()\n .min(\n 1,\n tWithFallback(t, 'createWorkspace.validation.tenantRequired', 'Please select a tenant')\n ),\n organizationId: z\n .string()\n .min(\n 1,\n tWithFallback(\n t,\n 'createWorkspace.validation.organizationRequired',\n 'Please select an organization'\n )\n ),\n type: z.enum(['TEAM', 'PERSONAL']).optional(),\n }),\n [t]\n );\n\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { mutate: createWorkspace, isPending } = useCreateWorkspace();\n const { data: tenantsData, isLoading: isLoadingTenants, error: tenantsError } = useMyTenants();\n const {\n data: organizationsData,\n isLoading: isLoadingOrganizations,\n error: organizationsError,\n } = useMyOrganizations();\n\n const tenants = useMemo(() => tenantsData?.items ?? [], [tenantsData]);\n const allOrganizations = useMemo(() => organizationsData?.items ?? [], [organizationsData]);\n\n const defaultTenantId = useMemo(() => {\n const sharedTenant = tenants.find(\n (tenant) => tenant.type === 'SHARED' || tenant.slug === 'burdenoff-shared'\n );\n return sharedTenant?.id ?? tenants[0]?.id ?? '';\n }, [tenants]);\n\n const form = useForm<CreateWorkspaceFormData>({\n schema: createWorkspaceSchema,\n defaultValues: {\n name: '',\n slug: '',\n tenantId: '',\n organizationId: '',\n type: 'TEAM',\n },\n });\n\n const selectedTenantId = form.watch('tenantId');\n const workspaceName = form.watch('name');\n\n // Check workspace:create permission across ALL the user's orgs up front\n // (bulk-checked in a single rbac round-trip). This is the authoritative\n // gate — only orgs where the user can actually create a workspace make\n // it into the dropdown, and the page can decisively render the \"no\n // permissions anywhere\" state once the check resolves.\n const allOrgIds = useMemo(() => allOrganizations.map((o) => o.id), [allOrganizations]);\n const {\n allowedIds: workspaceCreateAllowedOrgIds,\n loading: isCheckingOrgPermissions,\n isError: orgPermissionsHadError,\n } = useOrgsWithPermission(allOrgIds, 'workspace', 'create');\n\n // Filter organizations by selected tenant AND permission. When the rbac\n // permission lookup errored we deliberately skip the permission filter\n // (see `orgPermissionsHadError` block below) — the gateway @rbac\n // directive on `createWorkspace` is the authoritative gate, so it's\n // safer to let the user pick from the full tenant list and hit a 403 on\n // submit than to lock them out of a flow they actually have access to.\n const tenantOrganizations = useMemo(() => {\n if (!selectedTenantId) return [];\n return allOrganizations.filter((org) => org.tenantId === selectedTenantId);\n }, [selectedTenantId, allOrganizations]);\n const filteredOrganizations = useMemo(() => {\n if (orgPermissionsHadError) return tenantOrganizations;\n return tenantOrganizations.filter((org) => workspaceCreateAllowedOrgIds.has(org.id));\n }, [tenantOrganizations, workspaceCreateAllowedOrgIds, orgPermissionsHadError]);\n\n // Auto-generate slug from name (clear when name is empty)\n useEffect(() => {\n form.setValue('slug', workspaceName ? generateSlug(workspaceName) : '');\n }, [workspaceName, form]);\n\n // Reset organizationId when tenant changes\n useEffect(() => {\n form.setValue('organizationId', '');\n }, [selectedTenantId, form]);\n\n useEffect(() => {\n if (!form.getValues('tenantId') && defaultTenantId) {\n form.setValue('tenantId', defaultTenantId, {\n shouldDirty: false,\n shouldTouch: false,\n });\n }\n }, [defaultTenantId, form]);\n\n useEffect(() => {\n const currentOrganizationId = form.getValues('organizationId');\n const hasCurrentOrganization = filteredOrganizations.some(\n (organization) => organization.id === currentOrganizationId\n );\n\n if (!hasCurrentOrganization && filteredOrganizations[0]?.id) {\n form.setValue('organizationId', filteredOrganizations[0].id, {\n shouldDirty: false,\n shouldTouch: false,\n });\n }\n }, [filteredOrganizations, form]);\n\n const onSubmit = (data: CreateWorkspaceFormData) => {\n createWorkspace(\n {\n name: data.name,\n slug: data.slug,\n tenantId: data.tenantId,\n organizationId: data.organizationId,\n type: data.type,\n },\n {\n onSuccess: (newWorkspace) => {\n // Navigate to the new workspace\n navigate(`${basePath}/${newWorkspace.id}`);\n },\n }\n );\n };\n\n // `workspace:create` is an org-scoped permission. Page-level `PermissionGate`\n // resolved at the outer shell scope and incorrectly denied users who *do*\n // have the permission inside at least one of their orgs. The authoritative\n // gate now lives on the org dropdown (only orgs where the user has\n // workspace:create are listed via useOrgsWithPermission) and on the gateway\n // @rbac directive enforcing the createWorkspace mutation. We still want a\n // clear \"you can't create anywhere\" empty state — once tenants and org\n // permission lookups have settled, if no org is eligible we render it.\n // The AccessDenied empty-state should ONLY render when we know with\n // certainty that the user has no creatable org. That requires:\n // 1. tenants + organizations queries both succeeded (otherwise an\n // empty `allOrganizations` is a data-fetch failure, not an authz\n // failure — masking it as \"permission denied\" hides the real\n // error and offers the user no path to retry),\n // 2. the rbac permission lookup also succeeded (errors there fall\n // through to the unfiltered form via the warning banner below),\n // 3. and even then, only when the user has at least one org but none\n // of them allow workspace:create.\n // If `allOrganizations` is empty because the user genuinely belongs to\n // no orgs, the inner \"no organizations found for this tenant\" message\n // is a better surface than an authz-denied page.\n const hasAnyCreatableOrg = workspaceCreateAllowedOrgIds.size > 0;\n const stillResolvingPermissions =\n isLoadingTenants || isLoadingOrganizations || isCheckingOrgPermissions;\n const orgDataLoadedSuccessfully =\n !tenantsError && !organizationsError && allOrganizations.length > 0;\n if (\n !stillResolvingPermissions &&\n !orgPermissionsHadError &&\n orgDataLoadedSuccessfully &&\n !hasAnyCreatableOrg\n ) {\n return (\n <div className=\"p-6\">\n <Link\n to={`${basePath}/list`}\n className=\"inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-6\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'createWorkspace.backToWorkspaces', 'Back to Workspaces')}\n </Link>\n <AccessDenied\n variant=\"card\"\n title={tWithFallback(t, 'createWorkspace.permissionRequiredTitle', 'Permission Required')}\n description={tWithFallback(\n t,\n 'createWorkspace.permissionRequiredDescription',\n \"You don't have permission to create workspaces in any of your organizations.\"\n )}\n details={tWithFallback(\n t,\n 'createWorkspace.permissionRequiredDetails',\n 'Required: workspace:create'\n )}\n primaryAction={{\n label: tWithFallback(t, 'createWorkspace.goBack', 'Go Back'),\n onClick: () => navigate(`${basePath}/list`),\n }}\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Back link */}\n <Link\n to={`${basePath}/list`}\n className=\"inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'createWorkspace.backToWorkspaces', 'Back to Workspaces')}\n </Link>\n\n {showUpgradeBanner && (\n <div className=\"max-w-2xl mx-auto w-full\">\n <UpgradeInlineBanner\n resource=\"workspaces\"\n current={workspacesUsage.current}\n limit={workspacesUsage.limit ?? undefined}\n severity={atWorkspaceCap ? 'critical' : undefined}\n message={\n atWorkspaceCap\n ? tWithFallback(\n t,\n 'createWorkspace.upgradeCapMessage',\n `You've used all workspaces in the ${planSnapshot?.planName ?? 'free'} plan.`\n )\n : tWithFallback(\n t,\n 'createWorkspace.upgradeApproachingMessage',\n 'Upgrade to create more workspaces.'\n )\n }\n />\n </div>\n )}\n\n {/* Form card */}\n <Card className=\"max-w-2xl mx-auto\">\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'createWorkspace.title', 'Create New Workspace')}</CardTitle>\n <CardDescription>\n {tWithFallback(\n t,\n 'createWorkspace.subtitle',\n 'Set up a new workspace for your team or personal use'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {orgPermissionsHadError && (\n <div\n role=\"alert\"\n className=\"mb-4 flex items-start gap-2 rounded-md border border-warning/30 bg-warning/10 p-3 text-sm text-foreground\"\n >\n <AlertCircle size={16} className=\"mt-0.5 shrink-0 text-warning\" />\n <span>\n {tWithFallback(\n t,\n 'createWorkspace.permissionCheckUnavailable',\n \"We couldn't verify your workspace-create permissions just now, so the organization list isn't filtered. If you submit and don't have access, the request will be rejected and you can retry.\"\n )}\n </span>\n </div>\n )}\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-6\">\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-name\" className=\"text-sm font-medium text-foreground\">\n {tWithFallback(t, 'createWorkspace.nameLabel', 'Workspace Name *')}\n </label>\n <input\n id=\"workspace-name\"\n placeholder={tWithFallback(t, 'createWorkspace.namePlaceholder', 'My Workspace')}\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n {...form.register('name')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.nameHint',\n 'A descriptive name for your workspace'\n )}\n </p>\n {form.formState.errors.name?.message && (\n <p className=\"text-sm text-destructive\">{form.formState.errors.name.message}</p>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-slug\" className=\"text-sm font-medium text-foreground\">\n {tWithFallback(t, 'createWorkspace.slugLabel', 'Slug *')}\n </label>\n <input\n id=\"workspace-slug\"\n placeholder={tWithFallback(t, 'createWorkspace.slugPlaceholder', 'my-workspace')}\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n {...form.register('slug')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.slugHint',\n 'URL-friendly identifier (auto-generated, but you can edit it)'\n )}\n </p>\n {form.formState.errors.slug?.message && (\n <p className=\"text-sm text-destructive\">{form.formState.errors.slug.message}</p>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-tenant\" className=\"text-sm font-medium text-foreground\">\n {tWithFallback(t, 'createWorkspace.tenantLabel', 'Tenant *')}\n </label>\n {isLoadingTenants ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : tenantsError ? (\n <div className=\"flex items-center gap-2 text-sm text-destructive\">\n <AlertCircle size={16} />\n {tWithFallback(\n t,\n 'createWorkspace.failedToLoadTenants',\n 'Failed to load tenants'\n )}\n </div>\n ) : tenants.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.noTenantsAvailable',\n 'No tenants available. Contact your administrator.'\n )}\n </div>\n ) : (\n <select\n id=\"workspace-tenant\"\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n {...form.register('tenantId')}\n >\n <option value=\"\">\n {tWithFallback(t, 'createWorkspace.selectTenantPlaceholder', 'Select a tenant')}\n </option>\n {tenants.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name}\n </option>\n ))}\n </select>\n )}\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.tenantHint',\n 'Select which tenant this workspace belongs to'\n )}\n </p>\n {form.formState.errors.tenantId?.message && (\n <p className=\"text-sm text-destructive\">{form.formState.errors.tenantId.message}</p>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <label\n htmlFor=\"workspace-organization\"\n className=\"text-sm font-medium text-foreground\"\n >\n {tWithFallback(t, 'createWorkspace.organizationLabel', 'Organization *')}\n </label>\n {isLoadingOrganizations || isCheckingOrgPermissions ? (\n // While the permission check is in flight,\n // `workspaceCreateAllowedOrgIds` is empty so\n // `filteredOrganizations` is also empty. Render the same\n // skeleton we use for the org-load itself instead of the\n // \"no organizations found\" empty-state — otherwise a slow\n // rbac response briefly looks like an authorization\n // failure for users who DO have workspace:create.\n <Skeleton className=\"h-10 w-full\" />\n ) : organizationsError ? (\n <div className=\"flex items-center gap-2 text-sm text-destructive\">\n <AlertCircle size={16} />\n {tWithFallback(\n t,\n 'createWorkspace.failedToLoadOrganizations',\n 'Failed to load organizations'\n )}\n </div>\n ) : !selectedTenantId ? (\n <div className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.selectTenantFirst',\n 'Please select a tenant first'\n )}\n </div>\n ) : filteredOrganizations.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.noOrganizationsFound',\n 'No organizations found for this tenant'\n )}\n </div>\n ) : (\n <select\n id=\"workspace-organization\"\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n disabled={!selectedTenantId}\n {...form.register('organizationId')}\n >\n <option value=\"\">\n {tWithFallback(\n t,\n 'createWorkspace.selectOrganizationPlaceholder',\n 'Select an organization'\n )}\n </option>\n {filteredOrganizations.map((org) => (\n <option key={org.id} value={org.id}>\n {org.name}\n </option>\n ))}\n </select>\n )}\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.organizationHint',\n 'Select which organization owns this workspace'\n )}\n </p>\n {form.formState.errors.organizationId?.message && (\n <p className=\"text-sm text-destructive\">\n {form.formState.errors.organizationId.message}\n </p>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-type\" className=\"text-sm font-medium text-foreground\">\n {tWithFallback(t, 'createWorkspace.typeLabel', 'Workspace Type')}\n </label>\n <select\n id=\"workspace-type\"\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n {...form.register('type')}\n >\n <option value=\"TEAM\">\n {tWithFallback(t, 'createWorkspace.typeTeam', 'Team Workspace')}\n </option>\n <option value=\"PERSONAL\">\n {tWithFallback(t, 'createWorkspace.typePersonal', 'Personal Workspace')}\n </option>\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.typeHint',\n 'Choose between a collaborative workspace or a personal one'\n )}\n </p>\n </div>\n\n <div className=\"flex justify-end gap-3 pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`${basePath}/list`)}\n disabled={isPending}\n >\n {tWithFallback(t, 'createWorkspace.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n disabled={\n isPending ||\n isLoadingTenants ||\n isLoadingOrganizations ||\n isCheckingOrgPermissions ||\n atWorkspaceCap\n }\n title={\n atWorkspaceCap\n ? tWithFallback(\n t,\n 'createWorkspace.disabledAtCap',\n 'Workspace limit reached — upgrade your plan to create more.'\n )\n : undefined\n }\n >\n {isPending\n ? tWithFallback(t, 'createWorkspace.creating', 'Creating...')\n : tWithFallback(t, 'createWorkspace.submit', 'Create Workspace')}\n </Button>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreateWorkspacePage.js","names":[],"sources":["../../src/pages/CreateWorkspacePage.tsx"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { ArrowLeft, AlertCircle } from 'lucide-react';\nimport { Link, useNavigate } from 'react-router-dom';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useForm, z } from '@burdenoff/fe-libs/form';\nimport { AccessDenied, UpgradeInlineBanner } from '@burdenoff/fe-libs/shared/components';\nimport { useUpgradePrompt } from '@burdenoff/fe-libs/shared/hooks';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useCreateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useMyTenants, useMyOrganizations } from '../hooks/useTenants';\nimport { useOrgsWithPermission } from '../hooks/useOrgsWithPermission';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { generateSlug } from '../utils/slug';\nimport { tWithFallback } from '../utils/i18n';\n\ntype CreateWorkspaceFormData = {\n name: string;\n slug: string;\n tenantId: string;\n organizationId: string;\n};\n\nexport function CreateWorkspacePage() {\n useWorkspacesPageView('workspaces.create');\n const { t } = useI18n();\n const {\n isFree,\n approachingLimits,\n plan: planSnapshot,\n } = useUpgradePrompt({ resource: 'workspaces' });\n const workspacesUsage = approachingLimits[0];\n const atWorkspaceCap =\n !!workspacesUsage &&\n typeof workspacesUsage.limit === 'number' &&\n workspacesUsage.current >= workspacesUsage.limit;\n const showUpgradeBanner = isFree && !!workspacesUsage;\n const createWorkspaceSchema = useMemo(\n () =>\n z.object({\n name: z\n .string()\n .min(\n 2,\n tWithFallback(\n t,\n 'createWorkspace.validation.nameMin',\n 'Name must be at least 2 characters'\n )\n )\n .max(100),\n slug: z\n .string()\n .min(\n 2,\n tWithFallback(\n t,\n 'createWorkspace.validation.slugMin',\n 'Slug must be at least 2 characters'\n )\n )\n .max(50)\n .regex(\n /^[a-z0-9-]+$/,\n tWithFallback(\n t,\n 'createWorkspace.validation.slugFormat',\n 'Slug can only contain lowercase letters, numbers, and hyphens'\n )\n ),\n tenantId: z\n .string()\n .min(\n 1,\n tWithFallback(t, 'createWorkspace.validation.tenantRequired', 'Please select a tenant')\n ),\n organizationId: z\n .string()\n .min(\n 1,\n tWithFallback(\n t,\n 'createWorkspace.validation.organizationRequired',\n 'Please select an organization'\n )\n ),\n }),\n [t]\n );\n\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { mutate: createWorkspace, isPending } = useCreateWorkspace();\n const { data: tenantsData, isLoading: isLoadingTenants, error: tenantsError } = useMyTenants();\n const {\n data: organizationsData,\n isLoading: isLoadingOrganizations,\n error: organizationsError,\n } = useMyOrganizations();\n\n const tenants = useMemo(() => tenantsData?.items ?? [], [tenantsData]);\n const allOrganizations = useMemo(() => organizationsData?.items ?? [], [organizationsData]);\n\n const defaultTenantId = useMemo(() => {\n const sharedTenant = tenants.find(\n (tenant) => tenant.type === 'SHARED' || tenant.slug === 'burdenoff-shared'\n );\n return sharedTenant?.id ?? tenants[0]?.id ?? '';\n }, [tenants]);\n\n const form = useForm<CreateWorkspaceFormData>({\n schema: createWorkspaceSchema,\n defaultValues: {\n name: '',\n slug: '',\n tenantId: '',\n organizationId: '',\n },\n });\n\n const selectedTenantId = form.watch('tenantId');\n const workspaceName = form.watch('name');\n\n // Check workspace:create permission across ALL the user's orgs up front\n // (bulk-checked in a single rbac round-trip). This is the authoritative\n // gate — only orgs where the user can actually create a workspace make\n // it into the dropdown, and the page can decisively render the \"no\n // permissions anywhere\" state once the check resolves.\n const allOrgIds = useMemo(() => allOrganizations.map((o) => o.id), [allOrganizations]);\n const {\n allowedIds: workspaceCreateAllowedOrgIds,\n loading: isCheckingOrgPermissions,\n isError: orgPermissionsHadError,\n } = useOrgsWithPermission(allOrgIds, 'workspace', 'create');\n\n // Filter organizations by selected tenant AND permission. When the rbac\n // permission lookup errored we deliberately skip the permission filter\n // (see `orgPermissionsHadError` block below) — the gateway @rbac\n // directive on `createWorkspace` is the authoritative gate, so it's\n // safer to let the user pick from the full tenant list and hit a 403 on\n // submit than to lock them out of a flow they actually have access to.\n const tenantOrganizations = useMemo(() => {\n if (!selectedTenantId) return [];\n return allOrganizations.filter((org) => org.tenantId === selectedTenantId);\n }, [selectedTenantId, allOrganizations]);\n const filteredOrganizations = useMemo(() => {\n if (orgPermissionsHadError) return tenantOrganizations;\n return tenantOrganizations.filter((org) => workspaceCreateAllowedOrgIds.has(org.id));\n }, [tenantOrganizations, workspaceCreateAllowedOrgIds, orgPermissionsHadError]);\n\n // Auto-generate slug from name (clear when name is empty)\n useEffect(() => {\n form.setValue('slug', workspaceName ? generateSlug(workspaceName) : '');\n }, [workspaceName, form]);\n\n // Reset organizationId when tenant changes\n useEffect(() => {\n form.setValue('organizationId', '');\n }, [selectedTenantId, form]);\n\n useEffect(() => {\n if (!form.getValues('tenantId') && defaultTenantId) {\n form.setValue('tenantId', defaultTenantId, {\n shouldDirty: false,\n shouldTouch: false,\n });\n }\n }, [defaultTenantId, form]);\n\n useEffect(() => {\n const currentOrganizationId = form.getValues('organizationId');\n const hasCurrentOrganization = filteredOrganizations.some(\n (organization) => organization.id === currentOrganizationId\n );\n\n if (!hasCurrentOrganization && filteredOrganizations[0]?.id) {\n form.setValue('organizationId', filteredOrganizations[0].id, {\n shouldDirty: false,\n shouldTouch: false,\n });\n }\n }, [filteredOrganizations, form]);\n\n const onSubmit = (data: CreateWorkspaceFormData) => {\n createWorkspace(\n {\n name: data.name,\n slug: data.slug,\n tenantId: data.tenantId,\n organizationId: data.organizationId,\n },\n {\n onSuccess: (newWorkspace) => {\n // Navigate to the new workspace\n navigate(`${basePath}/${newWorkspace.id}`);\n },\n }\n );\n };\n\n // `workspace:create` is an org-scoped permission. Page-level `PermissionGate`\n // resolved at the outer shell scope and incorrectly denied users who *do*\n // have the permission inside at least one of their orgs. The authoritative\n // gate now lives on the org dropdown (only orgs where the user has\n // workspace:create are listed via useOrgsWithPermission) and on the gateway\n // @rbac directive enforcing the createWorkspace mutation. We still want a\n // clear \"you can't create anywhere\" empty state — once tenants and org\n // permission lookups have settled, if no org is eligible we render it.\n // The AccessDenied empty-state should ONLY render when we know with\n // certainty that the user has no creatable org. That requires:\n // 1. tenants + organizations queries both succeeded (otherwise an\n // empty `allOrganizations` is a data-fetch failure, not an authz\n // failure — masking it as \"permission denied\" hides the real\n // error and offers the user no path to retry),\n // 2. the rbac permission lookup also succeeded (errors there fall\n // through to the unfiltered form via the warning banner below),\n // 3. and even then, only when the user has at least one org but none\n // of them allow workspace:create.\n // If `allOrganizations` is empty because the user genuinely belongs to\n // no orgs, the inner \"no organizations found for this tenant\" message\n // is a better surface than an authz-denied page.\n const hasAnyCreatableOrg = workspaceCreateAllowedOrgIds.size > 0;\n const stillResolvingPermissions =\n isLoadingTenants || isLoadingOrganizations || isCheckingOrgPermissions;\n const orgDataLoadedSuccessfully =\n !tenantsError && !organizationsError && allOrganizations.length > 0;\n if (\n !stillResolvingPermissions &&\n !orgPermissionsHadError &&\n orgDataLoadedSuccessfully &&\n !hasAnyCreatableOrg\n ) {\n return (\n <div className=\"p-6\">\n <Link\n to={`${basePath}/list`}\n className=\"inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-6\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'createWorkspace.backToWorkspaces', 'Back to Workspaces')}\n </Link>\n <AccessDenied\n variant=\"card\"\n title={tWithFallback(t, 'createWorkspace.permissionRequiredTitle', 'Permission Required')}\n description={tWithFallback(\n t,\n 'createWorkspace.permissionRequiredDescription',\n \"You don't have permission to create workspaces in any of your organizations.\"\n )}\n details={tWithFallback(\n t,\n 'createWorkspace.permissionRequiredDetails',\n 'Required: workspace:create'\n )}\n primaryAction={{\n label: tWithFallback(t, 'createWorkspace.goBack', 'Go Back'),\n onClick: () => navigate(`${basePath}/list`),\n }}\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Back link */}\n <Link\n to={`${basePath}/list`}\n className=\"inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'createWorkspace.backToWorkspaces', 'Back to Workspaces')}\n </Link>\n\n {showUpgradeBanner && (\n <div className=\"max-w-2xl mx-auto w-full\">\n <UpgradeInlineBanner\n resource=\"workspaces\"\n current={workspacesUsage.current}\n limit={workspacesUsage.limit ?? undefined}\n severity={atWorkspaceCap ? 'critical' : undefined}\n message={\n atWorkspaceCap\n ? tWithFallback(\n t,\n 'createWorkspace.upgradeCapMessage',\n `You've used all workspaces in the ${planSnapshot?.planName ?? 'free'} plan.`\n )\n : tWithFallback(\n t,\n 'createWorkspace.upgradeApproachingMessage',\n 'Upgrade to create more workspaces.'\n )\n }\n />\n </div>\n )}\n\n {/* Form card */}\n <Card className=\"max-w-2xl mx-auto\">\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'createWorkspace.title', 'Create New Workspace')}</CardTitle>\n <CardDescription>\n {tWithFallback(t, 'createWorkspace.subtitle', 'Set up a new workspace for your team')}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {orgPermissionsHadError && (\n <div\n role=\"alert\"\n className=\"mb-4 flex items-start gap-2 rounded-md border border-warning/30 bg-warning/10 p-3 text-sm text-foreground\"\n >\n <AlertCircle size={16} className=\"mt-0.5 shrink-0 text-warning\" />\n <span>\n {tWithFallback(\n t,\n 'createWorkspace.permissionCheckUnavailable',\n \"We couldn't verify your workspace-create permissions just now, so the organization list isn't filtered. If you submit and don't have access, the request will be rejected and you can retry.\"\n )}\n </span>\n </div>\n )}\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-6\">\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-name\" className=\"text-sm font-medium text-foreground\">\n {tWithFallback(t, 'createWorkspace.nameLabel', 'Workspace Name *')}\n </label>\n <input\n id=\"workspace-name\"\n placeholder={tWithFallback(t, 'createWorkspace.namePlaceholder', 'My Workspace')}\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n {...form.register('name')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.nameHint',\n 'A descriptive name for your workspace'\n )}\n </p>\n {form.formState.errors.name?.message && (\n <p className=\"text-sm text-destructive\">{form.formState.errors.name.message}</p>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-slug\" className=\"text-sm font-medium text-foreground\">\n {tWithFallback(t, 'createWorkspace.slugLabel', 'Slug *')}\n </label>\n <input\n id=\"workspace-slug\"\n placeholder={tWithFallback(t, 'createWorkspace.slugPlaceholder', 'my-workspace')}\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n {...form.register('slug')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.slugHint',\n 'URL-friendly identifier (auto-generated, but you can edit it)'\n )}\n </p>\n {form.formState.errors.slug?.message && (\n <p className=\"text-sm text-destructive\">{form.formState.errors.slug.message}</p>\n )}\n </div>\n\n {/* Tenant picker — shown only when the user has more than one\n tenant to choose between. With a single shared tenant there is\n nothing to choose, so it is hidden and auto-selected. The\n loading / error / empty branches still render (length !== 1). */}\n {tenants.length !== 1 ? (\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-tenant\" className=\"text-sm font-medium text-foreground\">\n {tWithFallback(t, 'createWorkspace.tenantLabel', 'Tenant *')}\n </label>\n {isLoadingTenants ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : tenantsError ? (\n <div className=\"flex items-center gap-2 text-sm text-destructive\">\n <AlertCircle size={16} />\n {tWithFallback(\n t,\n 'createWorkspace.failedToLoadTenants',\n 'Failed to load tenants'\n )}\n </div>\n ) : tenants.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.noTenantsAvailable',\n 'No tenants available. Contact your administrator.'\n )}\n </div>\n ) : (\n <select\n id=\"workspace-tenant\"\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n {...form.register('tenantId')}\n >\n <option value=\"\">\n {tWithFallback(\n t,\n 'createWorkspace.selectTenantPlaceholder',\n 'Select a tenant'\n )}\n </option>\n {tenants.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name}\n </option>\n ))}\n </select>\n )}\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.tenantHint',\n 'Select which tenant this workspace belongs to'\n )}\n </p>\n {form.formState.errors.tenantId?.message && (\n <p className=\"text-sm text-destructive\">\n {form.formState.errors.tenantId.message}\n </p>\n )}\n </div>\n ) : null}\n\n <div className=\"space-y-2\">\n <label\n htmlFor=\"workspace-organization\"\n className=\"text-sm font-medium text-foreground\"\n >\n {tWithFallback(t, 'createWorkspace.organizationLabel', 'Organization *')}\n </label>\n {isLoadingOrganizations || isCheckingOrgPermissions ? (\n // While the permission check is in flight,\n // `workspaceCreateAllowedOrgIds` is empty so\n // `filteredOrganizations` is also empty. Render the same\n // skeleton we use for the org-load itself instead of the\n // \"no organizations found\" empty-state — otherwise a slow\n // rbac response briefly looks like an authorization\n // failure for users who DO have workspace:create.\n <Skeleton className=\"h-10 w-full\" />\n ) : organizationsError ? (\n <div className=\"flex items-center gap-2 text-sm text-destructive\">\n <AlertCircle size={16} />\n {tWithFallback(\n t,\n 'createWorkspace.failedToLoadOrganizations',\n 'Failed to load organizations'\n )}\n </div>\n ) : !selectedTenantId ? (\n <div className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.selectTenantFirst',\n 'Please select a tenant first'\n )}\n </div>\n ) : filteredOrganizations.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.noOrganizationsFound',\n 'No organizations found for this tenant'\n )}\n </div>\n ) : (\n <select\n id=\"workspace-organization\"\n className=\"h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground\"\n disabled={!selectedTenantId}\n {...form.register('organizationId')}\n >\n <option value=\"\">\n {tWithFallback(\n t,\n 'createWorkspace.selectOrganizationPlaceholder',\n 'Select an organization'\n )}\n </option>\n {filteredOrganizations.map((org) => (\n <option key={org.id} value={org.id}>\n {org.name}\n </option>\n ))}\n </select>\n )}\n <p className=\"text-xs text-muted-foreground\">\n {tWithFallback(\n t,\n 'createWorkspace.organizationHint',\n 'Select which organization owns this workspace'\n )}\n </p>\n {form.formState.errors.organizationId?.message && (\n <p className=\"text-sm text-destructive\">\n {form.formState.errors.organizationId.message}\n </p>\n )}\n </div>\n\n <div className=\"flex justify-end gap-3 pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`${basePath}/list`)}\n disabled={isPending}\n >\n {tWithFallback(t, 'createWorkspace.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n disabled={\n isPending ||\n isLoadingTenants ||\n isLoadingOrganizations ||\n isCheckingOrgPermissions ||\n atWorkspaceCap\n }\n title={\n atWorkspaceCap\n ? tWithFallback(\n t,\n 'createWorkspace.disabledAtCap',\n 'Workspace limit reached — upgrade your plan to create more.'\n )\n : undefined\n }\n >\n {isPending\n ? tWithFallback(t, 'createWorkspace.creating', 'Creating...')\n : tWithFallback(t, 'createWorkspace.submit', 'Create Workspace')}\n </Button>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}