@stapel/workspaces-react 0.9.0 → 0.10.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 +51 -0
- package/dist/api/generated/schema.d.ts +173 -17
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/api/types.d.ts +14 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/workspacesApi.d.ts +29 -1
- package/dist/api/workspacesApi.d.ts.map +1 -1
- package/dist/api/workspacesApi.js +2 -0
- package/dist/api/workspacesApi.js.map +1 -1
- package/dist/i18n/generated/errors.gen.d.ts +30 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.gen.js +15 -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 +5 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
- package/dist/index.d.ts +3 -3
- 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 +83 -1
- package/dist/model/mutations.d.ts.map +1 -1
- package/dist/model/mutations.js +93 -0
- package/dist/model/mutations.js.map +1 -1
- package/dist/model/queryKeys.d.ts +1 -0
- package/dist/model/queryKeys.d.ts.map +1 -1
- package/dist/model/queryKeys.js +7 -0
- package/dist/model/queryKeys.js.map +1 -1
- package/llms.txt +7 -3
- package/manifest.json +73 -2
- package/package.json +3 -3
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +173 -17
- package/src/api/types.ts +14 -0
- package/src/api/workspacesApi.ts +53 -0
- package/src/i18n/generated/errors.gen.ts +15 -0
- package/src/i18n/generated/errors.json +35 -0
- package/src/i18n/generated/errors.ru.gen.ts +5 -0
- package/src/index.ts +6 -0
- package/src/model/mutations.ts +127 -0
- package/src/model/queryKeys.ts +8 -0
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.10.0",
|
|
5
5
|
"backend": {
|
|
6
6
|
"module": "stapel-workspaces",
|
|
7
|
-
"contract": ">=0.
|
|
7
|
+
"contract": ">=0.19 <0.20"
|
|
8
8
|
},
|
|
9
9
|
"layers": [
|
|
10
10
|
"api",
|
|
@@ -81,6 +81,13 @@
|
|
|
81
81
|
"request": null,
|
|
82
82
|
"response": "PaginatedInvitationResponseList"
|
|
83
83
|
},
|
|
84
|
+
"workspaces_api_v1_invitations_name_partial_update": {
|
|
85
|
+
"method": "PATCH",
|
|
86
|
+
"path": "/workspaces/api/v1/{workspace_id}/invitations/{invitation_id}/name",
|
|
87
|
+
"tag": "Members",
|
|
88
|
+
"request": "PatchedDisplayNameUpdateRequest",
|
|
89
|
+
"response": "DisplayNameResponse"
|
|
90
|
+
},
|
|
84
91
|
"workspaces_api_v1_invitations_resend_create": {
|
|
85
92
|
"method": "POST",
|
|
86
93
|
"path": "/workspaces/api/v1/{workspace_id}/invitations/{invitation_id}/resend",
|
|
@@ -123,6 +130,13 @@
|
|
|
123
130
|
"request": null,
|
|
124
131
|
"response": "PaginatedMemberResponseList"
|
|
125
132
|
},
|
|
133
|
+
"workspaces_api_v1_members_name_partial_update": {
|
|
134
|
+
"method": "PATCH",
|
|
135
|
+
"path": "/workspaces/api/v1/{workspace_id}/members/{user_id}/name",
|
|
136
|
+
"tag": "Members",
|
|
137
|
+
"request": "PatchedDisplayNameUpdateRequest",
|
|
138
|
+
"response": "DisplayNameResponse"
|
|
139
|
+
},
|
|
126
140
|
"workspaces_api_v1_members_partial_update": {
|
|
127
141
|
"method": "PATCH",
|
|
128
142
|
"path": "/workspaces/api/v1/{workspace_id}/members/{user_id}",
|
|
@@ -288,6 +302,27 @@
|
|
|
288
302
|
]
|
|
289
303
|
]
|
|
290
304
|
},
|
|
305
|
+
"useRenameInvitation": {
|
|
306
|
+
"kind": "mutation",
|
|
307
|
+
"operation": "renameInvitation",
|
|
308
|
+
"invalidates": [
|
|
309
|
+
[
|
|
310
|
+
"workspaces",
|
|
311
|
+
"invitations",
|
|
312
|
+
"{workspaceId}"
|
|
313
|
+
]
|
|
314
|
+
]
|
|
315
|
+
},
|
|
316
|
+
"useRenameMember": {
|
|
317
|
+
"kind": "mutation",
|
|
318
|
+
"operation": "renameMember",
|
|
319
|
+
"invalidates": [
|
|
320
|
+
[
|
|
321
|
+
"workspaces",
|
|
322
|
+
"members"
|
|
323
|
+
]
|
|
324
|
+
]
|
|
325
|
+
},
|
|
291
326
|
"useResendInvitation": {
|
|
292
327
|
"kind": "mutation",
|
|
293
328
|
"operation": "resendInvitation",
|
|
@@ -422,6 +457,26 @@
|
|
|
422
457
|
"params": [],
|
|
423
458
|
"remediation": "retry"
|
|
424
459
|
},
|
|
460
|
+
"error.400.display_name_emoji": {
|
|
461
|
+
"status": 400,
|
|
462
|
+
"params": [],
|
|
463
|
+
"remediation": "fix_input"
|
|
464
|
+
},
|
|
465
|
+
"error.400.display_name_forbidden_chars": {
|
|
466
|
+
"status": 400,
|
|
467
|
+
"params": [],
|
|
468
|
+
"remediation": "fix_input"
|
|
469
|
+
},
|
|
470
|
+
"error.400.display_name_invisible_chars": {
|
|
471
|
+
"status": 400,
|
|
472
|
+
"params": [],
|
|
473
|
+
"remediation": "fix_input"
|
|
474
|
+
},
|
|
475
|
+
"error.400.display_name_too_short": {
|
|
476
|
+
"status": 400,
|
|
477
|
+
"params": [],
|
|
478
|
+
"remediation": "fix_input"
|
|
479
|
+
},
|
|
425
480
|
"error.400.expected_list": {
|
|
426
481
|
"status": 400,
|
|
427
482
|
"params": [],
|
|
@@ -742,6 +797,11 @@
|
|
|
742
797
|
"status": 503,
|
|
743
798
|
"params": [],
|
|
744
799
|
"remediation": "wait_and_retry"
|
|
800
|
+
},
|
|
801
|
+
"error.503.profiles_unavailable": {
|
|
802
|
+
"status": 503,
|
|
803
|
+
"params": [],
|
|
804
|
+
"remediation": "wait_and_retry"
|
|
745
805
|
}
|
|
746
806
|
},
|
|
747
807
|
"events": {
|
|
@@ -852,6 +912,10 @@
|
|
|
852
912
|
"error.400.bad_request",
|
|
853
913
|
"error.400.captcha_invalid",
|
|
854
914
|
"error.400.captcha_required",
|
|
915
|
+
"error.400.display_name_emoji",
|
|
916
|
+
"error.400.display_name_forbidden_chars",
|
|
917
|
+
"error.400.display_name_invisible_chars",
|
|
918
|
+
"error.400.display_name_too_short",
|
|
855
919
|
"error.400.expected_list",
|
|
856
920
|
"error.400.field.blank",
|
|
857
921
|
"error.400.field.does_not_exist",
|
|
@@ -909,6 +973,7 @@
|
|
|
909
973
|
"error.429.too_many_requests",
|
|
910
974
|
"error.500.internal",
|
|
911
975
|
"error.503.auth_unavailable",
|
|
976
|
+
"error.503.profiles_unavailable",
|
|
912
977
|
"workspaces.accept.accept",
|
|
913
978
|
"workspaces.accept.accepted",
|
|
914
979
|
"workspaces.accept.accepting",
|
|
@@ -1018,6 +1083,8 @@
|
|
|
1018
1083
|
"useInviteMembers",
|
|
1019
1084
|
"useMembers",
|
|
1020
1085
|
"useRemoveMember",
|
|
1086
|
+
"useRenameInvitation",
|
|
1087
|
+
"useRenameMember",
|
|
1021
1088
|
"useResendInvitation",
|
|
1022
1089
|
"useResetMemberPassword",
|
|
1023
1090
|
"useRevokeInvitation",
|
|
@@ -1041,6 +1108,8 @@
|
|
|
1041
1108
|
"CapabilityGate",
|
|
1042
1109
|
"CapabilityLevel",
|
|
1043
1110
|
"CreateWorkspacesRuntimeOptions",
|
|
1111
|
+
"DisplayNameResult",
|
|
1112
|
+
"DisplayNameUpdate",
|
|
1044
1113
|
"FlowEndpoint",
|
|
1045
1114
|
"FlowError",
|
|
1046
1115
|
"FlowMachine",
|
|
@@ -1051,6 +1120,7 @@
|
|
|
1051
1120
|
"InvitationClaim",
|
|
1052
1121
|
"InvitationListData",
|
|
1053
1122
|
"InvitationPreview",
|
|
1123
|
+
"InvitationRename",
|
|
1054
1124
|
"InvitationStatusFilter",
|
|
1055
1125
|
"InvitationsParams",
|
|
1056
1126
|
"InviteAcceptFlowBag",
|
|
@@ -1064,6 +1134,7 @@
|
|
|
1064
1134
|
"MemberPasswordReset",
|
|
1065
1135
|
"MemberPasswordResetResult",
|
|
1066
1136
|
"MemberPasswordResetVars",
|
|
1137
|
+
"MemberRename",
|
|
1067
1138
|
"MemberRoleChange",
|
|
1068
1139
|
"MemberRoleUpdate",
|
|
1069
1140
|
"MembersBag",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stapel/workspaces-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
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": {
|
|
@@ -82,9 +82,9 @@
|
|
|
82
82
|
"size-limit": "^11.2.0",
|
|
83
83
|
"typescript": "^5.8.3",
|
|
84
84
|
"vitest": "^3.2.4",
|
|
85
|
-
"@stapel/
|
|
85
|
+
"@stapel/tokens": "^0.5.0",
|
|
86
86
|
"@stapel/showcase": "^0.2.0",
|
|
87
|
-
"@stapel/
|
|
87
|
+
"@stapel/core": "^0.10.0"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=22"
|
|
@@ -126,6 +126,46 @@ export interface paths {
|
|
|
126
126
|
patch?: never;
|
|
127
127
|
trace?: never;
|
|
128
128
|
};
|
|
129
|
+
"/workspaces/api/v1/{workspace_id}/invitations/{invitation_id}/name": {
|
|
130
|
+
parameters: {
|
|
131
|
+
query?: never;
|
|
132
|
+
header?: never;
|
|
133
|
+
path?: never;
|
|
134
|
+
cookie?: never;
|
|
135
|
+
};
|
|
136
|
+
get?: never;
|
|
137
|
+
put?: never;
|
|
138
|
+
post?: never;
|
|
139
|
+
delete?: never;
|
|
140
|
+
options?: never;
|
|
141
|
+
head?: never;
|
|
142
|
+
/**
|
|
143
|
+
* @description ``PATCH <ws>/invitations/<id>/name`` — fix a pending invite's name hint.
|
|
144
|
+
*
|
|
145
|
+
* The same correction as :class:`MemberNameView`, one step earlier: the
|
|
146
|
+
* invitee has not accepted, so there is no profile of theirs to write and
|
|
147
|
+
* the name lives on the invitation as ``display_name_hint`` — the invite
|
|
148
|
+
* modal's "Name" field, which ``accept_invitation`` copies onto the
|
|
149
|
+
* membership at acceptance. Editing it after the fact is why this endpoint
|
|
150
|
+
* exists: before #109's invitation surface the only fix for a typo in an
|
|
151
|
+
* invitee's name was to revoke and re-invite, which re-mails the person.
|
|
152
|
+
*
|
|
153
|
+
* Only a **pending** invitation is editable, with the same keyed refusals
|
|
154
|
+
* revoke gives for each terminal state (``invitation_revoked`` /
|
|
155
|
+
* ``already_used`` / ``declined`` / ``expired``): an accepted invitation's
|
|
156
|
+
* name is the member's name now — use the member endpoint — and a dead
|
|
157
|
+
* invitation is not a thing to relabel. Unknown and cross-workspace ids
|
|
158
|
+
* collapse into one identical 404 (inherited resolution).
|
|
159
|
+
*
|
|
160
|
+
* The value is still held to stapel-profiles' name canon even though the
|
|
161
|
+
* column being written is local: this hint becomes a displayed name, and
|
|
162
|
+
* the two endpoints must not disagree about what a name may contain.
|
|
163
|
+
*
|
|
164
|
+
* **Permissions:** `IsAuthenticated`
|
|
165
|
+
*/
|
|
166
|
+
patch: operations["workspaces_api_v1_invitations_name_partial_update"];
|
|
167
|
+
trace?: never;
|
|
168
|
+
};
|
|
129
169
|
"/workspaces/api/v1/{workspace_id}/invitations/{invitation_id}/resend": {
|
|
130
170
|
parameters: {
|
|
131
171
|
query?: never;
|
|
@@ -261,6 +301,50 @@ export interface paths {
|
|
|
261
301
|
patch: operations["workspaces_api_v1_members_partial_update"];
|
|
262
302
|
trace?: never;
|
|
263
303
|
};
|
|
304
|
+
"/workspaces/api/v1/{workspace_id}/members/{user_id}/name": {
|
|
305
|
+
parameters: {
|
|
306
|
+
query?: never;
|
|
307
|
+
header?: never;
|
|
308
|
+
path?: never;
|
|
309
|
+
cookie?: never;
|
|
310
|
+
};
|
|
311
|
+
get?: never;
|
|
312
|
+
put?: never;
|
|
313
|
+
post?: never;
|
|
314
|
+
delete?: never;
|
|
315
|
+
options?: never;
|
|
316
|
+
head?: never;
|
|
317
|
+
/**
|
|
318
|
+
* @description ``PATCH <ws>/members/<user_id>/name`` — correct a member's display name.
|
|
319
|
+
*
|
|
320
|
+
* Writes the CANONICAL name: stapel-profiles' ``Profile.display_name``,
|
|
321
|
+
* reached through this module's existing in-process profiles seam
|
|
322
|
+
* (``services.set_profile_display_name``), which also publishes
|
|
323
|
+
* ``profile.changed`` so every consumer of that name follows.
|
|
324
|
+
*
|
|
325
|
+
* NOT ``WorkspaceMember.display_name_hint``: the hint is a pre-profile
|
|
326
|
+
* placeholder, copied once at creation and dark from the moment a real
|
|
327
|
+
* profile exists (see its docstring in ``models.py``). Writing it would
|
|
328
|
+
* produce a correction the roster shows and nothing else in the product
|
|
329
|
+
* ever does — including, eventually, the roster.
|
|
330
|
+
*
|
|
331
|
+
* Where stapel-profiles does not run in this process there is nothing to
|
|
332
|
+
* write and no remote operation to call for it (that module publishes no
|
|
333
|
+
* write-somebody-else's-name endpoint and no comm Function), so the answer
|
|
334
|
+
* is an honest ``error.503.profiles_unavailable`` — never a 200 over a
|
|
335
|
+
* write that did not happen.
|
|
336
|
+
*
|
|
337
|
+
* Only an owner may rename an owner — the same hardcoded owner protection
|
|
338
|
+
* that role changes, removals and password resets carry. Renaming is not
|
|
339
|
+
* escalation, but an admin relabelling the owner of the organization on
|
|
340
|
+
* every screen in the product is close enough to the same act to answer
|
|
341
|
+
* the same way.
|
|
342
|
+
*
|
|
343
|
+
* **Permissions:** `IsAuthenticated`
|
|
344
|
+
*/
|
|
345
|
+
patch: operations["workspaces_api_v1_members_name_partial_update"];
|
|
346
|
+
trace?: never;
|
|
347
|
+
};
|
|
264
348
|
"/workspaces/api/v1/{workspace_id}/members/{user_id}/password/reset": {
|
|
265
349
|
parameters: {
|
|
266
350
|
query?: never;
|
|
@@ -606,34 +690,42 @@ export interface paths {
|
|
|
606
690
|
export type webhooks = Record<string, never>;
|
|
607
691
|
export interface components {
|
|
608
692
|
schemas: {
|
|
693
|
+
/** @description What the name is after the write — the stored value, not the request. */
|
|
694
|
+
DisplayNameResponse: {
|
|
695
|
+
/**
|
|
696
|
+
* @description The stored name, trimmed and canon-checked. Empty string when the name was cleared
|
|
697
|
+
* @example Ada Lovelace
|
|
698
|
+
*/
|
|
699
|
+
display_name: string;
|
|
700
|
+
};
|
|
609
701
|
/**
|
|
610
|
-
* @description
|
|
611
|
-
*
|
|
702
|
+
* @description How THIS instance is deployed — what the client needs to know before
|
|
703
|
+
* deciding what to show a person with no workspace.
|
|
612
704
|
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
705
|
+
* The ``landing`` axis (``STREET_LANDING_MODE``) has existed since
|
|
706
|
+
* 2026-08-03 but lived only in backend config, with nothing exposing it —
|
|
707
|
+
* a client had no way to tell a closed deployment from a public cloud one.
|
|
616
708
|
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
709
|
+
* This matters because the screen shown to someone who is no longer (or
|
|
710
|
+
* never was) a workspace member differs by deployment:
|
|
619
711
|
*
|
|
620
|
-
* * ``none`` —
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
* * ``personal`` —
|
|
624
|
-
*
|
|
712
|
+
* * ``none`` — closed deployment: the person has no workspace and none is
|
|
713
|
+
* obtainable. An honest screen says "you're not a member, ask for an
|
|
714
|
+
* invite" and offers no path forward;
|
|
715
|
+
* * ``personal`` — public cloud: they have their own workspace, and the
|
|
716
|
+
* client can route them there.
|
|
625
717
|
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
718
|
+
* Offering "create a workspace" to someone the instance will never let
|
|
719
|
+
* create one is a dead end drawn as a button.
|
|
628
720
|
*/
|
|
629
721
|
InstanceShapeResponse: {
|
|
630
722
|
/**
|
|
631
|
-
* @description
|
|
723
|
+
* @description What a "street" (no-invite) arrival lands on
|
|
632
724
|
* @example personal
|
|
633
725
|
*/
|
|
634
726
|
landing: string;
|
|
635
727
|
/**
|
|
636
|
-
* @description
|
|
728
|
+
* @description Whether self-registration is available
|
|
637
729
|
* @example True
|
|
638
730
|
*/
|
|
639
731
|
registration_open: boolean;
|
|
@@ -754,7 +846,7 @@ export interface components {
|
|
|
754
846
|
/** @description Invite payload. */
|
|
755
847
|
MemberInviteRequest: {
|
|
756
848
|
/**
|
|
757
|
-
* @description Name hint for the invitee (this invite's "
|
|
849
|
+
* @description Name hint for the invitee (this invite's "Name" field), applied to every email in this request. Optional — an invite without one behaves exactly as before. NOT written into stapel-profiles directly (that store is the invitee's own): held on the invitation, copied onto the member at accept time, and shown only until stapel-profiles has a real name for the person
|
|
758
850
|
* @example Ada Lovelace
|
|
759
851
|
*/
|
|
760
852
|
display_name?: string | null;
|
|
@@ -888,6 +980,14 @@ export interface components {
|
|
|
888
980
|
/** @description Number of items in current page */
|
|
889
981
|
count: number;
|
|
890
982
|
};
|
|
983
|
+
/** @description Roster-side name correction (one field, both name-edit endpoints). */
|
|
984
|
+
PatchedDisplayNameUpdateRequest: {
|
|
985
|
+
/**
|
|
986
|
+
* @description The name to show for this person. Held to stapel-profiles' display-name canon (validate_display_name) — this module never adds a second, differently-strict rule. Blank, whitespace-only, null and a missing key all mean the same thing: clear the name
|
|
987
|
+
* @example Ada Lovelace
|
|
988
|
+
*/
|
|
989
|
+
display_name?: string | null;
|
|
990
|
+
};
|
|
891
991
|
/** @description MemberUpdateRequest(role: str) */
|
|
892
992
|
PatchedMemberUpdateRequest: {
|
|
893
993
|
role?: string;
|
|
@@ -1249,6 +1349,34 @@ export interface operations {
|
|
|
1249
1349
|
};
|
|
1250
1350
|
};
|
|
1251
1351
|
};
|
|
1352
|
+
workspaces_api_v1_invitations_name_partial_update: {
|
|
1353
|
+
parameters: {
|
|
1354
|
+
query?: never;
|
|
1355
|
+
header?: never;
|
|
1356
|
+
path: {
|
|
1357
|
+
invitation_id: string;
|
|
1358
|
+
workspace_id: string;
|
|
1359
|
+
};
|
|
1360
|
+
cookie?: never;
|
|
1361
|
+
};
|
|
1362
|
+
requestBody?: {
|
|
1363
|
+
content: {
|
|
1364
|
+
"application/json": components["schemas"]["PatchedDisplayNameUpdateRequest"];
|
|
1365
|
+
"application/x-www-form-urlencoded": components["schemas"]["PatchedDisplayNameUpdateRequest"];
|
|
1366
|
+
"multipart/form-data": components["schemas"]["PatchedDisplayNameUpdateRequest"];
|
|
1367
|
+
};
|
|
1368
|
+
};
|
|
1369
|
+
responses: {
|
|
1370
|
+
200: {
|
|
1371
|
+
headers: {
|
|
1372
|
+
[name: string]: unknown;
|
|
1373
|
+
};
|
|
1374
|
+
content: {
|
|
1375
|
+
"application/json": components["schemas"]["DisplayNameResponse"];
|
|
1376
|
+
};
|
|
1377
|
+
};
|
|
1378
|
+
};
|
|
1379
|
+
};
|
|
1252
1380
|
workspaces_api_v1_invitations_resend_create: {
|
|
1253
1381
|
parameters: {
|
|
1254
1382
|
query?: never;
|
|
@@ -1372,6 +1500,34 @@ export interface operations {
|
|
|
1372
1500
|
};
|
|
1373
1501
|
};
|
|
1374
1502
|
};
|
|
1503
|
+
workspaces_api_v1_members_name_partial_update: {
|
|
1504
|
+
parameters: {
|
|
1505
|
+
query?: never;
|
|
1506
|
+
header?: never;
|
|
1507
|
+
path: {
|
|
1508
|
+
user_id: string;
|
|
1509
|
+
workspace_id: string;
|
|
1510
|
+
};
|
|
1511
|
+
cookie?: never;
|
|
1512
|
+
};
|
|
1513
|
+
requestBody?: {
|
|
1514
|
+
content: {
|
|
1515
|
+
"application/json": components["schemas"]["PatchedDisplayNameUpdateRequest"];
|
|
1516
|
+
"application/x-www-form-urlencoded": components["schemas"]["PatchedDisplayNameUpdateRequest"];
|
|
1517
|
+
"multipart/form-data": components["schemas"]["PatchedDisplayNameUpdateRequest"];
|
|
1518
|
+
};
|
|
1519
|
+
};
|
|
1520
|
+
responses: {
|
|
1521
|
+
200: {
|
|
1522
|
+
headers: {
|
|
1523
|
+
[name: string]: unknown;
|
|
1524
|
+
};
|
|
1525
|
+
content: {
|
|
1526
|
+
"application/json": components["schemas"]["DisplayNameResponse"];
|
|
1527
|
+
};
|
|
1528
|
+
};
|
|
1529
|
+
};
|
|
1530
|
+
};
|
|
1375
1531
|
workspaces_api_v1_members_password_reset_create: {
|
|
1376
1532
|
parameters: {
|
|
1377
1533
|
query?: never;
|
package/src/api/types.ts
CHANGED
|
@@ -53,6 +53,20 @@ export type MemberPasswordReset = Schemas["MemberPasswordResetRequest"];
|
|
|
53
53
|
* — see {@link "../model/mutations.js".useResetMemberPassword}, which keeps it
|
|
54
54
|
* out of the query cache. */
|
|
55
55
|
export type MemberPasswordResetResult = Schemas["MemberPasswordResetResponse"];
|
|
56
|
+
/**
|
|
57
|
+
* PATCH /{id}/members/{userId}/name and PATCH
|
|
58
|
+
* /{id}/invitations/{invitationId}/name request body — the ONE field both
|
|
59
|
+
* name-edit endpoints take (stapel-workspaces ≥0.19.0). Blank,
|
|
60
|
+
* whitespace-only, `null` and a missing key all mean the same thing to the
|
|
61
|
+
* backend: clear the name. The value is held to stapel-profiles'
|
|
62
|
+
* `validate_display_name` canon (35-char ceiling as the serializer's
|
|
63
|
+
* `max_length`) — this module declares no second, differently-strict rule.
|
|
64
|
+
*/
|
|
65
|
+
export type DisplayNameUpdate = Schemas["PatchedDisplayNameUpdateRequest"];
|
|
66
|
+
/** PATCH …/name 200 body — what the name IS after the write (the stored,
|
|
67
|
+
* trimmed, canon-checked value; empty string when the name was cleared), not
|
|
68
|
+
* an echo of the request. */
|
|
69
|
+
export type DisplayNameResult = Schemas["DisplayNameResponse"];
|
|
56
70
|
/** POST /invitations/accept request body — the token from the email link. */
|
|
57
71
|
export type InvitationAccept = Schemas["InvitationAcceptRequest"];
|
|
58
72
|
/** GET /invitations/{token} 200 body — the public (AllowAny) preview the
|
package/src/api/workspacesApi.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { StapelClient, StapelRequestOptions } from "@stapel/core";
|
|
2
2
|
import type {
|
|
3
|
+
DisplayNameResult,
|
|
4
|
+
DisplayNameUpdate,
|
|
3
5
|
Invitation,
|
|
4
6
|
InvitationPage,
|
|
5
7
|
InvitationsParams,
|
|
@@ -93,6 +95,24 @@ export interface WorkspacesApi {
|
|
|
93
95
|
/** Remove a member from a workspace (admin+; the last owner is protected). */
|
|
94
96
|
removeMember(workspaceId: string, userId: string): Promise<void>;
|
|
95
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Correct a member's display name (stapel-workspaces ≥0.19.0, capability
|
|
100
|
+
* `members.role.change`). Writes the CANONICAL name — stapel-profiles'
|
|
101
|
+
* `Profile.display_name` through this module's in-process profiles seam,
|
|
102
|
+
* which also publishes `profile.changed` — NOT the membership's
|
|
103
|
+
* `display_name_hint`, which is a pre-profile placeholder that goes dark the
|
|
104
|
+
* moment a real profile exists.
|
|
105
|
+
*
|
|
106
|
+
* Only an owner may rename an owner. Where stapel-profiles does not run in
|
|
107
|
+
* the deployment's process the answer is `error.503.profiles_unavailable`,
|
|
108
|
+
* never a 200 over a write that did not happen.
|
|
109
|
+
*/
|
|
110
|
+
renameMember(
|
|
111
|
+
workspaceId: string,
|
|
112
|
+
userId: string,
|
|
113
|
+
body: DisplayNameUpdate
|
|
114
|
+
): Promise<DisplayNameResult>;
|
|
115
|
+
|
|
96
116
|
/**
|
|
97
117
|
* Reset a member's password on the organization's order (stapel-workspaces
|
|
98
118
|
* ≥0.14.0, #110). Capability `members.password.reset`, declared **high** —
|
|
@@ -141,6 +161,25 @@ export interface WorkspacesApi {
|
|
|
141
161
|
workspaceId: string,
|
|
142
162
|
invitationId: string
|
|
143
163
|
): Promise<Invitation>;
|
|
164
|
+
/**
|
|
165
|
+
* Fix a still-PENDING invitation's name hint (stapel-workspaces ≥0.19.0,
|
|
166
|
+
* capability `members.role.change` — the same one as {@link renameMember},
|
|
167
|
+
* because this hint IS the member's name after acceptance and splitting the
|
|
168
|
+
* two would let a role fix a name that reverts).
|
|
169
|
+
*
|
|
170
|
+
* The same correction as {@link renameMember}, one step earlier: the invitee
|
|
171
|
+
* has not accepted, so there is no profile of theirs to write and the name
|
|
172
|
+
* lives on the invitation. Before it, the only fix for a typo in an
|
|
173
|
+
* invitee's name was revoke-and-re-invite, which re-mails the person. A
|
|
174
|
+
* terminal invitation refuses with the keyed answers revoke gives
|
|
175
|
+
* (`error.400.invitation_already_used` / `_revoked` / `_declined` /
|
|
176
|
+
* `_expired`).
|
|
177
|
+
*/
|
|
178
|
+
renameInvitation(
|
|
179
|
+
workspaceId: string,
|
|
180
|
+
invitationId: string,
|
|
181
|
+
body: DisplayNameUpdate
|
|
182
|
+
): Promise<DisplayNameResult>;
|
|
144
183
|
|
|
145
184
|
/** Accept an invitation by its token — returns the caller's new membership. */
|
|
146
185
|
acceptInvitation(body: InvitationAccept): Promise<Member>;
|
|
@@ -230,6 +269,20 @@ export function createWorkspacesApi(client: StapelClient): WorkspacesApi {
|
|
|
230
269
|
mutating()
|
|
231
270
|
),
|
|
232
271
|
|
|
272
|
+
renameMember: (workspaceId, userId, body) =>
|
|
273
|
+
client.patch(
|
|
274
|
+
`/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(userId)}/name`,
|
|
275
|
+
body satisfies DisplayNameUpdate,
|
|
276
|
+
mutating()
|
|
277
|
+
),
|
|
278
|
+
|
|
279
|
+
renameInvitation: (workspaceId, invitationId, body) =>
|
|
280
|
+
client.patch(
|
|
281
|
+
`/${encodeURIComponent(workspaceId)}/invitations/${encodeURIComponent(invitationId)}/name`,
|
|
282
|
+
body satisfies DisplayNameUpdate,
|
|
283
|
+
mutating()
|
|
284
|
+
),
|
|
285
|
+
|
|
233
286
|
resetMemberPassword: (workspaceId, userId, body) =>
|
|
234
287
|
client.post(
|
|
235
288
|
`/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(userId)}/password/reset`,
|
|
@@ -39,6 +39,10 @@ export const WORKSPACES_ERRORS = {
|
|
|
39
39
|
"error.400.bad_request": { status: 400, params: [], remediation: "fix_input", en: "Bad request" },
|
|
40
40
|
"error.400.captcha_invalid": { status: 400, params: [], remediation: "retry", en: "Captcha verification failed. Please try again." },
|
|
41
41
|
"error.400.captcha_required": { status: 400, params: [], remediation: "retry", en: "Captcha token is required." },
|
|
42
|
+
"error.400.display_name_emoji": { status: 400, params: [], remediation: "fix_input", en: "Display name cannot contain emoji" },
|
|
43
|
+
"error.400.display_name_forbidden_chars": { status: 400, params: [], remediation: "fix_input", en: "Display name contains forbidden characters" },
|
|
44
|
+
"error.400.display_name_invisible_chars": { status: 400, params: [], remediation: "fix_input", en: "Display name contains invisible characters" },
|
|
45
|
+
"error.400.display_name_too_short": { status: 400, params: [], remediation: "fix_input", en: "Display name must be at least 2 characters" },
|
|
42
46
|
"error.400.expected_list": { status: 400, params: [], remediation: "fix_input", en: "Expected a list of items" },
|
|
43
47
|
"error.400.field.blank": { status: 400, params: ["field"], remediation: "fix_input", en: "{field} may not be blank" },
|
|
44
48
|
"error.400.field.does_not_exist": { status: 400, params: ["field"], remediation: "fix_input", en: "{field} does not exist" },
|
|
@@ -96,6 +100,7 @@ export const WORKSPACES_ERRORS = {
|
|
|
96
100
|
"error.429.too_many_requests": { status: 429, params: [], remediation: "wait_and_retry", en: "Too many requests. Please try again later." },
|
|
97
101
|
"error.500.internal": { status: 500, params: [], remediation: "contact_support", en: "Something went wrong" },
|
|
98
102
|
"error.503.auth_unavailable": { status: 503, params: [], remediation: "wait_and_retry", en: "The authentication service is unavailable; try again later" },
|
|
103
|
+
"error.503.profiles_unavailable": { status: 503, params: [], remediation: "wait_and_retry", en: "The profiles service is unavailable; try again later" },
|
|
99
104
|
} as const;
|
|
100
105
|
|
|
101
106
|
export type WorkspacesErrorCode = keyof typeof WORKSPACES_ERRORS;
|
|
@@ -106,6 +111,10 @@ export const WORKSPACES_ERROR_CODES: readonly WorkspacesErrorCode[] = [
|
|
|
106
111
|
"error.400.bad_request",
|
|
107
112
|
"error.400.captcha_invalid",
|
|
108
113
|
"error.400.captcha_required",
|
|
114
|
+
"error.400.display_name_emoji",
|
|
115
|
+
"error.400.display_name_forbidden_chars",
|
|
116
|
+
"error.400.display_name_invisible_chars",
|
|
117
|
+
"error.400.display_name_too_short",
|
|
109
118
|
"error.400.expected_list",
|
|
110
119
|
"error.400.field.blank",
|
|
111
120
|
"error.400.field.does_not_exist",
|
|
@@ -163,6 +172,7 @@ export const WORKSPACES_ERROR_CODES: readonly WorkspacesErrorCode[] = [
|
|
|
163
172
|
"error.429.too_many_requests",
|
|
164
173
|
"error.500.internal",
|
|
165
174
|
"error.503.auth_unavailable",
|
|
175
|
+
"error.503.profiles_unavailable",
|
|
166
176
|
];
|
|
167
177
|
|
|
168
178
|
/**
|
|
@@ -175,6 +185,10 @@ export const workspacesErrorBundleEn: Record<WorkspacesErrorCode, string> = {
|
|
|
175
185
|
"error.400.bad_request": "Bad request",
|
|
176
186
|
"error.400.captcha_invalid": "Captcha verification failed. Please try again.",
|
|
177
187
|
"error.400.captcha_required": "Captcha token is required.",
|
|
188
|
+
"error.400.display_name_emoji": "Display name cannot contain emoji",
|
|
189
|
+
"error.400.display_name_forbidden_chars": "Display name contains forbidden characters",
|
|
190
|
+
"error.400.display_name_invisible_chars": "Display name contains invisible characters",
|
|
191
|
+
"error.400.display_name_too_short": "Display name must be at least 2 characters",
|
|
178
192
|
"error.400.expected_list": "Expected a list of items",
|
|
179
193
|
"error.400.field.blank": "{field} may not be blank",
|
|
180
194
|
"error.400.field.does_not_exist": "{field} does not exist",
|
|
@@ -232,4 +246,5 @@ export const workspacesErrorBundleEn: Record<WorkspacesErrorCode, string> = {
|
|
|
232
246
|
"error.429.too_many_requests": "Too many requests. Please try again later.",
|
|
233
247
|
"error.500.internal": "Something went wrong",
|
|
234
248
|
"error.503.auth_unavailable": "The authentication service is unavailable; try again later",
|
|
249
|
+
"error.503.profiles_unavailable": "The profiles service is unavailable; try again later",
|
|
235
250
|
};
|
|
@@ -27,6 +27,34 @@
|
|
|
27
27
|
"remediation": "retry",
|
|
28
28
|
"en": "Captcha token is required."
|
|
29
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"code": "error.400.display_name_emoji",
|
|
32
|
+
"status": 400,
|
|
33
|
+
"params": [],
|
|
34
|
+
"remediation": "fix_input",
|
|
35
|
+
"en": "Display name cannot contain emoji"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"code": "error.400.display_name_forbidden_chars",
|
|
39
|
+
"status": 400,
|
|
40
|
+
"params": [],
|
|
41
|
+
"remediation": "fix_input",
|
|
42
|
+
"en": "Display name contains forbidden characters"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"code": "error.400.display_name_invisible_chars",
|
|
46
|
+
"status": 400,
|
|
47
|
+
"params": [],
|
|
48
|
+
"remediation": "fix_input",
|
|
49
|
+
"en": "Display name contains invisible characters"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"code": "error.400.display_name_too_short",
|
|
53
|
+
"status": 400,
|
|
54
|
+
"params": [],
|
|
55
|
+
"remediation": "fix_input",
|
|
56
|
+
"en": "Display name must be at least 2 characters"
|
|
57
|
+
},
|
|
30
58
|
{
|
|
31
59
|
"code": "error.400.expected_list",
|
|
32
60
|
"status": 400,
|
|
@@ -461,5 +489,12 @@
|
|
|
461
489
|
"params": [],
|
|
462
490
|
"remediation": "wait_and_retry",
|
|
463
491
|
"en": "The authentication service is unavailable; try again later"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"code": "error.503.profiles_unavailable",
|
|
495
|
+
"status": 503,
|
|
496
|
+
"params": [],
|
|
497
|
+
"remediation": "wait_and_retry",
|
|
498
|
+
"en": "The profiles service is unavailable; try again later"
|
|
464
499
|
}
|
|
465
500
|
]
|
|
@@ -16,6 +16,10 @@ export const workspacesErrorBundleRu: Record<WorkspacesErrorCode, string> = {
|
|
|
16
16
|
"error.400.bad_request": "Некорректный запрос",
|
|
17
17
|
"error.400.captcha_invalid": "Проверка капчи не пройдена. Пожалуйста, попробуйте ещё раз.",
|
|
18
18
|
"error.400.captcha_required": "Требуется токен капчи.",
|
|
19
|
+
"error.400.display_name_emoji": "Отображаемое имя не может содержать эмодзи",
|
|
20
|
+
"error.400.display_name_forbidden_chars": "Отображаемое имя содержит запрещённые символы",
|
|
21
|
+
"error.400.display_name_invisible_chars": "Отображаемое имя содержит невидимые символы",
|
|
22
|
+
"error.400.display_name_too_short": "Отображаемое имя должно содержать не менее 2 символов",
|
|
19
23
|
"error.400.expected_list": "Ожидался список элементов",
|
|
20
24
|
"error.400.field.blank": "Поле «{field}» не может быть пустым",
|
|
21
25
|
"error.400.field.does_not_exist": "«{field}» не существует",
|
|
@@ -73,4 +77,5 @@ export const workspacesErrorBundleRu: Record<WorkspacesErrorCode, string> = {
|
|
|
73
77
|
"error.429.too_many_requests": "Слишком много запросов. Пожалуйста, повторите попытку позже.",
|
|
74
78
|
"error.500.internal": "Что-то пошло не так",
|
|
75
79
|
"error.503.auth_unavailable": "Сервис аутентификации недоступен; повторите попытку позже",
|
|
80
|
+
"error.503.profiles_unavailable": "Сервис профилей недоступен; повторите попытку позже",
|
|
76
81
|
};
|