@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.
- package/CHANGELOG.md +56 -0
- package/dist/api/generated/schema.d.ts +774 -7
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/api/types.d.ts +104 -7
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/workspacesApi.d.ts +55 -1
- package/dist/api/workspacesApi.d.ts.map +1 -1
- package/dist/api/workspacesApi.js +23 -0
- package/dist/api/workspacesApi.js.map +1 -1
- package/dist/default/InviteAcceptPage.d.ts +28 -0
- package/dist/default/InviteAcceptPage.d.ts.map +1 -0
- package/dist/default/InviteAcceptPage.js +141 -0
- package/dist/default/InviteAcceptPage.js.map +1 -0
- package/dist/default/MembersManager.d.ts +4 -4
- package/dist/default/MembersManager.d.ts.map +1 -1
- package/dist/default/MembersManager.js +61 -56
- package/dist/default/MembersManager.js.map +1 -1
- package/dist/default/index.d.ts +2 -0
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +1 -0
- package/dist/default/index.js.map +1 -1
- package/dist/flows/inviteAcceptFlow.d.ts +155 -0
- package/dist/flows/inviteAcceptFlow.d.ts.map +1 -0
- package/dist/flows/inviteAcceptFlow.js +126 -0
- package/dist/flows/inviteAcceptFlow.js.map +1 -0
- package/dist/headless/Can.d.ts +40 -0
- package/dist/headless/Can.d.ts.map +1 -0
- package/dist/headless/Can.js +30 -0
- package/dist/headless/Can.js.map +1 -0
- package/dist/headless/InviteAcceptFlow.d.ts +44 -0
- package/dist/headless/InviteAcceptFlow.d.ts.map +1 -0
- package/dist/headless/InviteAcceptFlow.js +67 -0
- package/dist/headless/InviteAcceptFlow.js.map +1 -0
- package/dist/headless/RoleSelect.d.ts +41 -0
- package/dist/headless/RoleSelect.d.ts.map +1 -0
- package/dist/headless/RoleSelect.js +34 -0
- package/dist/headless/RoleSelect.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +48 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.gen.js +24 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.js +8 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
- package/dist/i18n/keys.d.ts +28 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +64 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +30 -0
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/model/capabilities.d.ts +21 -0
- package/dist/model/capabilities.d.ts.map +1 -0
- package/dist/model/capabilities.js +32 -0
- package/dist/model/capabilities.js.map +1 -0
- package/dist/model/emailMask.d.ts +22 -0
- package/dist/model/emailMask.d.ts.map +1 -0
- package/dist/model/emailMask.js +36 -0
- package/dist/model/emailMask.js.map +1 -0
- package/dist/model/mutations.d.ts +107 -3
- package/dist/model/mutations.d.ts.map +1 -1
- package/dist/model/mutations.js +190 -1
- package/dist/model/mutations.js.map +1 -1
- package/dist/model/queries.d.ts +108 -2
- package/dist/model/queries.d.ts.map +1 -1
- package/dist/model/queries.js +139 -1
- package/dist/model/queries.js.map +1 -1
- package/dist/model/queryKeys.d.ts +12 -1
- package/dist/model/queryKeys.d.ts.map +1 -1
- package/dist/model/queryKeys.js +25 -0
- package/dist/model/queryKeys.js.map +1 -1
- package/dist/model/stepUp.d.ts +25 -0
- package/dist/model/stepUp.d.ts.map +1 -0
- package/dist/model/stepUp.js +79 -0
- package/dist/model/stepUp.js.map +1 -0
- package/llms.txt +49 -15
- package/manifest.json +346 -3
- package/package.json +5 -5
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +774 -7
- package/src/api/types.ts +113 -7
- package/src/api/workspacesApi.ts +137 -0
- package/src/default/InviteAcceptPage.tsx +373 -0
- package/src/default/MembersManager.tsx +155 -145
- package/src/default/index.ts +2 -0
- package/src/flows/inviteAcceptFlow.ts +289 -0
- package/src/headless/Can.tsx +49 -0
- package/src/headless/InviteAcceptFlow.tsx +105 -0
- package/src/headless/RoleSelect.tsx +58 -0
- package/src/i18n/generated/errors.gen.ts +24 -0
- package/src/i18n/generated/errors.json +62 -0
- package/src/i18n/generated/errors.ru.gen.ts +8 -0
- package/src/i18n/keys.ts +68 -0
- package/src/i18n/ru.ts +34 -0
- package/src/index.ts +54 -1
- package/src/model/capabilities.ts +37 -0
- package/src/model/emailMask.ts +37 -0
- package/src/model/mutations.ts +248 -4
- package/src/model/queries.ts +205 -2
- package/src/model/queryKeys.ts +45 -1
- 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.
|
|
1
|
+
# @stapel/workspaces-react 0.8.0
|
|
2
2
|
|
|
3
|
-
Headless React flow pair for stapel-workspaces (contract >=0.
|
|
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
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
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
|
-
|
|
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>;
|