@stapel/workspaces-react 0.6.0 → 0.7.0

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 (100) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/api/generated/schema.d.ts +384 -4
  3. package/dist/api/generated/schema.d.ts.map +1 -1
  4. package/dist/api/types.d.ts +20 -5
  5. package/dist/api/types.d.ts.map +1 -1
  6. package/dist/api/workspacesApi.d.ts +23 -1
  7. package/dist/api/workspacesApi.d.ts.map +1 -1
  8. package/dist/api/workspacesApi.js +4 -0
  9. package/dist/api/workspacesApi.js.map +1 -1
  10. package/dist/default/InviteAcceptPage.d.ts +28 -0
  11. package/dist/default/InviteAcceptPage.d.ts.map +1 -0
  12. package/dist/default/InviteAcceptPage.js +141 -0
  13. package/dist/default/InviteAcceptPage.js.map +1 -0
  14. package/dist/default/MembersManager.d.ts +4 -4
  15. package/dist/default/MembersManager.d.ts.map +1 -1
  16. package/dist/default/MembersManager.js +61 -56
  17. package/dist/default/MembersManager.js.map +1 -1
  18. package/dist/default/index.d.ts +2 -0
  19. package/dist/default/index.d.ts.map +1 -1
  20. package/dist/default/index.js +1 -0
  21. package/dist/default/index.js.map +1 -1
  22. package/dist/flows/inviteAcceptFlow.d.ts +155 -0
  23. package/dist/flows/inviteAcceptFlow.d.ts.map +1 -0
  24. package/dist/flows/inviteAcceptFlow.js +126 -0
  25. package/dist/flows/inviteAcceptFlow.js.map +1 -0
  26. package/dist/headless/Can.d.ts +40 -0
  27. package/dist/headless/Can.d.ts.map +1 -0
  28. package/dist/headless/Can.js +30 -0
  29. package/dist/headless/Can.js.map +1 -0
  30. package/dist/headless/InviteAcceptFlow.d.ts +44 -0
  31. package/dist/headless/InviteAcceptFlow.d.ts.map +1 -0
  32. package/dist/headless/InviteAcceptFlow.js +67 -0
  33. package/dist/headless/InviteAcceptFlow.js.map +1 -0
  34. package/dist/headless/RoleSelect.d.ts +41 -0
  35. package/dist/headless/RoleSelect.d.ts.map +1 -0
  36. package/dist/headless/RoleSelect.js +34 -0
  37. package/dist/headless/RoleSelect.js.map +1 -0
  38. package/dist/i18n/generated/errors.gen.d.ts +48 -0
  39. package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
  40. package/dist/i18n/generated/errors.gen.js +24 -0
  41. package/dist/i18n/generated/errors.gen.js.map +1 -1
  42. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -1
  43. package/dist/i18n/generated/errors.ru.gen.js +8 -0
  44. package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
  45. package/dist/i18n/keys.d.ts +28 -0
  46. package/dist/i18n/keys.d.ts.map +1 -1
  47. package/dist/i18n/keys.js +64 -0
  48. package/dist/i18n/keys.js.map +1 -1
  49. package/dist/i18n/ru.d.ts.map +1 -1
  50. package/dist/i18n/ru.js +30 -0
  51. package/dist/i18n/ru.js.map +1 -1
  52. package/dist/index.d.ts +14 -3
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +9 -2
  55. package/dist/index.js.map +1 -1
  56. package/dist/model/capabilities.d.ts +21 -0
  57. package/dist/model/capabilities.d.ts.map +1 -0
  58. package/dist/model/capabilities.js +32 -0
  59. package/dist/model/capabilities.js.map +1 -0
  60. package/dist/model/emailMask.d.ts +22 -0
  61. package/dist/model/emailMask.d.ts.map +1 -0
  62. package/dist/model/emailMask.js +36 -0
  63. package/dist/model/emailMask.js.map +1 -0
  64. package/dist/model/mutations.d.ts +23 -3
  65. package/dist/model/mutations.d.ts.map +1 -1
  66. package/dist/model/mutations.js +39 -1
  67. package/dist/model/mutations.js.map +1 -1
  68. package/dist/model/queries.d.ts +37 -1
  69. package/dist/model/queries.d.ts.map +1 -1
  70. package/dist/model/queries.js +49 -0
  71. package/dist/model/queries.js.map +1 -1
  72. package/dist/model/queryKeys.d.ts +2 -0
  73. package/dist/model/queryKeys.d.ts.map +1 -1
  74. package/dist/model/queryKeys.js +6 -0
  75. package/dist/model/queryKeys.js.map +1 -1
  76. package/llms.txt +40 -15
  77. package/manifest.json +225 -3
  78. package/package.json +4 -4
  79. package/src/analytics/generated/events.json +1 -1
  80. package/src/api/generated/schema.ts +384 -4
  81. package/src/api/types.ts +20 -5
  82. package/src/api/workspacesApi.ts +50 -0
  83. package/src/default/InviteAcceptPage.tsx +373 -0
  84. package/src/default/MembersManager.tsx +155 -145
  85. package/src/default/index.ts +2 -0
  86. package/src/flows/inviteAcceptFlow.ts +289 -0
  87. package/src/headless/Can.tsx +49 -0
  88. package/src/headless/InviteAcceptFlow.tsx +105 -0
  89. package/src/headless/RoleSelect.tsx +58 -0
  90. package/src/i18n/generated/errors.gen.ts +24 -0
  91. package/src/i18n/generated/errors.json +62 -0
  92. package/src/i18n/generated/errors.ru.gen.ts +8 -0
  93. package/src/i18n/keys.ts +68 -0
  94. package/src/i18n/ru.ts +34 -0
  95. package/src/index.ts +26 -0
  96. package/src/model/capabilities.ts +37 -0
  97. package/src/model/emailMask.ts +37 -0
  98. package/src/model/mutations.ts +56 -4
  99. package/src/model/queries.ts +73 -0
  100. package/src/model/queryKeys.ts +10 -0
@@ -0,0 +1,126 @@
1
+ import { createFlowMachine } from "@stapel/core";
2
+ import { emailMatchesMask } from "../model/emailMask.js";
3
+ import { toFlowError } from "./errors.js";
4
+ /**
5
+ * Flow id: stapel-workspaces annotates no `@flow_step` yet (its
6
+ * `docs/flows.json` is empty — the pair's flow registry is the zero-flow
7
+ * shim), so this id is pair-local until the backend documents the flow;
8
+ * switch to the generated registry id when it lands.
9
+ */
10
+ const FLOW_ID = "workspaces.invite_accept";
11
+ function routeSignedIn(preview, email) {
12
+ return emailMatchesMask(email, preview.email_masked)
13
+ ? { step: "acceptPrompt", preview }
14
+ : { step: "wrongAccount", preview };
15
+ }
16
+ function routePreview(preview, sessionEmail) {
17
+ if (preview.status !== "pending") {
18
+ return { step: "unavailable", preview, status: preview.status };
19
+ }
20
+ if (sessionEmail !== null && sessionEmail !== "") {
21
+ return routeSignedIn(preview, sessionEmail);
22
+ }
23
+ return preview.email_registered
24
+ ? { step: "loginRequired", preview }
25
+ : { step: "newUser", preview };
26
+ }
27
+ export function createInviteAcceptFlow(deps) {
28
+ const machine = createFlowMachine({
29
+ id: FLOW_ID,
30
+ initial: { step: "idle" },
31
+ analytics: deps.analytics ?? null,
32
+ });
33
+ async function load(sessionEmail) {
34
+ await machine.run({ step: "loadingPreview" }, () => deps.api.getInvitationPreview(deps.token), {
35
+ resolve: (preview) => routePreview(preview, sessionEmail),
36
+ reject: (error) => ({
37
+ step: "previewError",
38
+ error: toFlowError(error),
39
+ }),
40
+ });
41
+ }
42
+ async function claim() {
43
+ const s = machine.getState();
44
+ if (s.step !== "newUser" && s.step !== "claimError")
45
+ return;
46
+ const { preview } = s;
47
+ await machine.run({ step: "claiming", preview }, () => deps.api.claimInvitation(deps.token), {
48
+ resolve: (r) => {
49
+ deps.onLoginGrant?.(r.grant_token);
50
+ return { step: "grantIssued", preview, grantToken: r.grant_token };
51
+ },
52
+ reject: (error) => ({
53
+ step: "claimError",
54
+ preview,
55
+ error: toFlowError(error),
56
+ }),
57
+ });
58
+ }
59
+ function sessionEstablished(email) {
60
+ const s = machine.getState();
61
+ if (s.step !== "loginRequired" && s.step !== "wrongAccount")
62
+ return;
63
+ machine.to(routeSignedIn(s.preview, email));
64
+ }
65
+ function grantExchanged() {
66
+ const s = machine.getState();
67
+ if (s.step !== "grantIssued")
68
+ return;
69
+ machine.to({ step: "basicData", preview: s.preview });
70
+ }
71
+ function completeBasicData() {
72
+ const s = machine.getState();
73
+ if (s.step !== "basicData")
74
+ return;
75
+ machine.to({ step: "acceptPrompt", preview: s.preview });
76
+ }
77
+ async function accept() {
78
+ const s = machine.getState();
79
+ if (s.step !== "acceptPrompt" && s.step !== "acceptError")
80
+ return;
81
+ const { preview } = s;
82
+ await machine.run({ step: "accepting", preview }, () => deps.api.acceptInvitation({ token: deps.token }), {
83
+ resolve: (member) => {
84
+ deps.onAccepted?.(member);
85
+ return { step: "accepted", member, preview };
86
+ },
87
+ reject: (error) => ({
88
+ step: "acceptError",
89
+ preview,
90
+ error: toFlowError(error),
91
+ }),
92
+ });
93
+ }
94
+ async function decline() {
95
+ const s = machine.getState();
96
+ if (s.step !== "acceptPrompt" && s.step !== "acceptError")
97
+ return;
98
+ const { preview } = s;
99
+ await machine.run({ step: "declining", preview }, () => deps.api.declineInvitation(deps.token), {
100
+ resolve: () => {
101
+ deps.onDeclined?.();
102
+ return { step: "declined", preview };
103
+ },
104
+ reject: (error) => ({
105
+ step: "acceptError",
106
+ preview,
107
+ error: toFlowError(error),
108
+ }),
109
+ });
110
+ }
111
+ function reset() {
112
+ machine.to({ step: "idle" });
113
+ }
114
+ return {
115
+ machine,
116
+ load,
117
+ claim,
118
+ sessionEstablished,
119
+ grantExchanged,
120
+ completeBasicData,
121
+ accept,
122
+ decline,
123
+ reset,
124
+ };
125
+ }
126
+ //# sourceMappingURL=inviteAcceptFlow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inviteAcceptFlow.js","sourceRoot":"","sources":["../../src/flows/inviteAcceptFlow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from "react";
2
+ /** Render-prop bag for {@link Can}'s function-children variant. */
3
+ export interface CanBag {
4
+ /** The wildcard-aware verdict for `capability` in `workspaceId`. */
5
+ readonly allowed: boolean;
6
+ /** The underlying read is still in flight (verdict is `false` meanwhile —
7
+ * deny-by-default; render a skeleton off this if flicker matters). */
8
+ readonly isLoading: boolean;
9
+ }
10
+ /**
11
+ * Capability gate (org-program §A2) — the mandate model's UI affordance:
12
+ *
13
+ * ```tsx
14
+ * <Can capability="meetings.spotlight" workspaceId={wsId}>
15
+ * <SpotlightButton />
16
+ * </Can>
17
+ * // or the render-prop variant, when "disabled" beats "hidden":
18
+ * <Can capability="members.invite" workspaceId={wsId}>
19
+ * {({ allowed }) => <Button disabled={!allowed}>…</Button>}
20
+ * </Can>
21
+ * ```
22
+ *
23
+ * Reads the caller's `my_capabilities` from the workspace detail
24
+ * ({@link useCapabilities}) and matches with the backend's ported wildcard
25
+ * matcher (`*` / `prefix.*`). Deny-by-default: nothing renders (or
26
+ * `allowed: false`) while loading, on error, for a non-member, or against a
27
+ * pre-0.6 backend. UI convenience ONLY — the backend re-checks every
28
+ * capability on every operation; hiding a button is not access control.
29
+ */
30
+ export declare function Can(props: {
31
+ /** Namespaced capability string, e.g. `"meetings.spotlight"`. */
32
+ capability: string;
33
+ workspaceId: string;
34
+ /** Static children render when allowed; function children always render,
35
+ * receiving the {@link CanBag}. */
36
+ children: ReactNode | ((bag: CanBag) => ReactNode);
37
+ /** Rendered when the verdict is a definite "no" (static children only). */
38
+ fallback?: ReactNode;
39
+ }): ReactNode;
40
+ //# sourceMappingURL=Can.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Can.d.ts","sourceRoot":"","sources":["../../src/headless/Can.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,mEAAmE;AACnE,MAAM,WAAW,MAAM;IACrB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;0EACsE;IACtE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE;IACzB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;uCACmC;IACnC,QAAQ,EAAE,SAAS,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC;IACnD,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,GAAG,SAAS,CAOZ"}
@@ -0,0 +1,30 @@
1
+ import { useCapabilities } from "../model/queries.js";
2
+ /**
3
+ * Capability gate (org-program §A2) — the mandate model's UI affordance:
4
+ *
5
+ * ```tsx
6
+ * <Can capability="meetings.spotlight" workspaceId={wsId}>
7
+ * <SpotlightButton />
8
+ * </Can>
9
+ * // or the render-prop variant, when "disabled" beats "hidden":
10
+ * <Can capability="members.invite" workspaceId={wsId}>
11
+ * {({ allowed }) => <Button disabled={!allowed}>…</Button>}
12
+ * </Can>
13
+ * ```
14
+ *
15
+ * Reads the caller's `my_capabilities` from the workspace detail
16
+ * ({@link useCapabilities}) and matches with the backend's ported wildcard
17
+ * matcher (`*` / `prefix.*`). Deny-by-default: nothing renders (or
18
+ * `allowed: false`) while loading, on error, for a non-member, or against a
19
+ * pre-0.6 backend. UI convenience ONLY — the backend re-checks every
20
+ * capability on every operation; hiding a button is not access control.
21
+ */
22
+ export function Can(props) {
23
+ const caps = useCapabilities(props.workspaceId);
24
+ const allowed = caps.can(props.capability);
25
+ if (typeof props.children === "function") {
26
+ return props.children({ allowed, isLoading: caps.isLoading });
27
+ }
28
+ return allowed ? props.children : (props.fallback ?? null);
29
+ }
30
+ //# sourceMappingURL=Can.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Can.js","sourceRoot":"","sources":["../../src/headless/Can.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAWtD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,GAAG,CAAC,KASnB;IACC,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { ReactNode } from "react";
2
+ import type { InviteAcceptState } from "../flows/inviteAcceptFlow.js";
3
+ import type { Member } from "../api/types.js";
4
+ /** Render-prop bag for {@link InviteAcceptFlow}. */
5
+ export interface InviteAcceptFlowBag {
6
+ readonly state: InviteAcceptState;
7
+ /** From `newUser`/`claimError`: mint the login grant. */
8
+ claim(): void;
9
+ /** From `loginRequired`/`wrongAccount`: a session now exists — re-route on
10
+ * its email. (Also fired automatically when the `sessionEmail` prop
11
+ * becomes non-null while parked in either step.) */
12
+ sessionEstablished(email: string): void;
13
+ /** From `grantIssued`: the host exchanged the grant at auth. */
14
+ grantExchanged(): void;
15
+ /** From `basicData`: the initial-setup slot finished. */
16
+ completeBasicData(): void;
17
+ accept(): void;
18
+ decline(): void;
19
+ /** Re-fetch the preview and re-route (e.g. after a logout on wrongAccount). */
20
+ reload(): void;
21
+ }
22
+ /**
23
+ * Headless `/invite/{token}` journey (org-program §B4) — the flow machine
24
+ * (`flows/inviteAcceptFlow.ts`, see ITS doc for the full state graph and the
25
+ * grant_token hand-off seam) wired to this pair's runtime. Renderless: every
26
+ * screen (accept modal, wrong-account, the login and initial-setup SLOTS) is
27
+ * the host's; the default `InviteAcceptPage` is the reference skin.
28
+ *
29
+ * The pair knows nothing about auth: pass the signed-in email (or null) as
30
+ * `sessionEmail`, and connect `onLoginGrant` to auth-react's
31
+ * `exchangeLoginGrant` yourself — then call `grantExchanged()`.
32
+ */
33
+ export declare function InviteAcceptFlow(props: {
34
+ /** The invite token from the URL — a bearer secret, never log it. */
35
+ token: string;
36
+ /** The signed-in account's email, or null/undefined when no session. */
37
+ sessionEmail?: string | null;
38
+ children: (bag: InviteAcceptFlowBag) => ReactNode;
39
+ /** The grant_token hand-off (a credential — exchange, never log). */
40
+ onLoginGrant?: (grantToken: string) => void;
41
+ onAccepted?: (member: Member) => void;
42
+ onDeclined?: () => void;
43
+ }): ReactNode;
44
+ //# sourceMappingURL=InviteAcceptFlow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InviteAcceptFlow.d.ts","sourceRoot":"","sources":["../../src/headless/InviteAcceptFlow.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAG9C,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,yDAAyD;IACzD,KAAK,IAAI,IAAI,CAAC;IACd;;wDAEoD;IACpD,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,gEAAgE;IAChE,cAAc,IAAI,IAAI,CAAC;IACvB,yDAAyD;IACzD,iBAAiB,IAAI,IAAI,CAAC;IAC1B,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;IAChB,+EAA+E;IAC/E,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,SAAS,CAAC;IAClD,qEAAqE;IACrE,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB,GAAG,SAAS,CAwDZ"}
@@ -0,0 +1,67 @@
1
+ import { useEffect, useMemo } from "react";
2
+ import { useFlow } from "@stapel/core";
3
+ import { createInviteAcceptFlow } from "../flows/inviteAcceptFlow.js";
4
+ import { useWorkspacesAnalytics, useWorkspacesApi } from "../model/context.js";
5
+ /**
6
+ * Headless `/invite/{token}` journey (org-program §B4) — the flow machine
7
+ * (`flows/inviteAcceptFlow.ts`, see ITS doc for the full state graph and the
8
+ * grant_token hand-off seam) wired to this pair's runtime. Renderless: every
9
+ * screen (accept modal, wrong-account, the login and initial-setup SLOTS) is
10
+ * the host's; the default `InviteAcceptPage` is the reference skin.
11
+ *
12
+ * The pair knows nothing about auth: pass the signed-in email (or null) as
13
+ * `sessionEmail`, and connect `onLoginGrant` to auth-react's
14
+ * `exchangeLoginGrant` yourself — then call `grantExchanged()`.
15
+ */
16
+ export function InviteAcceptFlow(props) {
17
+ const api = useWorkspacesApi();
18
+ const analytics = useWorkspacesAnalytics();
19
+ const sessionEmail = props.sessionEmail ?? null;
20
+ const { token, onLoginGrant, onAccepted, onDeclined } = props;
21
+ const flow = useMemo(() => createInviteAcceptFlow({
22
+ api,
23
+ analytics,
24
+ token,
25
+ ...(onLoginGrant !== undefined ? { onLoginGrant } : {}),
26
+ ...(onAccepted !== undefined ? { onAccepted } : {}),
27
+ ...(onDeclined !== undefined ? { onDeclined } : {}),
28
+ }), [api, analytics, token, onLoginGrant, onAccepted, onDeclined]);
29
+ const state = useFlow(flow.machine);
30
+ // Initial load — once per flow instance (a new token remounts the memo).
31
+ useEffect(() => {
32
+ if (flow.machine.getState().step === "idle") {
33
+ void flow.load(sessionEmail);
34
+ }
35
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- sessionEmail is read once at load time by design; later session changes re-route via the dedicated effect below, not a re-load
36
+ }, [flow]);
37
+ // Auto hand-off: the host's login slot signed the user in while we were
38
+ // parked in `loginRequired`/`wrongAccount` — re-route without requiring the
39
+ // host to call `sessionEstablished` manually.
40
+ useEffect(() => {
41
+ if (sessionEmail === null || sessionEmail === "")
42
+ return;
43
+ const step = flow.machine.getState().step;
44
+ if (step === "loginRequired" || step === "wrongAccount") {
45
+ flow.sessionEstablished(sessionEmail);
46
+ }
47
+ }, [flow, sessionEmail, state.step]);
48
+ return props.children({
49
+ state,
50
+ claim: () => {
51
+ void flow.claim();
52
+ },
53
+ sessionEstablished: flow.sessionEstablished,
54
+ grantExchanged: flow.grantExchanged,
55
+ completeBasicData: flow.completeBasicData,
56
+ accept: () => {
57
+ void flow.accept();
58
+ },
59
+ decline: () => {
60
+ void flow.decline();
61
+ },
62
+ reload: () => {
63
+ void flow.load(sessionEmail);
64
+ },
65
+ });
66
+ }
67
+ //# sourceMappingURL=InviteAcceptFlow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InviteAcceptFlow.js","sourceRoot":"","sources":["../../src/headless/InviteAcceptFlow.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAE3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAGtE,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAqB/E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAUhC;IACC,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,sBAAsB,EAAE,CAAC;IAC3C,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC;IAChD,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IAC9D,MAAM,IAAI,GAAG,OAAO,CAClB,GAAG,EAAE,CACH,sBAAsB,CAAC;QACrB,GAAG;QACH,SAAS;QACT,KAAK;QACL,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpD,CAAC,EACJ,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,CAAC,CAC9D,CAAC;IACF,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEpC,yEAAyE;IACzE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC5C,KAAK,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/B,CAAC;QACD,yLAAyL;IAC3L,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,wEAAwE;IACxE,4EAA4E;IAC5E,8CAA8C;IAC9C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,KAAK,EAAE;YAAE,OAAO;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QAC1C,IAAI,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;YACxD,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACxC,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAErC,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK;QACL,KAAK,EAAE,GAAG,EAAE;YACV,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;QACD,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;QAC3C,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;QACzC,MAAM,EAAE,GAAG,EAAE;YACX,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;QACtB,CAAC;QACD,MAAM,EAAE,GAAG,EAAE;YACX,KAAK,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { ReactNode } from "react";
2
+ import type { StapelApiError } from "@stapel/core";
3
+ import type { RoleInfo } from "../api/types.js";
4
+ /** Render-prop bag for {@link RoleSelect}. */
5
+ export interface RoleSelectBag {
6
+ /** The effective registry (GET /roles): builtin four + the deployment's
7
+ * overlay, rank-descending. Empty while loading. */
8
+ readonly roles: readonly RoleInfo[];
9
+ /**
10
+ * Display label for a role key: the i18n key `workspaces.role.<key>` when
11
+ * the current locale's merged bundle carries it (the pair ships the builtin
12
+ * four; a client bundle merges its own — e.g. `workspaces.role.secretary`),
13
+ * else the RAW role key. The raw-name fallback is deliberate: a
14
+ * deployment-defined role without a translation must still be pickable,
15
+ * never render as a dotted i18n key.
16
+ */
17
+ labelFor(role: string): string;
18
+ readonly isLoading: boolean;
19
+ readonly isError: boolean;
20
+ readonly error: StapelApiError | null;
21
+ }
22
+ /**
23
+ * Headless role picker over the effective role registry (org-program §A2) —
24
+ * the component that lets role UI stop hardcoding the builtin four. Bring
25
+ * your own `<Select>`:
26
+ *
27
+ * ```tsx
28
+ * <RoleSelect>
29
+ * {({ roles, labelFor }) => (
30
+ * <Select options={roles.map((r) => ({ value: r.role, label: labelFor(r.role) }))} />
31
+ * )}
32
+ * </RoleSelect>
33
+ * ```
34
+ *
35
+ * `owner` is in the registry too — filter it out where "only an owner grants
36
+ * owner" applies (the backend enforces regardless).
37
+ */
38
+ export declare function RoleSelect(props: {
39
+ children: (bag: RoleSelectBag) => ReactNode;
40
+ }): ReactNode;
41
+ //# sourceMappingURL=RoleSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RoleSelect.d.ts","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;wDACoD;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IACpC;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,QAAQ,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,SAAS,CAAC;CAC7C,GAAG,SAAS,CAcZ"}
@@ -0,0 +1,34 @@
1
+ import { useI18n } from "@stapel/core";
2
+ import { useRoles } from "../model/queries.js";
3
+ /**
4
+ * Headless role picker over the effective role registry (org-program §A2) —
5
+ * the component that lets role UI stop hardcoding the builtin four. Bring
6
+ * your own `<Select>`:
7
+ *
8
+ * ```tsx
9
+ * <RoleSelect>
10
+ * {({ roles, labelFor }) => (
11
+ * <Select options={roles.map((r) => ({ value: r.role, label: labelFor(r.role) }))} />
12
+ * )}
13
+ * </RoleSelect>
14
+ * ```
15
+ *
16
+ * `owner` is in the registry too — filter it out where "only an owner grants
17
+ * owner" applies (the backend enforces regardless).
18
+ */
19
+ export function RoleSelect(props) {
20
+ const query = useRoles();
21
+ const i18n = useI18n();
22
+ // Raw bundle lookup, NOT `t()`: `t` falls back to the key itself, which is
23
+ // exactly the wrong fallback here (a deployment role without a translation
24
+ // must show its raw name, not `workspaces.role.secretary`).
25
+ const bundle = i18n.getBundle();
26
+ return props.children({
27
+ roles: query.data ?? [],
28
+ labelFor: (role) => bundle[`workspaces.role.${role}`] ?? role,
29
+ isLoading: query.isLoading,
30
+ isError: query.isError,
31
+ error: query.error ?? null,
32
+ });
33
+ }
34
+ //# sourceMappingURL=RoleSelect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RoleSelect.js","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAqB/C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,UAAU,CAAC,KAE1B;IACC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,2EAA2E;IAC3E,2EAA2E;IAC3E,4DAA4D;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;QACvB,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC,IAAI,IAAI;QAC7D,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,IAAI;KAC3B,CAAC,CAAC;AACL,CAAC"}
@@ -124,6 +124,12 @@ export declare const WORKSPACES_ERRORS: {
124
124
  readonly remediation: "fix_input";
125
125
  readonly en: "Invalid advertisement ID";
126
126
  };
127
+ readonly "error.400.invalid_provision_username": {
128
+ readonly status: 400;
129
+ readonly params: readonly [];
130
+ readonly remediation: "fix_input";
131
+ readonly en: "Invalid username for a provisioned account";
132
+ };
127
133
  readonly "error.400.invalid_role": {
128
134
  readonly status: 400;
129
135
  readonly params: readonly [];
@@ -136,6 +142,12 @@ export declare const WORKSPACES_ERRORS: {
136
142
  readonly remediation: "fix_input";
137
143
  readonly en: "Invitation has already been used";
138
144
  };
145
+ readonly "error.400.invitation_declined": {
146
+ readonly status: 400;
147
+ readonly params: readonly [];
148
+ readonly remediation: "contact_support";
149
+ readonly en: "Invitation has been declined";
150
+ };
139
151
  readonly "error.400.invitation_expired": {
140
152
  readonly status: 400;
141
153
  readonly params: readonly [];
@@ -178,6 +190,18 @@ export declare const WORKSPACES_ERRORS: {
178
190
  readonly remediation: "reauthenticate";
179
191
  readonly en: "Authentication required";
180
192
  };
193
+ readonly "error.402.entitlement_required": {
194
+ readonly status: 402;
195
+ readonly params: readonly [];
196
+ readonly remediation: "fix_input";
197
+ readonly en: "The workspace owner's plan does not include this feature";
198
+ };
199
+ readonly "error.402.member_limit_reached": {
200
+ readonly status: 402;
201
+ readonly params: readonly ["limit"];
202
+ readonly remediation: "fix_input";
203
+ readonly en: "The workspace member limit ({limit}) has been reached";
204
+ };
181
205
  readonly "error.402.payment_required": {
182
206
  readonly status: 402;
183
207
  readonly params: readonly [];
@@ -202,6 +226,18 @@ export declare const WORKSPACES_ERRORS: {
202
226
  readonly remediation: "fix_input";
203
227
  readonly en: "The last owner cannot be removed; transfer ownership first";
204
228
  };
229
+ readonly "error.403.membership_suspended": {
230
+ readonly status: 403;
231
+ readonly params: readonly ["reason"];
232
+ readonly remediation: "fix_input";
233
+ readonly en: "Your membership in this workspace is suspended ({reason})";
234
+ };
235
+ readonly "error.403.missing_capability": {
236
+ readonly status: 403;
237
+ readonly params: readonly ["capability"];
238
+ readonly remediation: "contact_support";
239
+ readonly en: "Your role does not include the {capability} capability in this workspace";
240
+ };
205
241
  readonly "error.403.network_blocked": {
206
242
  readonly status: 403;
207
243
  readonly params: readonly [];
@@ -280,6 +316,12 @@ export declare const WORKSPACES_ERRORS: {
280
316
  readonly remediation: "fix_input";
281
317
  readonly en: "Resource already exists";
282
318
  };
319
+ readonly "error.409.email_already_registered": {
320
+ readonly status: 409;
321
+ readonly params: readonly [];
322
+ readonly remediation: "reauthenticate";
323
+ readonly en: "An account with this email already exists — log in instead";
324
+ };
283
325
  readonly "error.410.gone": {
284
326
  readonly status: 410;
285
327
  readonly params: readonly [];
@@ -334,6 +376,12 @@ export declare const WORKSPACES_ERRORS: {
334
376
  readonly remediation: "contact_support";
335
377
  readonly en: "Something went wrong";
336
378
  };
379
+ readonly "error.503.auth_unavailable": {
380
+ readonly status: 503;
381
+ readonly params: readonly [];
382
+ readonly remediation: "wait_and_retry";
383
+ readonly en: "The authentication service is unavailable; try again later";
384
+ };
337
385
  };
338
386
  export type WorkspacesErrorCode = keyof typeof WORKSPACES_ERRORS;
339
387
  /** Every backend error code this module can surface, sorted. */
@@ -1 +1 @@
1
- {"version":3,"file":"errors.gen.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated/errors.gen.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,gBAAgB,GAChB,gBAAgB,GAChB,QAAQ,GACR,WAAW,GACX,iBAAiB,GACjB,KAAK,CAAC;AAEV,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDpB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,iBAAiB,CAAC;AAEjE,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,EAAE,SAAS,mBAAmB,EAqDhE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAqDvE,CAAC"}
1
+ {"version":3,"file":"errors.gen.d.ts","sourceRoot":"","sources":["../../../src/i18n/generated/errors.gen.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,gBAAgB,GAChB,gBAAgB,GAChB,QAAQ,GACR,WAAW,GACX,iBAAiB,GACjB,KAAK,CAAC;AAEV,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6DpB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,iBAAiB,CAAC;AAEjE,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,EAAE,SAAS,mBAAmB,EA6DhE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CA6DvE,CAAC"}
@@ -24,8 +24,10 @@ export const WORKSPACES_ERRORS = {
24
24
  "error.400.field.required": { status: 400, params: ["field"], remediation: "fix_input", en: "{field} is required" },
25
25
  "error.400.field.unique": { status: 400, params: ["field"], remediation: "fix_input", en: "{field} must be unique" },
26
26
  "error.400.invalid_ad_id": { status: 400, params: [], remediation: "fix_input", en: "Invalid advertisement ID" },
27
+ "error.400.invalid_provision_username": { status: 400, params: [], remediation: "fix_input", en: "Invalid username for a provisioned account" },
27
28
  "error.400.invalid_role": { status: 400, params: [], remediation: "fix_input", en: "Invalid role" },
28
29
  "error.400.invitation_already_used": { status: 400, params: [], remediation: "fix_input", en: "Invitation has already been used" },
30
+ "error.400.invitation_declined": { status: 400, params: [], remediation: "contact_support", en: "Invitation has been declined" },
29
31
  "error.400.invitation_expired": { status: 400, params: [], remediation: "contact_support", en: "Invitation has expired" },
30
32
  "error.400.invitation_revoked": { status: 400, params: [], remediation: "contact_support", en: "Invitation has been revoked" },
31
33
  "error.400.validation_error": { status: 400, params: [], remediation: "fix_input", en: "Validation error" },
@@ -33,10 +35,14 @@ export const WORKSPACES_ERRORS = {
33
35
  "error.400.verification_invalid_factor": { status: 400, params: [], remediation: "verify", en: "This verification factor is not available" },
34
36
  "error.400.workspace_slug_taken": { status: 400, params: [], remediation: "fix_input", en: "Workspace slug is already taken" },
35
37
  "error.401.unauthorized": { status: 401, params: [], remediation: "reauthenticate", en: "Authentication required" },
38
+ "error.402.entitlement_required": { status: 402, params: [], remediation: "fix_input", en: "The workspace owner's plan does not include this feature" },
39
+ "error.402.member_limit_reached": { status: 402, params: ["limit"], remediation: "fix_input", en: "The workspace member limit ({limit}) has been reached" },
36
40
  "error.402.payment_required": { status: 402, params: [], remediation: "retry", en: "Payment required" },
37
41
  "error.403.forbidden": { status: 403, params: [], remediation: "retry", en: "You do not have permission to perform this action" },
38
42
  "error.403.forbidden_workspace": { status: 403, params: [], remediation: "contact_support", en: "You do not have access to this workspace" },
39
43
  "error.403.last_owner_cannot_be_removed": { status: 403, params: [], remediation: "fix_input", en: "The last owner cannot be removed; transfer ownership first" },
44
+ "error.403.membership_suspended": { status: 403, params: ["reason"], remediation: "fix_input", en: "Your membership in this workspace is suspended ({reason})" },
45
+ "error.403.missing_capability": { status: 403, params: ["capability"], remediation: "contact_support", en: "Your role does not include the {capability} capability in this workspace" },
40
46
  "error.403.network_blocked": { status: 403, params: [], remediation: "contact_support", en: "Requests from this network are not allowed" },
41
47
  "error.403.verification_enrollment_required": { status: 403, params: [], remediation: "verify", en: "Verification factor enrollment required" },
42
48
  "error.403.verification_required": { status: 403, params: [], remediation: "verify", en: "Additional verification required" },
@@ -50,6 +56,7 @@ export const WORKSPACES_ERRORS = {
50
56
  "error.406.not_acceptable": { status: 406, params: [], remediation: "retry", en: "Not acceptable" },
51
57
  "error.408.request_timeout": { status: 408, params: [], remediation: "retry", en: "Request timeout" },
52
58
  "error.409.conflict": { status: 409, params: [], remediation: "fix_input", en: "Resource already exists" },
59
+ "error.409.email_already_registered": { status: 409, params: [], remediation: "reauthenticate", en: "An account with this email already exists — log in instead" },
53
60
  "error.410.gone": { status: 410, params: [], remediation: "retry", en: "Resource has been permanently removed" },
54
61
  "error.413.payload_too_large": { status: 413, params: [], remediation: "retry", en: "Request body is too large" },
55
62
  "error.415.unsupported_media_type": { status: 415, params: [], remediation: "retry", en: "Unsupported media type" },
@@ -59,6 +66,7 @@ export const WORKSPACES_ERRORS = {
59
66
  "error.429.rate_limit": { status: 429, params: ["retry_after_minutes"], remediation: "wait_and_retry", en: "Too many attempts. Try again in {retry_after_minutes} minutes." },
60
67
  "error.429.too_many_requests": { status: 429, params: [], remediation: "wait_and_retry", en: "Too many requests. Please try again later." },
61
68
  "error.500.internal": { status: 500, params: [], remediation: "contact_support", en: "Something went wrong" },
69
+ "error.503.auth_unavailable": { status: 503, params: [], remediation: "wait_and_retry", en: "The authentication service is unavailable; try again later" },
62
70
  };
63
71
  /** Every backend error code this module can surface, sorted. */
64
72
  export const WORKSPACES_ERROR_CODES = [
@@ -79,8 +87,10 @@ export const WORKSPACES_ERROR_CODES = [
79
87
  "error.400.field.required",
80
88
  "error.400.field.unique",
81
89
  "error.400.invalid_ad_id",
90
+ "error.400.invalid_provision_username",
82
91
  "error.400.invalid_role",
83
92
  "error.400.invitation_already_used",
93
+ "error.400.invitation_declined",
84
94
  "error.400.invitation_expired",
85
95
  "error.400.invitation_revoked",
86
96
  "error.400.validation_error",
@@ -88,10 +98,14 @@ export const WORKSPACES_ERROR_CODES = [
88
98
  "error.400.verification_invalid_factor",
89
99
  "error.400.workspace_slug_taken",
90
100
  "error.401.unauthorized",
101
+ "error.402.entitlement_required",
102
+ "error.402.member_limit_reached",
91
103
  "error.402.payment_required",
92
104
  "error.403.forbidden",
93
105
  "error.403.forbidden_workspace",
94
106
  "error.403.last_owner_cannot_be_removed",
107
+ "error.403.membership_suspended",
108
+ "error.403.missing_capability",
95
109
  "error.403.network_blocked",
96
110
  "error.403.verification_enrollment_required",
97
111
  "error.403.verification_required",
@@ -105,6 +119,7 @@ export const WORKSPACES_ERROR_CODES = [
105
119
  "error.406.not_acceptable",
106
120
  "error.408.request_timeout",
107
121
  "error.409.conflict",
122
+ "error.409.email_already_registered",
108
123
  "error.410.gone",
109
124
  "error.413.payload_too_large",
110
125
  "error.415.unsupported_media_type",
@@ -114,6 +129,7 @@ export const WORKSPACES_ERROR_CODES = [
114
129
  "error.429.rate_limit",
115
130
  "error.429.too_many_requests",
116
131
  "error.500.internal",
132
+ "error.503.auth_unavailable",
117
133
  ];
118
134
  /**
119
135
  * English fallback bundle — one string per backend key, so a `StapelApiError.code`
@@ -138,8 +154,10 @@ export const workspacesErrorBundleEn = {
138
154
  "error.400.field.required": "{field} is required",
139
155
  "error.400.field.unique": "{field} must be unique",
140
156
  "error.400.invalid_ad_id": "Invalid advertisement ID",
157
+ "error.400.invalid_provision_username": "Invalid username for a provisioned account",
141
158
  "error.400.invalid_role": "Invalid role",
142
159
  "error.400.invitation_already_used": "Invitation has already been used",
160
+ "error.400.invitation_declined": "Invitation has been declined",
143
161
  "error.400.invitation_expired": "Invitation has expired",
144
162
  "error.400.invitation_revoked": "Invitation has been revoked",
145
163
  "error.400.validation_error": "Validation error",
@@ -147,10 +165,14 @@ export const workspacesErrorBundleEn = {
147
165
  "error.400.verification_invalid_factor": "This verification factor is not available",
148
166
  "error.400.workspace_slug_taken": "Workspace slug is already taken",
149
167
  "error.401.unauthorized": "Authentication required",
168
+ "error.402.entitlement_required": "The workspace owner's plan does not include this feature",
169
+ "error.402.member_limit_reached": "The workspace member limit ({limit}) has been reached",
150
170
  "error.402.payment_required": "Payment required",
151
171
  "error.403.forbidden": "You do not have permission to perform this action",
152
172
  "error.403.forbidden_workspace": "You do not have access to this workspace",
153
173
  "error.403.last_owner_cannot_be_removed": "The last owner cannot be removed; transfer ownership first",
174
+ "error.403.membership_suspended": "Your membership in this workspace is suspended ({reason})",
175
+ "error.403.missing_capability": "Your role does not include the {capability} capability in this workspace",
154
176
  "error.403.network_blocked": "Requests from this network are not allowed",
155
177
  "error.403.verification_enrollment_required": "Verification factor enrollment required",
156
178
  "error.403.verification_required": "Additional verification required",
@@ -164,6 +186,7 @@ export const workspacesErrorBundleEn = {
164
186
  "error.406.not_acceptable": "Not acceptable",
165
187
  "error.408.request_timeout": "Request timeout",
166
188
  "error.409.conflict": "Resource already exists",
189
+ "error.409.email_already_registered": "An account with this email already exists — log in instead",
167
190
  "error.410.gone": "Resource has been permanently removed",
168
191
  "error.413.payload_too_large": "Request body is too large",
169
192
  "error.415.unsupported_media_type": "Unsupported media type",
@@ -173,5 +196,6 @@ export const workspacesErrorBundleEn = {
173
196
  "error.429.rate_limit": "Too many attempts. Try again in {retry_after_minutes} minutes.",
174
197
  "error.429.too_many_requests": "Too many requests. Please try again later.",
175
198
  "error.500.internal": "Something went wrong",
199
+ "error.503.auth_unavailable": "The authentication service is unavailable; try again later",
176
200
  };
177
201
  //# sourceMappingURL=errors.gen.js.map