@stapel/workspaces-react 0.6.0 → 0.8.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 (105) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/api/generated/schema.d.ts +774 -7
  3. package/dist/api/generated/schema.d.ts.map +1 -1
  4. package/dist/api/types.d.ts +104 -7
  5. package/dist/api/types.d.ts.map +1 -1
  6. package/dist/api/workspacesApi.d.ts +55 -1
  7. package/dist/api/workspacesApi.d.ts.map +1 -1
  8. package/dist/api/workspacesApi.js +23 -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 +17 -4
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +10 -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 +107 -3
  65. package/dist/model/mutations.d.ts.map +1 -1
  66. package/dist/model/mutations.js +190 -1
  67. package/dist/model/mutations.js.map +1 -1
  68. package/dist/model/queries.d.ts +108 -2
  69. package/dist/model/queries.d.ts.map +1 -1
  70. package/dist/model/queries.js +139 -1
  71. package/dist/model/queries.js.map +1 -1
  72. package/dist/model/queryKeys.d.ts +12 -1
  73. package/dist/model/queryKeys.d.ts.map +1 -1
  74. package/dist/model/queryKeys.js +25 -0
  75. package/dist/model/queryKeys.js.map +1 -1
  76. package/dist/model/stepUp.d.ts +25 -0
  77. package/dist/model/stepUp.d.ts.map +1 -0
  78. package/dist/model/stepUp.js +79 -0
  79. package/dist/model/stepUp.js.map +1 -0
  80. package/llms.txt +49 -15
  81. package/manifest.json +346 -3
  82. package/package.json +5 -5
  83. package/src/analytics/generated/events.json +1 -1
  84. package/src/api/generated/schema.ts +774 -7
  85. package/src/api/types.ts +113 -7
  86. package/src/api/workspacesApi.ts +137 -0
  87. package/src/default/InviteAcceptPage.tsx +373 -0
  88. package/src/default/MembersManager.tsx +155 -145
  89. package/src/default/index.ts +2 -0
  90. package/src/flows/inviteAcceptFlow.ts +289 -0
  91. package/src/headless/Can.tsx +49 -0
  92. package/src/headless/InviteAcceptFlow.tsx +105 -0
  93. package/src/headless/RoleSelect.tsx +58 -0
  94. package/src/i18n/generated/errors.gen.ts +24 -0
  95. package/src/i18n/generated/errors.json +62 -0
  96. package/src/i18n/generated/errors.ru.gen.ts +8 -0
  97. package/src/i18n/keys.ts +68 -0
  98. package/src/i18n/ru.ts +34 -0
  99. package/src/index.ts +54 -1
  100. package/src/model/capabilities.ts +37 -0
  101. package/src/model/emailMask.ts +37 -0
  102. package/src/model/mutations.ts +248 -4
  103. package/src/model/queries.ts +205 -2
  104. package/src/model/queryKeys.ts +45 -1
  105. package/src/model/stepUp.ts +96 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Step-up ahead of the button, not behind it (org-program §A3).
3
+ *
4
+ * Some workspace operations are declared **high**: the backend gates them
5
+ * with `stapel_core`'s `@requires_verification(scope="sensitive")` on top of
6
+ * the capability check, so an ambient cookie is never enough. A client that
7
+ * only learns this from the 403 shows the admin a button, a spinner and a
8
+ * refusal; this module lets it know BEFORE the click.
9
+ *
10
+ * Two things travel from the backend, and they are not the same thing:
11
+ *
12
+ * 1. **A step-up CHALLENGE** — `verification.challenge_id` is present. This
13
+ * one nobody has to handle here: `@stapel/core`'s client intercepts it,
14
+ * hands it to the app's verification controller (auth-react's factor
15
+ * machines) and replays the original request with
16
+ * `X-Verification-Token`. It is a detour, not a failure.
17
+ * 2. **An ENROLLMENT demand** — `verification.enroll === true` and there is
18
+ * NO challenge id, because the user holds no factor that could be
19
+ * challenged. Core deliberately does not intercept this one (there is
20
+ * nothing to drive), so it surfaces as a plain 403 and the app must send
21
+ * the user to factor enrollment first. {@link readVerificationEnrollment}
22
+ * is how a hook says "this is that, and these are the factors".
23
+ *
24
+ * KEEP IN SYNC with stapel-workspaces `capabilities.BUILTIN_CAPABILITY_LEVELS`
25
+ * — a PORT for the same reason `capabilityMatches` is one: `GET /roles`
26
+ * publishes each role's capabilities but not their levels, so the level map
27
+ * cannot be read off the wire. A deployment may raise further capabilities via
28
+ * `STAPEL_WORKSPACES["CAPABILITY_LEVELS"]`; those are additive, so treating an
29
+ * unlisted capability as `standard` can only ever under-promise (the backend
30
+ * still challenges, and core still drives it).
31
+ */
32
+ import { isStapelApiError } from "@stapel/core";
33
+ /** The verification scope every `high` workspace capability is gated under. */
34
+ export const SENSITIVE_SCOPE = "sensitive";
35
+ /** PORT of `BUILTIN_CAPABILITY_LEVELS` — everything unlisted is `standard`. */
36
+ export const BUILTIN_CAPABILITY_LEVELS = {
37
+ "members.provision": "high",
38
+ "members.password.reset": "high",
39
+ "workspace.security.manage": "high",
40
+ };
41
+ /** The declared step-up level of one capability string. */
42
+ export function capabilityLevel(capability) {
43
+ return BUILTIN_CAPABILITY_LEVELS[capability] ?? "standard";
44
+ }
45
+ function asRecord(value) {
46
+ return typeof value === "object" && value !== null && !Array.isArray(value)
47
+ ? value
48
+ : null;
49
+ }
50
+ /**
51
+ * Read the enrollment demand out of a rejected request, or `null` when the
52
+ * error is anything else (including a step-up CHALLENGE, which carries a
53
+ * `challenge_id` and is core's business, not the consumer's).
54
+ */
55
+ export function readVerificationEnrollment(error) {
56
+ // Both dialects, narrowed through the imported predicate (never through a
57
+ // shape this call site invented): the typed error carries the parsed body,
58
+ // a rethrown raw envelope IS the body.
59
+ const body = asRecord(isStapelApiError(error) ? error.body : error);
60
+ if (body === null)
61
+ return null;
62
+ const verification = asRecord(body["verification"]);
63
+ if (verification === null)
64
+ return null;
65
+ if (verification["enroll"] !== true)
66
+ return null;
67
+ if (typeof verification["challenge_id"] === "string")
68
+ return null;
69
+ const factors = verification["factors"];
70
+ return {
71
+ scope: typeof verification["scope"] === "string"
72
+ ? verification["scope"]
73
+ : SENSITIVE_SCOPE,
74
+ factors: Array.isArray(factors)
75
+ ? factors.filter((f) => typeof f === "string")
76
+ : [],
77
+ };
78
+ }
79
+ //# sourceMappingURL=stepUp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stepUp.js","sourceRoot":"","sources":["../../src/model/stepUp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAK3C,+EAA+E;AAC/E,MAAM,CAAC,MAAM,yBAAyB,GAAqC;IACzE,mBAAmB,EAAE,MAAM;IAC3B,wBAAwB,EAAE,MAAM;IAChC,2BAA2B,EAAE,MAAM;CACpC,CAAC;AAEF,2DAA2D;AAC3D,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,OAAO,yBAAyB,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;AAC7D,CAAC;AAaD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACzE,CAAC,CAAE,KAAiC;QACpC,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAc;IAEd,0EAA0E;IAC1E,2EAA2E;IAC3E,uCAAuC;IACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACpE,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;IACpD,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,YAAY,CAAC,QAAQ,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjD,IAAI,OAAO,YAAY,CAAC,cAAc,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAClE,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IACxC,OAAO;QACL,KAAK,EACH,OAAO,YAAY,CAAC,OAAO,CAAC,KAAK,QAAQ;YACvC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC;YACvB,CAAC,CAAC,eAAe;QACrB,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAC7B,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YAC3D,CAAC,CAAC,EAAE;KACP,CAAC;AACJ,CAAC"}
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
- # @stapel/workspaces-react 0.6.0
1
+ # @stapel/workspaces-react 0.8.0
2
2
 
3
- Headless React flow pair for stapel-workspaces (contract >=0.4 <0.5) — business + state, zero visual opinion.
3
+ Headless React flow pair for stapel-workspaces (contract >=0.14 <0.15) — business + state, zero visual opinion.
4
4
  Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
5
5
  verification-403 interception, i18n engine, analytics facade, TanStack Query layer.
6
6
 
@@ -16,37 +16,59 @@ verification-403 interception, i18n engine, analytics facade, TanStack Query lay
16
16
  ## Layers
17
17
  api (typed client) · model (hooks, session) · flows (machines) · headless · i18n
18
18
 
19
+ ## Machines (createFlowMachine; analytics funnel flow.<id>.<step>)
20
+ - createInviteAcceptFlow
21
+
19
22
  ## Documented flows (flows.json — canonical id, steps, endpoints)
20
23
 
21
24
  ## Operations (typed; use the named op, never a path string)
22
25
  Request/response schema names are in manifest.json + the generated types.
23
- - workspaces_api_v1_create: POST /workspaces/api/v1/
24
- - workspaces_api_v1_destroy: DELETE /workspaces/api/v1/{workspace_id}
25
- - workspaces_api_v1_internal_members_retrieve: GET /workspaces/api/v1/internal/{workspace_id}/members/{user_id}
26
- - workspaces_api_v1_internal_users_personal_create: POST /workspaces/api/v1/internal/users/{user_id}/personal
27
- - workspaces_api_v1_invitations_accept_create: POST /workspaces/api/v1/invitations/accept
28
- - workspaces_api_v1_members_destroy: DELETE /workspaces/api/v1/{workspace_id}/members/{user_id}
29
- - workspaces_api_v1_members_invite_create: POST /workspaces/api/v1/{workspace_id}/members/invite
30
- - workspaces_api_v1_members_list: GET /workspaces/api/v1/{workspace_id}/members
31
- - workspaces_api_v1_members_partial_update: PATCH /workspaces/api/v1/{workspace_id}/members/{user_id}
32
- - workspaces_api_v1_partial_update: PATCH /workspaces/api/v1/{workspace_id}
33
- - workspaces_api_v1_retrieve: GET /workspaces/api/v1/
34
- - workspaces_api_v1_retrieve_2: GET /workspaces/api/v1/{workspace_id}
26
+ Paths are relative to `/workspaces/api/v1/`.
27
+ - workspaces_api_v1_create: POST /
28
+ - workspaces_api_v1_destroy: DELETE /{workspace_id}
29
+ - workspaces_api_v1_internal_members_retrieve: GET /internal/{workspace_id}/members/{user_id}
30
+ - workspaces_api_v1_internal_users_personal_create: POST /internal/users/{user_id}/personal
31
+ - workspaces_api_v1_invitations_accept_create: POST /invitations/accept
32
+ - workspaces_api_v1_invitations_claim_create: POST /invitations/{token}/claim
33
+ - workspaces_api_v1_invitations_decline_create: POST /invitations/{token}/decline
34
+ - workspaces_api_v1_invitations_list: GET /{workspace_id}/invitations
35
+ - workspaces_api_v1_invitations_resend_create: POST /{workspace_id}/invitations/{invitation_id}/resend
36
+ - workspaces_api_v1_invitations_retrieve: GET /invitations/{token}
37
+ - workspaces_api_v1_invitations_revoke_create: POST /{workspace_id}/invitations/{invitation_id}/revoke
38
+ - workspaces_api_v1_members_destroy: DELETE /{workspace_id}/members/{user_id}
39
+ - workspaces_api_v1_members_invite_create: POST /{workspace_id}/members/invite
40
+ - workspaces_api_v1_members_list: GET /{workspace_id}/members
41
+ - workspaces_api_v1_members_partial_update: PATCH /{workspace_id}/members/{user_id}
42
+ - workspaces_api_v1_members_password_reset_create: POST /{workspace_id}/members/{user_id}/password/reset
43
+ - workspaces_api_v1_members_provision_create: POST /{workspace_id}/members/provision
44
+ - workspaces_api_v1_partial_update: PATCH /{workspace_id}
45
+ - workspaces_api_v1_retrieve: GET /
46
+ - workspaces_api_v1_retrieve_2: GET /{workspace_id}
47
+ - workspaces_api_v1_roles_retrieve: GET /roles
35
48
 
36
49
  ## Query hooks (server state; keys come only from the key factory)
37
50
  - useAcceptInvitation (mutation) → acceptInvitation
51
+ - useClaimInvitation (mutation) → claimInvitation
38
52
  - useCreateWorkspace (mutation) → createWorkspace
53
+ - useDeclineInvitation (mutation) → declineInvitation
39
54
  - useDeleteWorkspace (mutation) → deleteWorkspace
55
+ - useInvitationPreview (query) → getInvitationPreview
56
+ - useInvitations (query) → listInvitations
40
57
  - useInviteMembers (mutation) → inviteMembers
41
58
  - useMembers (query) → listMembers
42
59
  - useRemoveMember (mutation) → removeMember
60
+ - useResendInvitation (mutation) → resendInvitation
61
+ - useResetMemberPassword (mutation) → resetMemberPassword
62
+ - useRevokeInvitation (mutation) → revokeInvitation
63
+ - useRoles (query) → listRoles
43
64
  - useUpdateMemberRole (mutation) → updateMemberRole
65
+ - useUpdateSecuritySettings (mutation) → getWorkspace+updateWorkspace
44
66
  - useUpdateWorkspace (mutation) → updateWorkspace
45
67
  - useWorkspace (query) → getWorkspace
46
68
  - useWorkspaces (query) → listWorkspaces
47
69
 
48
70
  ## Errors (render t(code, params); UX from remediation)
49
- 52 keys (full catalog: manifest.json §errors). By remediation: fix_input 24 · retry 12 · contact_support 5 · verify 5 · wait_and_retry 5 · reauthenticate 1.
71
+ 60 keys (full catalog: manifest.json §errors). By remediation: fix_input 28 · retry 12 · contact_support 7 · wait_and_retry 6 · verify 5 · reauthenticate 2.
50
72
  Param-bearing keys (interpolation slots matter):
51
73
  - error.400.field.blank [400] → fix_input {field}
52
74
  - error.400.field.does_not_exist [400] → fix_input {field}
@@ -59,6 +81,9 @@ Param-bearing keys (interpolation slots matter):
59
81
  - error.400.field.null [400] → fix_input {field}
60
82
  - error.400.field.required [400] → fix_input {field}
61
83
  - error.400.field.unique [400] → fix_input {field}
84
+ - error.402.member_limit_reached [402] → fix_input {limit}
85
+ - error.403.membership_suspended [403] → fix_input {reason}
86
+ - error.403.missing_capability [403] → contact_support {capability}
62
87
  - error.429.rate_limit [429] → wait_and_retry {retry_after_minutes}
63
88
 
64
89
  ## Analytics events (typed; defineEvent → events.json, drift-gated)
@@ -82,13 +107,22 @@ const { tracked } = useTracked();
82
107
 
83
108
  ## Demos (defineDemo → manifest.demos; compiled, linted, rendered examples)
84
109
  - workspaces.accept → <AcceptInvitation> [default] demo/AcceptInvitation.demo.tsx
110
+ - workspaces.can → <Can> [default] demo/Can.demo.tsx
111
+ - workspaces.invite-accept → <InviteAcceptFlow> [default] demo/InviteAcceptFlow.demo.tsx
85
112
  - workspaces.list → <WorkspaceList> [default] demo/WorkspaceList.demo.tsx
86
113
  - workspaces.members → <Members> [default] demo/Members.demo.tsx
87
114
  - workspaces.provider → <WorkspacesProvider> [default] demo/Workspaces.demo.tsx
115
+ - workspaces.role-select → <RoleSelect> [default] demo/RoleSelect.demo.tsx
88
116
  Each source file is the canonical usage snippet (open the default variant).
89
117
 
90
118
  ## Snippets
91
119
  ```tsx
120
+ // A flow machine drives state; you render each step.
121
+ const flow = createInviteAcceptFlow({ api: useWorkspacesApi() });
122
+ const s = useFlow(flow.machine);
123
+ if (s.step.endsWith('Error')) return <p>{t(s.error.code, s.error.params)}</p>;
124
+ ```
125
+ ```tsx
92
126
  // Error rendering + remediation branch (one pattern for every pair).
93
127
  const r = explainWorkspacesError(err.code); // 'wait_and_retry' | 'verify' | ...
94
128
  return <Alert action={r}>{t(err.code, err.params)}</Alert>;