@stapel/workspaces-react 0.14.0 → 0.15.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.
- package/CHANGELOG.md +54 -0
- package/dist/api/generated/schema.d.ts +165 -3
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/api/types.d.ts +22 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/workspacesApi.d.ts +5 -1
- package/dist/api/workspacesApi.d.ts.map +1 -1
- package/dist/api/workspacesApi.js +16 -0
- package/dist/api/workspacesApi.js.map +1 -1
- package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.es.gen.js +2 -0
- package/dist/i18n/generated/errors.es.gen.js.map +1 -1
- package/dist/i18n/generated/errors.gen.d.ts +12 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.gen.js +6 -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 +2 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model/mutations.d.ts.map +1 -1
- package/dist/model/mutations.js +21 -0
- package/dist/model/mutations.js.map +1 -1
- package/dist/model/queries.d.ts +33 -1
- package/dist/model/queries.d.ts.map +1 -1
- package/dist/model/queries.js +44 -0
- package/dist/model/queries.js.map +1 -1
- package/dist/model/queryKeys.d.ts +3 -1
- package/dist/model/queryKeys.d.ts.map +1 -1
- package/dist/model/queryKeys.js +6 -0
- package/dist/model/queryKeys.js.map +1 -1
- package/llms.txt +6 -3
- package/manifest.json +53 -2
- package/package.json +2 -2
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +165 -3
- package/src/api/types.ts +23 -0
- package/src/api/workspacesApi.ts +22 -0
- package/src/i18n/generated/errors.es.gen.ts +2 -0
- package/src/i18n/generated/errors.gen.ts +6 -0
- package/src/i18n/generated/errors.json +16 -0
- package/src/i18n/generated/errors.ru.gen.ts +2 -0
- package/src/index.ts +5 -0
- package/src/model/mutations.ts +21 -0
- package/src/model/queries.ts +51 -0
- package/src/model/queryKeys.ts +16 -1
package/manifest.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
|
|
3
3
|
"package": "@stapel/workspaces-react",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.1",
|
|
5
5
|
"backend": {
|
|
6
6
|
"module": "stapel-workspaces",
|
|
7
|
-
"contract": ">=0.
|
|
7
|
+
"contract": ">=0.24 <0.25"
|
|
8
8
|
},
|
|
9
9
|
"layers": [
|
|
10
10
|
"api",
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
"createInviteAcceptFlow"
|
|
19
19
|
],
|
|
20
20
|
"operations": {
|
|
21
|
+
"workspaces_api_v1_audit_list": {
|
|
22
|
+
"method": "GET",
|
|
23
|
+
"path": "/workspaces/api/v1/{workspace_id}/audit",
|
|
24
|
+
"tag": "Members",
|
|
25
|
+
"request": null,
|
|
26
|
+
"response": "PaginatedAuditEventResponseList"
|
|
27
|
+
},
|
|
21
28
|
"workspaces_api_v1_create": {
|
|
22
29
|
"method": "POST",
|
|
23
30
|
"path": "/workspaces/api/v1/",
|
|
@@ -217,6 +224,16 @@
|
|
|
217
224
|
]
|
|
218
225
|
]
|
|
219
226
|
},
|
|
227
|
+
"useAudit": {
|
|
228
|
+
"kind": "query",
|
|
229
|
+
"operation": "listAudit",
|
|
230
|
+
"queryKey": [
|
|
231
|
+
"workspaces",
|
|
232
|
+
"audit",
|
|
233
|
+
"{workspaceId}",
|
|
234
|
+
"{params}"
|
|
235
|
+
]
|
|
236
|
+
},
|
|
220
237
|
"useClaimInvitation": {
|
|
221
238
|
"kind": "mutation",
|
|
222
239
|
"operation": "claimInvitation"
|
|
@@ -298,6 +315,11 @@
|
|
|
298
315
|
"kind": "mutation",
|
|
299
316
|
"operation": "inviteMembers",
|
|
300
317
|
"invalidates": [
|
|
318
|
+
[
|
|
319
|
+
"workspaces",
|
|
320
|
+
"audit",
|
|
321
|
+
"{workspaceId}"
|
|
322
|
+
],
|
|
301
323
|
[
|
|
302
324
|
"workspaces",
|
|
303
325
|
"members",
|
|
@@ -319,6 +341,11 @@
|
|
|
319
341
|
"kind": "mutation",
|
|
320
342
|
"operation": "removeMember",
|
|
321
343
|
"invalidates": [
|
|
344
|
+
[
|
|
345
|
+
"workspaces",
|
|
346
|
+
"audit",
|
|
347
|
+
"{workspaceId}"
|
|
348
|
+
],
|
|
322
349
|
[
|
|
323
350
|
"workspaces",
|
|
324
351
|
"members",
|
|
@@ -407,6 +434,11 @@
|
|
|
407
434
|
"kind": "mutation",
|
|
408
435
|
"operation": "updateMemberRole",
|
|
409
436
|
"invalidates": [
|
|
437
|
+
[
|
|
438
|
+
"workspaces",
|
|
439
|
+
"audit",
|
|
440
|
+
"{workspaceId}"
|
|
441
|
+
],
|
|
410
442
|
[
|
|
411
443
|
"workspaces",
|
|
412
444
|
"members",
|
|
@@ -725,6 +757,11 @@
|
|
|
725
757
|
"params": [],
|
|
726
758
|
"remediation": "verify"
|
|
727
759
|
},
|
|
760
|
+
"error.403.workspace_creation_closed": {
|
|
761
|
+
"status": 403,
|
|
762
|
+
"params": [],
|
|
763
|
+
"remediation": "contact_support"
|
|
764
|
+
},
|
|
728
765
|
"error.404.ad_not_found": {
|
|
729
766
|
"status": 404,
|
|
730
767
|
"params": [],
|
|
@@ -810,6 +847,13 @@
|
|
|
810
847
|
"params": [],
|
|
811
848
|
"remediation": "wait_and_retry"
|
|
812
849
|
},
|
|
850
|
+
"error.429.invitation_resend_cooldown": {
|
|
851
|
+
"status": 429,
|
|
852
|
+
"params": [
|
|
853
|
+
"retry_after"
|
|
854
|
+
],
|
|
855
|
+
"remediation": "wait_and_retry"
|
|
856
|
+
},
|
|
813
857
|
"error.429.rate_limit": {
|
|
814
858
|
"status": 429,
|
|
815
859
|
"params": [
|
|
@@ -991,6 +1035,7 @@
|
|
|
991
1035
|
"error.403.role_exceeds_inviter_rank",
|
|
992
1036
|
"error.403.verification_enrollment_required",
|
|
993
1037
|
"error.403.verification_required",
|
|
1038
|
+
"error.403.workspace_creation_closed",
|
|
994
1039
|
"error.404.ad_not_found",
|
|
995
1040
|
"error.404.invitation_not_found",
|
|
996
1041
|
"error.404.member_not_found",
|
|
@@ -1008,6 +1053,7 @@
|
|
|
1008
1053
|
"error.422.unprocessable_entity",
|
|
1009
1054
|
"error.423.locked",
|
|
1010
1055
|
"error.423.verification_locked",
|
|
1056
|
+
"error.429.invitation_resend_cooldown",
|
|
1011
1057
|
"error.429.rate_limit",
|
|
1012
1058
|
"error.429.too_many_requests",
|
|
1013
1059
|
"error.500.internal",
|
|
@@ -1117,6 +1163,8 @@
|
|
|
1117
1163
|
"registerWorkspacesI18n",
|
|
1118
1164
|
"toFlowError",
|
|
1119
1165
|
"useAcceptInvitation",
|
|
1166
|
+
"useAudit",
|
|
1167
|
+
"useCanCreateWorkspace",
|
|
1120
1168
|
"useCapabilities",
|
|
1121
1169
|
"useCapabilityGate",
|
|
1122
1170
|
"useClaimInvitation",
|
|
@@ -1154,6 +1202,9 @@
|
|
|
1154
1202
|
],
|
|
1155
1203
|
"types": [
|
|
1156
1204
|
"AcceptInvitationBag",
|
|
1205
|
+
"AuditEvent",
|
|
1206
|
+
"AuditPage",
|
|
1207
|
+
"AuditParams",
|
|
1157
1208
|
"CanBag",
|
|
1158
1209
|
"CapabilitiesResult",
|
|
1159
1210
|
"CapabilityGate",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stapel/workspaces-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Headless React flow pair for stapel-workspaces: typed API client, TanStack Query hooks, flow machines, headless components, and i18n keys. Zero visual opinion.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
}
|
|
68
68
|
],
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@stapel/core": ">=0.
|
|
70
|
+
"@stapel/core": ">=0.13.0 <1.0.0",
|
|
71
71
|
"@tanstack/react-query": "^5.0.0",
|
|
72
72
|
"antd": ">=5.20.0 <7",
|
|
73
73
|
"react": ">=19"
|
|
@@ -89,6 +89,43 @@ export interface paths {
|
|
|
89
89
|
patch: operations["workspaces_api_v1_partial_update"];
|
|
90
90
|
trace?: never;
|
|
91
91
|
};
|
|
92
|
+
"/workspaces/api/v1/{workspace_id}/audit": {
|
|
93
|
+
parameters: {
|
|
94
|
+
query?: never;
|
|
95
|
+
header?: never;
|
|
96
|
+
path?: never;
|
|
97
|
+
cookie?: never;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* @description ``GET <workspace_id>/audit`` — the workspace's membership history.
|
|
101
|
+
*
|
|
102
|
+
* THE QUESTION THIS ANSWERS is "who let this person in, who took them out,
|
|
103
|
+
* and when" — which nothing in this module could answer before. Half the
|
|
104
|
+
* transitions the owner listed emit no comm event at all (an invitation
|
|
105
|
+
* created, an invitation accepted, an account born from one), and the ones
|
|
106
|
+
* that do emit are fire-and-forget notifications to other services: nothing
|
|
107
|
+
* keeps them, so there was no record to ask.
|
|
108
|
+
*
|
|
109
|
+
* GATED ON ``members.view``, not on a new capability of its own. An audit of
|
|
110
|
+
* who is in the workspace is the same class of fact as the member list —
|
|
111
|
+
* every role that may see who is in the room may see how they got there. A
|
|
112
|
+
* separate mandate would mean a deployment could grant one without the
|
|
113
|
+
* other, which describes no real product.
|
|
114
|
+
*
|
|
115
|
+
* Read-only by construction: this view has no write method, and the model
|
|
116
|
+
* has no update or delete path.
|
|
117
|
+
*
|
|
118
|
+
* **Permissions:** `IsAuthenticated`
|
|
119
|
+
*/
|
|
120
|
+
get: operations["workspaces_api_v1_audit_list"];
|
|
121
|
+
put?: never;
|
|
122
|
+
post?: never;
|
|
123
|
+
delete?: never;
|
|
124
|
+
options?: never;
|
|
125
|
+
head?: never;
|
|
126
|
+
patch?: never;
|
|
127
|
+
trace?: never;
|
|
128
|
+
};
|
|
92
129
|
"/workspaces/api/v1/{workspace_id}/invitations": {
|
|
93
130
|
parameters: {
|
|
94
131
|
query?: never;
|
|
@@ -180,9 +217,9 @@ export interface paths {
|
|
|
180
217
|
*
|
|
181
218
|
* Accepts an **expired** invitation on purpose — a dead TTL is the most
|
|
182
219
|
* common reason to resend — and refuses the three stored terminal states,
|
|
183
|
-
* which are decisions rather than delivery failures. The
|
|
184
|
-
*
|
|
185
|
-
*
|
|
220
|
+
* which are decisions rather than delivery failures. The TTL restarts, so
|
|
221
|
+
* the invitee has the full window again from the letter that just went
|
|
222
|
+
* out.
|
|
186
223
|
*
|
|
187
224
|
* Reviving an expired invitation re-reserves a seat, so the plan ceiling
|
|
188
225
|
* is re-checked here exactly as it is on invite: capability first ("may
|
|
@@ -190,6 +227,17 @@ export interface paths {
|
|
|
190
227
|
* that is already pending costs no additional seat and is never blocked
|
|
191
228
|
* by that check.
|
|
192
229
|
*
|
|
230
|
+
* Then the cooldown ("may we mail this PERSON again yet", 429). It is
|
|
231
|
+
* read here and enforced again inside the service's row lock, the same
|
|
232
|
+
* two-level shape the state check already has: the view answers a number
|
|
233
|
+
* the admin can read, the lock is what actually makes two simultaneous
|
|
234
|
+
* presses send one letter.
|
|
235
|
+
*
|
|
236
|
+
* The token is NOT rotated by default from 0.23 — the invitee's existing
|
|
237
|
+
* link keeps working, because the resend goes to the same mailbox that
|
|
238
|
+
* link is already sitting in. See
|
|
239
|
+
* ``STAPEL_WORKSPACES["INVITATION_ROTATE_TOKEN_ON_RESEND"]``.
|
|
240
|
+
*
|
|
193
241
|
* **Permissions:** `IsAuthenticated`
|
|
194
242
|
*/
|
|
195
243
|
post: operations["workspaces_api_v1_invitations_resend_create"];
|
|
@@ -739,6 +787,54 @@ export interface paths {
|
|
|
739
787
|
export type webhooks = Record<string, never>;
|
|
740
788
|
export interface components {
|
|
741
789
|
schemas: {
|
|
790
|
+
/** @description One line of a workspace's membership history. */
|
|
791
|
+
AuditEventResponse: {
|
|
792
|
+
/**
|
|
793
|
+
* Format: uuid
|
|
794
|
+
* @description Audit event UUID
|
|
795
|
+
*/
|
|
796
|
+
id: string;
|
|
797
|
+
/**
|
|
798
|
+
* @description What happened — a CLOSED vocabulary (models.AuditAction): invitation_created, invitation_accepted, invitation_revoked, invitation_declined, account_created_by_invitation, member_joined, member_provisioned, member_removed, member_role_changed, member_suspended, member_unsuspended
|
|
799
|
+
* @example member_removed
|
|
800
|
+
*/
|
|
801
|
+
action: string;
|
|
802
|
+
/**
|
|
803
|
+
* Format: uuid
|
|
804
|
+
* @description Who performed it; null for a transition nobody performed (a policy sweep)
|
|
805
|
+
* @example 0192a...
|
|
806
|
+
*/
|
|
807
|
+
actor_id: string | null;
|
|
808
|
+
/** @description Best-effort name of the actor, resolved like every other name in this module (stapel-profiles first); "" when unknown */
|
|
809
|
+
actor_display_name: string;
|
|
810
|
+
/**
|
|
811
|
+
* Format: uuid
|
|
812
|
+
* @description Whom it happened to; null for an invitation to an address with no account yet
|
|
813
|
+
* @example 0192b...
|
|
814
|
+
*/
|
|
815
|
+
subject_id: string | null;
|
|
816
|
+
/** @description Best-effort name of the subject; "" when unknown */
|
|
817
|
+
subject_display_name: string;
|
|
818
|
+
/**
|
|
819
|
+
* @description The invited address, when the row is about an invitation
|
|
820
|
+
* @example new@acme.test
|
|
821
|
+
*/
|
|
822
|
+
subject_email: string;
|
|
823
|
+
/**
|
|
824
|
+
* @description Role involved, when the action carries one
|
|
825
|
+
* @example member
|
|
826
|
+
*/
|
|
827
|
+
role: string;
|
|
828
|
+
/** @description Action-specific extras — old_role/new_role for a role change, reason for a suspension */
|
|
829
|
+
metadata: {
|
|
830
|
+
[key: string]: unknown;
|
|
831
|
+
};
|
|
832
|
+
/**
|
|
833
|
+
* @description ISO 8601 timestamp
|
|
834
|
+
* @example 2026-08-13T10:00:00Z
|
|
835
|
+
*/
|
|
836
|
+
created_at: string;
|
|
837
|
+
};
|
|
742
838
|
/** @description What the name is after the write — the stored value, not the request. */
|
|
743
839
|
DisplayNameResponse: {
|
|
744
840
|
/**
|
|
@@ -891,6 +987,17 @@ export interface components {
|
|
|
891
987
|
* @example Ada Lovelace
|
|
892
988
|
*/
|
|
893
989
|
display_name?: string | null;
|
|
990
|
+
/**
|
|
991
|
+
* Format: uuid
|
|
992
|
+
* @description UUID of the admin who withdrew it; null unless revoked, or when that account is gone
|
|
993
|
+
* @example 0192a...
|
|
994
|
+
*/
|
|
995
|
+
revoked_by_id?: string | null;
|
|
996
|
+
/**
|
|
997
|
+
* @description ISO 8601 time the invitation was last emailed; null if no letter was ever sent
|
|
998
|
+
* @example 2026-07-24T10:00:00Z
|
|
999
|
+
*/
|
|
1000
|
+
last_sent_at?: string | null;
|
|
894
1001
|
};
|
|
895
1002
|
/** @description Invite payload. */
|
|
896
1003
|
MemberInviteRequest: {
|
|
@@ -1003,6 +1110,19 @@ export interface components {
|
|
|
1003
1110
|
*/
|
|
1004
1111
|
display_name?: string | null;
|
|
1005
1112
|
};
|
|
1113
|
+
PaginatedAuditEventResponseList: {
|
|
1114
|
+
items: components["schemas"]["AuditEventResponse"][];
|
|
1115
|
+
/** @description Anchor value for next page */
|
|
1116
|
+
next_anchor?: string | null;
|
|
1117
|
+
/** @description Anchor value for previous page */
|
|
1118
|
+
prev_anchor?: string | null;
|
|
1119
|
+
/** @description Whether there are more items after this page */
|
|
1120
|
+
has_next: boolean;
|
|
1121
|
+
/** @description Whether there are items before this page */
|
|
1122
|
+
has_prev: boolean;
|
|
1123
|
+
/** @description Number of items in current page */
|
|
1124
|
+
count: number;
|
|
1125
|
+
};
|
|
1006
1126
|
PaginatedInvitationResponseList: {
|
|
1007
1127
|
items: components["schemas"]["InvitationResponse"][];
|
|
1008
1128
|
/** @description Anchor value for next page */
|
|
@@ -1203,6 +1323,11 @@ export interface components {
|
|
|
1203
1323
|
* @example a8bba7ae-5d2e-43e5-9911-0553e7df50b3
|
|
1204
1324
|
*/
|
|
1205
1325
|
preferred_workspace_id?: string;
|
|
1326
|
+
/**
|
|
1327
|
+
* @description Whether THIS caller may found a new workspace on this instance (WORKSPACE_CREATE_POLICY, evaluated by services.can_create_workspace). The ANSWER, not the policy name: resolving "instance_owner" client-side would mean re-implementing the instance-owner lookup in every client, and a client that got it wrong would draw a button that 403s — or hide one that should be there. Rides the list because the workspace switcher is the surface that draws "+ New space", and it already fetches this
|
|
1328
|
+
* @example true
|
|
1329
|
+
*/
|
|
1330
|
+
can_create_workspace?: boolean;
|
|
1206
1331
|
};
|
|
1207
1332
|
/** @description Workspace details. */
|
|
1208
1333
|
WorkspaceResponse: {
|
|
@@ -1266,6 +1391,11 @@ export interface components {
|
|
|
1266
1391
|
* ]
|
|
1267
1392
|
*/
|
|
1268
1393
|
my_capabilities?: string[];
|
|
1394
|
+
/**
|
|
1395
|
+
* @description The owner's display name, best-effort from stapel-profiles (empty when it has none, is not installed, or is unreachable — never invented). Exists because a workspace NAME stopped identifying one: a person can belong to several spaces all called "Personal" — one per company that made them one — and ownership can be handed over, so "Personal" answers nothing on its own. Pickers draw it as the second line under the name. Resolved here rather than in each client, which would otherwise each need a profiles batch of its own for a caption
|
|
1396
|
+
* @example Victor P.
|
|
1397
|
+
*/
|
|
1398
|
+
owner_display_name?: string;
|
|
1269
1399
|
};
|
|
1270
1400
|
};
|
|
1271
1401
|
responses: never;
|
|
@@ -1388,6 +1518,38 @@ export interface operations {
|
|
|
1388
1518
|
};
|
|
1389
1519
|
};
|
|
1390
1520
|
};
|
|
1521
|
+
workspaces_api_v1_audit_list: {
|
|
1522
|
+
parameters: {
|
|
1523
|
+
query?: {
|
|
1524
|
+
/** @description Narrow to one action (models.AuditAction). An unknown value matches nothing rather than being ignored — a filter that silently does not apply is worse than an empty page. */
|
|
1525
|
+
action?: string;
|
|
1526
|
+
/** @description Anchor value to paginate from (exclusive) */
|
|
1527
|
+
anchor?: string;
|
|
1528
|
+
/** @description Pagination direction */
|
|
1529
|
+
direction?: "next" | "prev" | "center";
|
|
1530
|
+
/** @description Number of items (default 100, max 500) */
|
|
1531
|
+
limit?: number;
|
|
1532
|
+
/** @description Narrow to one person's history (as the SUBJECT). */
|
|
1533
|
+
user_id?: string;
|
|
1534
|
+
};
|
|
1535
|
+
header?: never;
|
|
1536
|
+
path: {
|
|
1537
|
+
workspace_id: string;
|
|
1538
|
+
};
|
|
1539
|
+
cookie?: never;
|
|
1540
|
+
};
|
|
1541
|
+
requestBody?: never;
|
|
1542
|
+
responses: {
|
|
1543
|
+
200: {
|
|
1544
|
+
headers: {
|
|
1545
|
+
[name: string]: unknown;
|
|
1546
|
+
};
|
|
1547
|
+
content: {
|
|
1548
|
+
"application/json": components["schemas"]["PaginatedAuditEventResponseList"];
|
|
1549
|
+
};
|
|
1550
|
+
};
|
|
1551
|
+
};
|
|
1552
|
+
};
|
|
1391
1553
|
workspaces_api_v1_invitations_list: {
|
|
1392
1554
|
parameters: {
|
|
1393
1555
|
query?: {
|
package/src/api/types.ts
CHANGED
|
@@ -33,6 +33,29 @@ export type PreferredWorkspace = Schemas["PreferredWorkspaceRequest"];
|
|
|
33
33
|
/** PUT/DELETE /me/preferred-workspace 200 body — the choice after the write
|
|
34
34
|
* (`""` after a DELETE cleared it). */
|
|
35
35
|
export type PreferredWorkspaceResult = Schemas["PreferredWorkspaceResponse"];
|
|
36
|
+
/** One line of a workspace's membership history (`GET {id}/audit`).
|
|
37
|
+
*
|
|
38
|
+
* The vocabulary of `action` is CLOSED on the server (stapel-workspaces
|
|
39
|
+
* `models.AuditAction`) — an audit whose action is a free string is a log:
|
|
40
|
+
* nobody can filter it, translate it, or notice that a new lifecycle
|
|
41
|
+
* transition shipped without a line.
|
|
42
|
+
*/
|
|
43
|
+
export type AuditEvent = Schemas["AuditEventResponse"];
|
|
44
|
+
/** `GET {id}/audit` 200 body — one anchor-paginated page of history. */
|
|
45
|
+
export type AuditPage = Schemas["PaginatedAuditEventResponseList"];
|
|
46
|
+
/** Query parameters for the audit list (narrow by action or by subject). */
|
|
47
|
+
export interface AuditParams {
|
|
48
|
+
/** One `AuditEvent["action"]`. An unknown value matches nothing rather than
|
|
49
|
+
* being ignored — a filter that silently does not apply is worse than an
|
|
50
|
+
* empty page. */
|
|
51
|
+
readonly action?: string;
|
|
52
|
+
/** One person's history, as the SUBJECT of the events. */
|
|
53
|
+
readonly user_id?: string;
|
|
54
|
+
readonly anchor?: string;
|
|
55
|
+
readonly direction?: "next" | "prev";
|
|
56
|
+
readonly limit?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
36
59
|
/** POST / request body — create a workspace (slug auto-generated when omitted). */
|
|
37
60
|
export type WorkspaceCreate = Schemas["WorkspaceCreateRequest"];
|
|
38
61
|
/** PATCH /{id} request body — a partial name / slug / settings update. */
|
package/src/api/workspacesApi.ts
CHANGED
|
@@ -12,6 +12,8 @@ import type {
|
|
|
12
12
|
MemberPasswordReset,
|
|
13
13
|
MemberPasswordResetResult,
|
|
14
14
|
MemberRoleUpdate,
|
|
15
|
+
AuditParams,
|
|
16
|
+
AuditPage,
|
|
15
17
|
MembersParams,
|
|
16
18
|
PreferredWorkspace,
|
|
17
19
|
PreferredWorkspaceResult,
|
|
@@ -88,6 +90,14 @@ export interface WorkspacesApi {
|
|
|
88
90
|
clearPreferredWorkspace(): Promise<PreferredWorkspaceResult>;
|
|
89
91
|
|
|
90
92
|
/** A workspace's members (viewer+), an anchor-paginated page. */
|
|
93
|
+
/** The workspace's membership history, newest first — who let this person
|
|
94
|
+
* in, who took them out, and when (stapel-workspaces 0.24). Anchor-paginated
|
|
95
|
+
* like the member and invitation lists. */
|
|
96
|
+
listAudit(
|
|
97
|
+
workspaceId: string,
|
|
98
|
+
params?: AuditParams
|
|
99
|
+
): Promise<AuditPage>;
|
|
100
|
+
|
|
91
101
|
listMembers(
|
|
92
102
|
workspaceId: string,
|
|
93
103
|
params?: MembersParams
|
|
@@ -259,6 +269,18 @@ export function createWorkspacesApi(client: StapelClient): WorkspacesApi {
|
|
|
259
269
|
clearPreferredWorkspace: () =>
|
|
260
270
|
client.delete("/me/preferred-workspace", mutating()),
|
|
261
271
|
|
|
272
|
+
listAudit: (workspaceId, params) => {
|
|
273
|
+
const query: Record<string, string | number> = {};
|
|
274
|
+
if (params?.action !== undefined) query.action = params.action;
|
|
275
|
+
if (params?.user_id !== undefined) query.user_id = params.user_id;
|
|
276
|
+
if (params?.anchor !== undefined) query.anchor = params.anchor;
|
|
277
|
+
if (params?.direction !== undefined) query.direction = params.direction;
|
|
278
|
+
if (params?.limit !== undefined) query.limit = params.limit;
|
|
279
|
+
return client.get(`/${encodeURIComponent(workspaceId)}/audit`, {
|
|
280
|
+
query,
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
|
|
262
284
|
listMembers: (workspaceId, params) => {
|
|
263
285
|
const query: Record<string, string | number> = {};
|
|
264
286
|
if (params?.anchor !== undefined) query.anchor = params.anchor;
|
|
@@ -56,6 +56,7 @@ export const workspacesErrorBundleEs: Record<WorkspacesErrorCode, string> = {
|
|
|
56
56
|
"error.403.role_exceeds_inviter_rank": "No puedes conceder un rol superior al tuyo ({role})",
|
|
57
57
|
"error.403.verification_enrollment_required": "Es necesario registrar un factor de verificación.",
|
|
58
58
|
"error.403.verification_required": "Se requiere verificación adicional",
|
|
59
|
+
"error.403.workspace_creation_closed": "Esta instancia no te permite crear espacios de trabajo",
|
|
59
60
|
"error.404.ad_not_found": "Anuncio no encontrado",
|
|
60
61
|
"error.404.invitation_not_found": "Invitación no encontrada",
|
|
61
62
|
"error.404.member_not_found": "Miembro no encontrado en este espacio de trabajo",
|
|
@@ -73,6 +74,7 @@ export const workspacesErrorBundleEs: Record<WorkspacesErrorCode, string> = {
|
|
|
73
74
|
"error.422.unprocessable_entity": "Entidad no procesable",
|
|
74
75
|
"error.423.locked": "El recurso está bloqueado",
|
|
75
76
|
"error.423.verification_locked": "Demasiados intentos fallidos — verificación bloqueada",
|
|
77
|
+
"error.429.invitation_resend_cooldown": "Esta invitación se envió por correo hace poco; puedes volver a enviarla en {retry_after} segundos",
|
|
76
78
|
"error.429.rate_limit": "Demasiados intentos. Inténtalo de nuevo en {retry_after_minutes} minutos.",
|
|
77
79
|
"error.429.too_many_requests": "Demasiadas solicitudes. Inténtalo de nuevo más tarde.",
|
|
78
80
|
"error.500.internal": "Algo salió mal",
|
|
@@ -79,6 +79,7 @@ export const WORKSPACES_ERRORS = {
|
|
|
79
79
|
"error.403.role_exceeds_inviter_rank": { status: 403, params: ["role"], remediation: "fix_input", en: "You cannot grant a role that outranks your own ({role})" },
|
|
80
80
|
"error.403.verification_enrollment_required": { status: 403, params: [], remediation: "verify", en: "Verification factor enrollment required" },
|
|
81
81
|
"error.403.verification_required": { status: 403, params: [], remediation: "verify", en: "Additional verification required" },
|
|
82
|
+
"error.403.workspace_creation_closed": { status: 403, params: [], remediation: "contact_support", en: "This instance does not allow you to create workspaces" },
|
|
82
83
|
"error.404.ad_not_found": { status: 404, params: [], remediation: "retry", en: "Listing not found" },
|
|
83
84
|
"error.404.invitation_not_found": { status: 404, params: [], remediation: "fix_input", en: "Invitation not found" },
|
|
84
85
|
"error.404.member_not_found": { status: 404, params: [], remediation: "fix_input", en: "Member not found in this workspace" },
|
|
@@ -96,6 +97,7 @@ export const WORKSPACES_ERRORS = {
|
|
|
96
97
|
"error.422.unprocessable_entity": { status: 422, params: [], remediation: "wait_and_retry", en: "Unprocessable entity" },
|
|
97
98
|
"error.423.locked": { status: 423, params: [], remediation: "wait_and_retry", en: "Resource is locked" },
|
|
98
99
|
"error.423.verification_locked": { status: 423, params: [], remediation: "wait_and_retry", en: "Too many failed attempts — verification locked" },
|
|
100
|
+
"error.429.invitation_resend_cooldown": { status: 429, params: ["retry_after"], remediation: "wait_and_retry", en: "This invitation was emailed recently; you can send it again in {retry_after} seconds" },
|
|
99
101
|
"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." },
|
|
100
102
|
"error.429.too_many_requests": { status: 429, params: [], remediation: "wait_and_retry", en: "Too many requests. Please try again later." },
|
|
101
103
|
"error.500.internal": { status: 500, params: [], remediation: "contact_support", en: "Something went wrong" },
|
|
@@ -152,6 +154,7 @@ export const WORKSPACES_ERROR_CODES: readonly WorkspacesErrorCode[] = [
|
|
|
152
154
|
"error.403.role_exceeds_inviter_rank",
|
|
153
155
|
"error.403.verification_enrollment_required",
|
|
154
156
|
"error.403.verification_required",
|
|
157
|
+
"error.403.workspace_creation_closed",
|
|
155
158
|
"error.404.ad_not_found",
|
|
156
159
|
"error.404.invitation_not_found",
|
|
157
160
|
"error.404.member_not_found",
|
|
@@ -169,6 +172,7 @@ export const WORKSPACES_ERROR_CODES: readonly WorkspacesErrorCode[] = [
|
|
|
169
172
|
"error.422.unprocessable_entity",
|
|
170
173
|
"error.423.locked",
|
|
171
174
|
"error.423.verification_locked",
|
|
175
|
+
"error.429.invitation_resend_cooldown",
|
|
172
176
|
"error.429.rate_limit",
|
|
173
177
|
"error.429.too_many_requests",
|
|
174
178
|
"error.500.internal",
|
|
@@ -227,6 +231,7 @@ export const workspacesErrorBundleEn: Record<WorkspacesErrorCode, string> = {
|
|
|
227
231
|
"error.403.role_exceeds_inviter_rank": "You cannot grant a role that outranks your own ({role})",
|
|
228
232
|
"error.403.verification_enrollment_required": "Verification factor enrollment required",
|
|
229
233
|
"error.403.verification_required": "Additional verification required",
|
|
234
|
+
"error.403.workspace_creation_closed": "This instance does not allow you to create workspaces",
|
|
230
235
|
"error.404.ad_not_found": "Listing not found",
|
|
231
236
|
"error.404.invitation_not_found": "Invitation not found",
|
|
232
237
|
"error.404.member_not_found": "Member not found in this workspace",
|
|
@@ -244,6 +249,7 @@ export const workspacesErrorBundleEn: Record<WorkspacesErrorCode, string> = {
|
|
|
244
249
|
"error.422.unprocessable_entity": "Unprocessable entity",
|
|
245
250
|
"error.423.locked": "Resource is locked",
|
|
246
251
|
"error.423.verification_locked": "Too many failed attempts — verification locked",
|
|
252
|
+
"error.429.invitation_resend_cooldown": "This invitation was emailed recently; you can send it again in {retry_after} seconds",
|
|
247
253
|
"error.429.rate_limit": "Too many attempts. Try again in {retry_after_minutes} minutes.",
|
|
248
254
|
"error.429.too_many_requests": "Too many requests. Please try again later.",
|
|
249
255
|
"error.500.internal": "Something went wrong",
|
|
@@ -341,6 +341,13 @@
|
|
|
341
341
|
"remediation": "verify",
|
|
342
342
|
"en": "Additional verification required"
|
|
343
343
|
},
|
|
344
|
+
{
|
|
345
|
+
"code": "error.403.workspace_creation_closed",
|
|
346
|
+
"status": 403,
|
|
347
|
+
"params": [],
|
|
348
|
+
"remediation": "contact_support",
|
|
349
|
+
"en": "This instance does not allow you to create workspaces"
|
|
350
|
+
},
|
|
344
351
|
{
|
|
345
352
|
"code": "error.404.ad_not_found",
|
|
346
353
|
"status": 404,
|
|
@@ -460,6 +467,15 @@
|
|
|
460
467
|
"remediation": "wait_and_retry",
|
|
461
468
|
"en": "Too many failed attempts — verification locked"
|
|
462
469
|
},
|
|
470
|
+
{
|
|
471
|
+
"code": "error.429.invitation_resend_cooldown",
|
|
472
|
+
"status": 429,
|
|
473
|
+
"params": [
|
|
474
|
+
"retry_after"
|
|
475
|
+
],
|
|
476
|
+
"remediation": "wait_and_retry",
|
|
477
|
+
"en": "This invitation was emailed recently; you can send it again in {retry_after} seconds"
|
|
478
|
+
},
|
|
463
479
|
{
|
|
464
480
|
"code": "error.429.rate_limit",
|
|
465
481
|
"status": 429,
|
|
@@ -56,6 +56,7 @@ export const workspacesErrorBundleRu: Record<WorkspacesErrorCode, string> = {
|
|
|
56
56
|
"error.403.role_exceeds_inviter_rank": "Вы не можете выдать роль, которая превышает вашу собственную ({role})",
|
|
57
57
|
"error.403.verification_enrollment_required": "Требуется регистрация фактора подтверждения.",
|
|
58
58
|
"error.403.verification_required": "Требуется дополнительная проверка",
|
|
59
|
+
"error.403.workspace_creation_closed": "На этом сервере вам недоступно создание рабочих пространств",
|
|
59
60
|
"error.404.ad_not_found": "Объявление не найдено",
|
|
60
61
|
"error.404.invitation_not_found": "Приглашение не найдено",
|
|
61
62
|
"error.404.member_not_found": "Участник не найден в этом рабочем пространстве",
|
|
@@ -73,6 +74,7 @@ export const workspacesErrorBundleRu: Record<WorkspacesErrorCode, string> = {
|
|
|
73
74
|
"error.422.unprocessable_entity": "Невозможно обработать данные запроса",
|
|
74
75
|
"error.423.locked": "Ресурс заблокирован",
|
|
75
76
|
"error.423.verification_locked": "Слишком много неудачных попыток — подтверждение заблокировано",
|
|
77
|
+
"error.429.invitation_resend_cooldown": "Это приглашение недавно отправлялось; отправить снова можно через {retry_after} с",
|
|
76
78
|
"error.429.rate_limit": "Слишком много попыток. Повторите попытку через {retry_after_minutes} мин.",
|
|
77
79
|
"error.429.too_many_requests": "Слишком много запросов. Пожалуйста, повторите попытку позже.",
|
|
78
80
|
"error.500.internal": "Что-то пошло не так",
|
package/src/index.ts
CHANGED
|
@@ -69,6 +69,8 @@ export {
|
|
|
69
69
|
useWorkspaces,
|
|
70
70
|
useWorkspace,
|
|
71
71
|
useMembers,
|
|
72
|
+
useAudit,
|
|
73
|
+
useCanCreateWorkspace,
|
|
72
74
|
useInvitations,
|
|
73
75
|
useInfiniteInvitations,
|
|
74
76
|
useRoles,
|
|
@@ -126,6 +128,9 @@ export type {
|
|
|
126
128
|
WorkspaceList as WorkspaceListData,
|
|
127
129
|
WorkspaceCreate,
|
|
128
130
|
WorkspaceUpdate,
|
|
131
|
+
AuditEvent,
|
|
132
|
+
AuditPage,
|
|
133
|
+
AuditParams,
|
|
129
134
|
Member as MemberData,
|
|
130
135
|
MemberPage as MemberListData,
|
|
131
136
|
MembersParams,
|
package/src/model/mutations.ts
CHANGED
|
@@ -125,6 +125,13 @@ export function useInviteMembers(
|
|
|
125
125
|
void queryClient.invalidateQueries({
|
|
126
126
|
queryKey: workspacesQueryKeys.members(workspaceId),
|
|
127
127
|
});
|
|
128
|
+
// The history beside the roster is a record OF this change: an admin
|
|
129
|
+
// who removes someone and looks down expects to see it. The audit key
|
|
130
|
+
// is its own root (queryKeys.ts), so the members invalidation above
|
|
131
|
+
// does not reach it.
|
|
132
|
+
void queryClient.invalidateQueries({
|
|
133
|
+
queryKey: workspacesQueryKeys.audit(workspaceId),
|
|
134
|
+
});
|
|
128
135
|
},
|
|
129
136
|
};
|
|
130
137
|
return useMutation(options);
|
|
@@ -157,6 +164,13 @@ export function useUpdateMemberRole(
|
|
|
157
164
|
void queryClient.invalidateQueries({
|
|
158
165
|
queryKey: workspacesQueryKeys.members(workspaceId),
|
|
159
166
|
});
|
|
167
|
+
// The history beside the roster is a record OF this change: an admin
|
|
168
|
+
// who removes someone and looks down expects to see it. The audit key
|
|
169
|
+
// is its own root (queryKeys.ts), so the members invalidation above
|
|
170
|
+
// does not reach it.
|
|
171
|
+
void queryClient.invalidateQueries({
|
|
172
|
+
queryKey: workspacesQueryKeys.audit(workspaceId),
|
|
173
|
+
});
|
|
160
174
|
},
|
|
161
175
|
};
|
|
162
176
|
return useMutation(options);
|
|
@@ -177,6 +191,13 @@ export function useRemoveMember(
|
|
|
177
191
|
void queryClient.invalidateQueries({
|
|
178
192
|
queryKey: workspacesQueryKeys.members(workspaceId),
|
|
179
193
|
});
|
|
194
|
+
// The history beside the roster is a record OF this change: an admin
|
|
195
|
+
// who removes someone and looks down expects to see it. The audit key
|
|
196
|
+
// is its own root (queryKeys.ts), so the members invalidation above
|
|
197
|
+
// does not reach it.
|
|
198
|
+
void queryClient.invalidateQueries({
|
|
199
|
+
queryKey: workspacesQueryKeys.audit(workspaceId),
|
|
200
|
+
});
|
|
180
201
|
},
|
|
181
202
|
};
|
|
182
203
|
return useMutation(options);
|