@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/src/index.ts
CHANGED
|
@@ -89,6 +89,8 @@ export {
|
|
|
89
89
|
useInviteMembers,
|
|
90
90
|
useUpdateMemberRole,
|
|
91
91
|
useRemoveMember,
|
|
92
|
+
useRenameMember,
|
|
93
|
+
useRenameInvitation,
|
|
92
94
|
useAcceptInvitation,
|
|
93
95
|
useClaimInvitation,
|
|
94
96
|
useDeclineInvitation,
|
|
@@ -99,6 +101,8 @@ export {
|
|
|
99
101
|
} from "./model/mutations.js";
|
|
100
102
|
export type {
|
|
101
103
|
MemberRoleChange,
|
|
104
|
+
MemberRename,
|
|
105
|
+
InvitationRename,
|
|
102
106
|
MemberPasswordResetVars,
|
|
103
107
|
} from "./model/mutations.js";
|
|
104
108
|
|
|
@@ -123,6 +127,8 @@ export type {
|
|
|
123
127
|
InvitationClaim,
|
|
124
128
|
MemberPasswordReset,
|
|
125
129
|
MemberPasswordResetResult,
|
|
130
|
+
DisplayNameUpdate,
|
|
131
|
+
DisplayNameResult,
|
|
126
132
|
ProvisionedUserPolicy,
|
|
127
133
|
WorkspaceSecuritySettings,
|
|
128
134
|
RoleInfo,
|
package/src/model/mutations.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from "@tanstack/react-query";
|
|
6
6
|
import type { StapelApiError } from "@stapel/core";
|
|
7
7
|
import type {
|
|
8
|
+
DisplayNameResult,
|
|
8
9
|
Invitation,
|
|
9
10
|
Member,
|
|
10
11
|
MemberInvite,
|
|
@@ -179,6 +180,132 @@ export function useRemoveMember(
|
|
|
179
180
|
return useMutation(options);
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
/** The variable for {@link useRenameMember}: whose name, and the new one.
|
|
184
|
+
* `displayName` is nullable because clearing IS a supported outcome — blank,
|
|
185
|
+
* whitespace-only and `null` all mean "no name for this person" to the
|
|
186
|
+
* backend, which then answers with an empty string. */
|
|
187
|
+
export interface MemberRename {
|
|
188
|
+
readonly userId: string;
|
|
189
|
+
readonly displayName: string | null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Correct a co-member's display name (PATCH /{id}/members/{userId}/name,
|
|
194
|
+
* stapel-workspaces ≥0.19.0) — the roster-side fix an owner/admin applies
|
|
195
|
+
* without waiting for the person themselves.
|
|
196
|
+
*
|
|
197
|
+
* **Capability `members.role.change`**, not `members.invite`: ask
|
|
198
|
+
* {@link "./queries.js".useCapabilityGate}(workspaceId,
|
|
199
|
+
* "members.role.change") before offering the affordance. It is `standard`, so
|
|
200
|
+
* no step-up is demanded (contrast {@link useResetMemberPassword}). Anonymous
|
|
201
|
+
* callers are denied outright, and only an owner may rename an owner.
|
|
202
|
+
*
|
|
203
|
+
* **What the write moves, and hence what is invalidated.** The backend writes
|
|
204
|
+
* the CANONICAL name — stapel-profiles' `Profile.display_name` through the
|
|
205
|
+
* in-process profiles seam — not the membership's local
|
|
206
|
+
* `display_name_hint`, which goes dark the moment a real profile exists. So
|
|
207
|
+
* the stale set is not "the page this row was on": it is EVERY cached roster,
|
|
208
|
+
* in every workspace, because `MemberResponse.display_name` is a live lookup
|
|
209
|
+
* of that one canonical value. Hence
|
|
210
|
+
* {@link "./queryKeys.js".workspacesQueryKeys.membersAll}, the workspace-less
|
|
211
|
+
* prefix — invalidating `members(workspaceId)` would leave the same person
|
|
212
|
+
* showing their old name on every other roster the session has open. A
|
|
213
|
+
* rename can also move a row out of an active `search` filter, which the
|
|
214
|
+
* prefix covers too (every page, every filter).
|
|
215
|
+
*
|
|
216
|
+
* A host that ALSO renders `@stapel/profiles-react` data for this person owns
|
|
217
|
+
* the other half: this pair never reaches into another pair's query namespace
|
|
218
|
+
* (the same seam discipline as `InviteAcceptFlow`'s basic-data slot), so
|
|
219
|
+
* invalidate `profilesQueryKeys.profile(userId)` yourself there.
|
|
220
|
+
*
|
|
221
|
+
* **Errors** arrive in the single dialect, keyed: the four display-name rules
|
|
222
|
+
* borrowed verbatim from stapel-profiles
|
|
223
|
+
* (`error.400.display_name_too_short` / `_forbidden_chars` /
|
|
224
|
+
* `_invisible_chars` / `_emoji`), over-length as the fleet-standard
|
|
225
|
+
* `error.400.field.max_length` with `{field, max_length}` (no bespoke code),
|
|
226
|
+
* and `error.503.profiles_unavailable` where stapel-profiles does not run in
|
|
227
|
+
* the deployment's process — an honest refusal rather than a 200 over a write
|
|
228
|
+
* that did not happen. Render them with `explainWorkspacesError` / `t(code,
|
|
229
|
+
* params)` like every other key.
|
|
230
|
+
*/
|
|
231
|
+
export function useRenameMember(
|
|
232
|
+
workspaceId: string
|
|
233
|
+
): UseMutationResult<DisplayNameResult, StapelApiError, MemberRename> {
|
|
234
|
+
const api = useWorkspacesApi();
|
|
235
|
+
const queryClient = useQueryClient();
|
|
236
|
+
const options: UseMutationOptions<
|
|
237
|
+
DisplayNameResult,
|
|
238
|
+
StapelApiError,
|
|
239
|
+
MemberRename
|
|
240
|
+
> = {
|
|
241
|
+
mutationFn: ({ userId, displayName }) =>
|
|
242
|
+
api.renameMember(workspaceId, userId, { display_name: displayName }),
|
|
243
|
+
onSuccess: () => {
|
|
244
|
+
void queryClient.invalidateQueries({
|
|
245
|
+
queryKey: workspacesQueryKeys.membersAll(),
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
return useMutation(options);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** The variable for {@link useRenameInvitation}: which pending invitation, and
|
|
253
|
+
* the corrected name hint (nullable for the same reason as
|
|
254
|
+
* {@link MemberRename}). */
|
|
255
|
+
export interface InvitationRename {
|
|
256
|
+
readonly invitationId: string;
|
|
257
|
+
readonly displayName: string | null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Fix a pending invitation's name hint (PATCH
|
|
262
|
+
* /{id}/invitations/{invitationId}/name, stapel-workspaces ≥0.19.0) — the
|
|
263
|
+
* same correction as {@link useRenameMember}, one step earlier. The invitee
|
|
264
|
+
* has not accepted, so there is no profile of theirs to write and the name
|
|
265
|
+
* lives on the invitation; `accept_invitation` copies it onto the membership.
|
|
266
|
+
* Before this endpoint the only fix for a typo in an invitee's name was
|
|
267
|
+
* revoke-and-re-invite, which re-mails the person.
|
|
268
|
+
*
|
|
269
|
+
* **The same capability as the member rename** (`members.role.change`, not
|
|
270
|
+
* the invitation surface's `members.invite`) — the hint IS the member's name
|
|
271
|
+
* after acceptance, so splitting them would let a role fix a name that
|
|
272
|
+
* reverts.
|
|
273
|
+
*
|
|
274
|
+
* Only a PENDING invitation is editable; a terminal one refuses with the
|
|
275
|
+
* keyed answers revoke gives (`error.400.invitation_already_used` /
|
|
276
|
+
* `_revoked` / `_declined` / `_expired`), and an accepted invitation's name
|
|
277
|
+
* is the member's name now — use {@link useRenameMember}. Validation is the
|
|
278
|
+
* same stapel-profiles canon, so the same four display-name keys surface
|
|
279
|
+
* here.
|
|
280
|
+
*
|
|
281
|
+
* Invalidation is deliberately NARROWER than the member rename's: the hint is
|
|
282
|
+
* a workspace-local column on one invitation, not a shared canonical name, so
|
|
283
|
+
* only this workspace's invitation lists (every page and the infinite walk —
|
|
284
|
+
* the bare 3-tuple prefix) can be showing it.
|
|
285
|
+
*/
|
|
286
|
+
export function useRenameInvitation(
|
|
287
|
+
workspaceId: string
|
|
288
|
+
): UseMutationResult<DisplayNameResult, StapelApiError, InvitationRename> {
|
|
289
|
+
const api = useWorkspacesApi();
|
|
290
|
+
const queryClient = useQueryClient();
|
|
291
|
+
const options: UseMutationOptions<
|
|
292
|
+
DisplayNameResult,
|
|
293
|
+
StapelApiError,
|
|
294
|
+
InvitationRename
|
|
295
|
+
> = {
|
|
296
|
+
mutationFn: ({ invitationId, displayName }) =>
|
|
297
|
+
api.renameInvitation(workspaceId, invitationId, {
|
|
298
|
+
display_name: displayName,
|
|
299
|
+
}),
|
|
300
|
+
onSuccess: () => {
|
|
301
|
+
void queryClient.invalidateQueries({
|
|
302
|
+
queryKey: workspacesQueryKeys.invitations(workspaceId),
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
return useMutation(options);
|
|
307
|
+
}
|
|
308
|
+
|
|
182
309
|
/**
|
|
183
310
|
* Withdraw a live invitation (POST /{id}/invitations/{invitationId}/revoke,
|
|
184
311
|
* #109). The variable is the invitation id; the reply is the UPDATED row, so
|
package/src/model/queryKeys.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const workspacesQueryKeys: {
|
|
|
14
14
|
list(): readonly ["workspaces", "list"];
|
|
15
15
|
detail(workspaceId: string): readonly ["workspaces", "detail", string];
|
|
16
16
|
members(workspaceId: string): readonly ["workspaces", "members", string];
|
|
17
|
+
membersAll(): readonly ["workspaces", "members"];
|
|
17
18
|
membersPage(
|
|
18
19
|
workspaceId: string,
|
|
19
20
|
params: MembersParams
|
|
@@ -45,6 +46,13 @@ export const workspacesQueryKeys: {
|
|
|
45
46
|
// The bare 3-tuple is a valid prefix of membersPage's 4-tuple, so
|
|
46
47
|
// invalidating `members(workspaceId)` (mutations.ts) drops every page.
|
|
47
48
|
members: (workspaceId) => [ROOT, "members", workspaceId],
|
|
49
|
+
// EVERY workspace's roster, not one. Not a read key — nothing fetches it —
|
|
50
|
+
// but the invalidation scope of a write that moves a name the whole fleet
|
|
51
|
+
// shares: `MemberResponse.display_name` is a live lookup in
|
|
52
|
+
// stapel-profiles, so a rename lands on the person's CANONICAL name and
|
|
53
|
+
// every roster that lists them is stale at once, not just the one the
|
|
54
|
+
// admin happened to be looking at.
|
|
55
|
+
membersAll: () => [ROOT, "members"],
|
|
48
56
|
membersPage: (workspaceId, params) => [ROOT, "members", workspaceId, params],
|
|
49
57
|
// Same prefix trick as `members`: the bare 3-tuple invalidates every page
|
|
50
58
|
// (and the infinite list, which keys on it) after a revoke / resend.
|