@shipfox/client-workspace-settings 5.0.0 → 6.0.2
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +53 -0
- package/dist/components/members/form-errors.d.ts +1 -0
- package/dist/components/members/form-errors.d.ts.map +1 -1
- package/dist/components/members/form-errors.js +26 -13
- package/dist/components/members/form-errors.js.map +1 -1
- package/dist/components/members/workspace-members-section.d.ts.map +1 -1
- package/dist/components/members/workspace-members-section.js +31 -28
- package/dist/components/members/workspace-members-section.js.map +1 -1
- package/dist/core/membership.d.ts +41 -0
- package/dist/core/membership.d.ts.map +1 -0
- package/dist/core/membership.js +16 -0
- package/dist/core/membership.js.map +1 -0
- package/dist/feature.d.ts +15 -2
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +20 -18
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/create-invitation.d.ts +4 -2
- package/dist/hooks/api/create-invitation.d.ts.map +1 -1
- package/dist/hooks/api/create-invitation.js +6 -3
- package/dist/hooks/api/create-invitation.js.map +1 -1
- package/dist/hooks/api/invitation-mapper.d.ts +2 -2
- package/dist/hooks/api/invitation-mapper.d.ts.map +1 -1
- package/dist/hooks/api/invitation-mapper.js.map +1 -1
- package/dist/hooks/api/list-invitations.d.ts +5 -5
- package/dist/hooks/api/list-invitations.d.ts.map +1 -1
- package/dist/hooks/api/list-invitations.js.map +1 -1
- package/dist/hooks/api/list-members.d.ts +10 -11
- package/dist/hooks/api/list-members.d.ts.map +1 -1
- package/dist/hooks/api/list-members.js +11 -5
- package/dist/hooks/api/list-members.js.map +1 -1
- package/dist/hooks/api/membership-mapper.d.ts +4 -0
- package/dist/hooks/api/membership-mapper.d.ts.map +1 -0
- package/dist/hooks/api/membership-mapper.js +14 -0
- package/dist/hooks/api/membership-mapper.js.map +1 -0
- package/dist/hooks/api/remove-member.d.ts +2 -1
- package/dist/hooks/api/remove-member.d.ts.map +1 -1
- package/dist/hooks/api/remove-member.js +6 -8
- package/dist/hooks/api/remove-member.js.map +1 -1
- package/dist/hooks/api/revoke-invitation.d.ts +2 -1
- package/dist/hooks/api/revoke-invitation.d.ts.map +1 -1
- package/dist/hooks/api/revoke-invitation.js +4 -4
- package/dist/hooks/api/revoke-invitation.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -11
- package/src/components/members/form-errors.test.ts +57 -4
- package/src/components/members/form-errors.ts +21 -10
- package/src/components/members/workspace-members-section.tsx +38 -35
- package/src/core/membership.test.ts +69 -0
- package/src/core/membership.ts +64 -0
- package/src/feature.ts +27 -20
- package/src/hooks/api/create-invitation.ts +8 -4
- package/src/hooks/api/invitation-mapper.ts +2 -2
- package/src/hooks/api/list-invitations.ts +2 -2
- package/src/hooks/api/list-members.ts +17 -7
- package/src/hooks/api/membership-mapper.test.ts +24 -0
- package/src/hooks/api/membership-mapper.ts +15 -0
- package/src/hooks/api/remove-member.ts +11 -8
- package/src/hooks/api/revoke-invitation.ts +11 -6
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/invitation.d.ts +0 -15
- package/dist/core/invitation.d.ts.map +0 -1
- package/dist/core/invitation.js +0 -3
- package/dist/core/invitation.js.map +0 -1
- package/src/core/invitation.ts +0 -15
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
2
|
+
Successfully compiled: 16 files with swc (276.33ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @shipfox/client-workspace-settings
|
|
2
2
|
|
|
3
|
+
## 6.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [4b85404]
|
|
8
|
+
- Updated dependencies [102c5f4]
|
|
9
|
+
- @shipfox/api-workspaces-dto@9.0.2
|
|
10
|
+
- @shipfox/react-ui@0.3.7
|
|
11
|
+
- @shipfox/client-ui@6.0.2
|
|
12
|
+
- @shipfox/client-shell@6.0.2
|
|
13
|
+
- @shipfox/client-auth@6.0.2
|
|
14
|
+
|
|
15
|
+
## 6.0.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 475ce59: Republishes all public packages after restoring release authorization.
|
|
20
|
+
- Updated dependencies [475ce59]
|
|
21
|
+
- Updated dependencies [3f8f1cb]
|
|
22
|
+
- @shipfox/api-workspaces-dto@9.0.1
|
|
23
|
+
- @shipfox/client-api@6.0.1
|
|
24
|
+
- @shipfox/client-auth@6.0.1
|
|
25
|
+
- @shipfox/client-shell@6.0.1
|
|
26
|
+
- @shipfox/client-ui@6.0.1
|
|
27
|
+
- @shipfox/react-ui@0.3.6
|
|
28
|
+
|
|
29
|
+
## 6.0.0
|
|
30
|
+
|
|
31
|
+
### Minor Changes
|
|
32
|
+
|
|
33
|
+
- 401b583: Exposes typed feature-owned navigation and settings contributions and enforces coordinator-owned client composition.
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- 01f1c88: Converges workspace membership settings on checked domain models and membership policies.
|
|
38
|
+
- d784a07: Enforces checked client API responses and removes stale transport compatibility helpers.
|
|
39
|
+
- Updated dependencies [401b583]
|
|
40
|
+
- Updated dependencies [d784a07]
|
|
41
|
+
- Updated dependencies [891e469]
|
|
42
|
+
- Updated dependencies [82eda45]
|
|
43
|
+
- Updated dependencies [9c9d266]
|
|
44
|
+
- Updated dependencies [cd90c19]
|
|
45
|
+
- Updated dependencies [24be269]
|
|
46
|
+
- Updated dependencies [c56c124]
|
|
47
|
+
- Updated dependencies [4a6d124]
|
|
48
|
+
- Updated dependencies [c02ac42]
|
|
49
|
+
- @shipfox/client-shell@6.0.0
|
|
50
|
+
- @shipfox/client-auth@6.0.0
|
|
51
|
+
- @shipfox/api-workspaces-dto@9.0.0
|
|
52
|
+
- @shipfox/client-api@6.0.0
|
|
53
|
+
- @shipfox/client-ui@6.0.0
|
|
54
|
+
- @shipfox/react-ui@0.3.5
|
|
55
|
+
|
|
3
56
|
## 5.0.0
|
|
4
57
|
|
|
5
58
|
### Patch Changes
|
|
@@ -8,4 +8,5 @@ export type InvitationFormErrorMapping = {
|
|
|
8
8
|
message: string;
|
|
9
9
|
};
|
|
10
10
|
export declare function invitationErrorToFormError(error: unknown): InvitationFormErrorMapping;
|
|
11
|
+
export declare function memberRemovalErrorMessage(error: unknown): string;
|
|
11
12
|
//# sourceMappingURL=form-errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-errors.d.ts","sourceRoot":"","sources":["../../../src/components/members/form-errors.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC;AAEtC,MAAM,MAAM,0BAA0B,GAClC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GACxD;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAC;AAEpC,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,
|
|
1
|
+
{"version":3,"file":"form-errors.d.ts","sourceRoot":"","sources":["../../../src/components/members/form-errors.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC;AAEtC,MAAM,MAAM,0BAA0B,GAClC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GACxD;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAC;AAEpC,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAgBrF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQhE"}
|
|
@@ -1,28 +1,41 @@
|
|
|
1
1
|
import { ApiError } from '@shipfox/client-api';
|
|
2
2
|
export function invitationErrorToFormError(error) {
|
|
3
|
-
if (error instanceof ApiError && error.code === 'open-invitation-exists') {
|
|
4
|
-
return {
|
|
5
|
-
kind: 'field',
|
|
6
|
-
field: 'email',
|
|
7
|
-
message: 'An open invitation already exists for this email.'
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
3
|
if (error instanceof ApiError) {
|
|
4
|
+
if (error.code === 'open-invitation-exists') {
|
|
5
|
+
return {
|
|
6
|
+
kind: 'field',
|
|
7
|
+
field: 'email',
|
|
8
|
+
message: 'An open invitation already exists for this email.'
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
if (error.code === 'rate-limited') {
|
|
12
|
+
return {
|
|
13
|
+
kind: 'form',
|
|
14
|
+
message: 'Too many invitations were sent. Try again shortly.'
|
|
15
|
+
};
|
|
16
|
+
}
|
|
11
17
|
return {
|
|
12
18
|
kind: 'form',
|
|
13
19
|
message: error.message
|
|
14
20
|
};
|
|
15
21
|
}
|
|
16
|
-
if (error instanceof Error) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
+
if (error instanceof Error) return {
|
|
23
|
+
kind: 'form',
|
|
24
|
+
message: error.message
|
|
25
|
+
};
|
|
22
26
|
return {
|
|
23
27
|
kind: 'form',
|
|
24
28
|
message: 'Could not send invitation.'
|
|
25
29
|
};
|
|
26
30
|
}
|
|
31
|
+
export function memberRemovalErrorMessage(error) {
|
|
32
|
+
if (error instanceof ApiError) {
|
|
33
|
+
if (error.code === 'self-removal-not-allowed') return "You can't remove yourself.";
|
|
34
|
+
if (error.code === 'last-member') return 'Cannot remove the last member.';
|
|
35
|
+
return error.message;
|
|
36
|
+
}
|
|
37
|
+
if (error instanceof Error) return error.message;
|
|
38
|
+
return 'Could not remove member.';
|
|
39
|
+
}
|
|
27
40
|
|
|
28
41
|
//# sourceMappingURL=form-errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/members/form-errors.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nexport type InvitationField = 'email';\n\nexport type InvitationFormErrorMapping =\n | {kind: 'field'; field: InvitationField; message: string}\n | {kind: 'form'; message: string};\n\nexport function invitationErrorToFormError(error: unknown): InvitationFormErrorMapping {\n if (error instanceof ApiError
|
|
1
|
+
{"version":3,"sources":["../../../src/components/members/form-errors.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nexport type InvitationField = 'email';\n\nexport type InvitationFormErrorMapping =\n | {kind: 'field'; field: InvitationField; message: string}\n | {kind: 'form'; message: string};\n\nexport function invitationErrorToFormError(error: unknown): InvitationFormErrorMapping {\n if (error instanceof ApiError) {\n if (error.code === 'open-invitation-exists') {\n return {\n kind: 'field',\n field: 'email',\n message: 'An open invitation already exists for this email.',\n };\n }\n if (error.code === 'rate-limited') {\n return {kind: 'form', message: 'Too many invitations were sent. Try again shortly.'};\n }\n return {kind: 'form', message: error.message};\n }\n if (error instanceof Error) return {kind: 'form', message: error.message};\n return {kind: 'form', message: 'Could not send invitation.'};\n}\n\nexport function memberRemovalErrorMessage(error: unknown): string {\n if (error instanceof ApiError) {\n if (error.code === 'self-removal-not-allowed') return \"You can't remove yourself.\";\n if (error.code === 'last-member') return 'Cannot remove the last member.';\n return error.message;\n }\n if (error instanceof Error) return error.message;\n return 'Could not remove member.';\n}\n"],"names":["ApiError","invitationErrorToFormError","error","code","kind","field","message","Error","memberRemovalErrorMessage"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAQ7C,OAAO,SAASC,2BAA2BC,KAAc;IACvD,IAAIA,iBAAiBF,UAAU;QAC7B,IAAIE,MAAMC,IAAI,KAAK,0BAA0B;YAC3C,OAAO;gBACLC,MAAM;gBACNC,OAAO;gBACPC,SAAS;YACX;QACF;QACA,IAAIJ,MAAMC,IAAI,KAAK,gBAAgB;YACjC,OAAO;gBAACC,MAAM;gBAAQE,SAAS;YAAoD;QACrF;QACA,OAAO;YAACF,MAAM;YAAQE,SAASJ,MAAMI,OAAO;QAAA;IAC9C;IACA,IAAIJ,iBAAiBK,OAAO,OAAO;QAACH,MAAM;QAAQE,SAASJ,MAAMI,OAAO;IAAA;IACxE,OAAO;QAACF,MAAM;QAAQE,SAAS;IAA4B;AAC7D;AAEA,OAAO,SAASE,0BAA0BN,KAAc;IACtD,IAAIA,iBAAiBF,UAAU;QAC7B,IAAIE,MAAMC,IAAI,KAAK,4BAA4B,OAAO;QACtD,IAAID,MAAMC,IAAI,KAAK,eAAe,OAAO;QACzC,OAAOD,MAAMI,OAAO;IACtB;IACA,IAAIJ,iBAAiBK,OAAO,OAAOL,MAAMI,OAAO;IAChD,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-members-section.d.ts","sourceRoot":"","sources":["../../../src/components/members/workspace-members-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workspace-members-section.d.ts","sourceRoot":"","sources":["../../../src/components/members/workspace-members-section.tsx"],"names":[],"mappings":"AA8CA,wBAAgB,+BAA+B,CAAC,EAC9C,WAAW,EACX,aAAa,GACd,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,+BAOA"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createInvitationBodySchema } from '@shipfox/api-workspaces-dto';
|
|
3
|
-
import { ApiError } from '@shipfox/client-api';
|
|
4
3
|
import { useAuthState } from '@shipfox/client-auth';
|
|
5
4
|
import { QueryLoadError } from '@shipfox/client-ui';
|
|
6
5
|
import { Badge } from '@shipfox/react-ui/badge';
|
|
@@ -17,13 +16,13 @@ import { Code, Header, Text } from '@shipfox/react-ui/typography';
|
|
|
17
16
|
import { formatDate } from '@shipfox/react-ui/utils';
|
|
18
17
|
import { useForm } from '@tanstack/react-form';
|
|
19
18
|
import { useState } from 'react';
|
|
19
|
+
import { getInvitationExpiry, getMemberRemovalRestriction, memberCount } from '#core/membership.js';
|
|
20
20
|
import { useCreateInvitation } from '#hooks/api/create-invitation.js';
|
|
21
21
|
import { useListInvitations } from '#hooks/api/list-invitations.js';
|
|
22
22
|
import { useListMembers } from '#hooks/api/list-members.js';
|
|
23
23
|
import { useRemoveMember } from '#hooks/api/remove-member.js';
|
|
24
24
|
import { useRevokeInvitation } from '#hooks/api/revoke-invitation.js';
|
|
25
|
-
import { invitationErrorToFormError } from './form-errors.js';
|
|
26
|
-
const EXPIRES_SOON_MS = 24 * 60 * 60 * 1000;
|
|
25
|
+
import { invitationErrorToFormError, memberRemovalErrorMessage } from './form-errors.js';
|
|
27
26
|
export function WorkspaceMembersSettingsSection({ workspaceId, workspaceName }) {
|
|
28
27
|
return /*#__PURE__*/ _jsxs("div", {
|
|
29
28
|
className: "flex min-w-0 flex-col gap-32",
|
|
@@ -42,7 +41,7 @@ export function WorkspaceMembersSettingsSection({ workspaceId, workspaceName })
|
|
|
42
41
|
function MembersSection({ workspaceId, workspaceName }) {
|
|
43
42
|
const auth = useAuthState();
|
|
44
43
|
const query = useListMembers(workspaceId);
|
|
45
|
-
const members = query.data
|
|
44
|
+
const members = query.data ?? [];
|
|
46
45
|
return /*#__PURE__*/ _jsxs("section", {
|
|
47
46
|
className: "flex flex-col gap-16",
|
|
48
47
|
children: [
|
|
@@ -56,7 +55,7 @@ function MembersSection({ workspaceId, workspaceName }) {
|
|
|
56
55
|
/*#__PURE__*/ _jsx(Text, {
|
|
57
56
|
size: "sm",
|
|
58
57
|
className: "text-foreground-neutral-muted",
|
|
59
|
-
children: query.isPending ? 'Loading members…' : `${members
|
|
58
|
+
children: query.isPending ? 'Loading members…' : `${memberCount(members)} ${memberCount(members) === 1 ? 'member' : 'members'}`
|
|
60
59
|
})
|
|
61
60
|
]
|
|
62
61
|
}),
|
|
@@ -92,7 +91,8 @@ function MembersSection({ workspaceId, workspaceName }) {
|
|
|
92
91
|
/*#__PURE__*/ _jsx(TableBody, {
|
|
93
92
|
children: members.map((member)=>/*#__PURE__*/ _jsx(MemberRow, {
|
|
94
93
|
member: member,
|
|
95
|
-
|
|
94
|
+
members: members,
|
|
95
|
+
currentUserId: auth.user?.id,
|
|
96
96
|
workspaceId: workspaceId,
|
|
97
97
|
workspaceName: workspaceName
|
|
98
98
|
}, member.id))
|
|
@@ -102,16 +102,23 @@ function MembersSection({ workspaceId, workspaceName }) {
|
|
|
102
102
|
]
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
-
function MemberRow({ member,
|
|
105
|
+
function MemberRow({ member, members, currentUserId, workspaceId, workspaceName }) {
|
|
106
106
|
const [open, setOpen] = useState(false);
|
|
107
107
|
const remove = useRemoveMember(workspaceId);
|
|
108
|
+
const restriction = getMemberRemovalRestriction({
|
|
109
|
+
member,
|
|
110
|
+
members,
|
|
111
|
+
currentUserId
|
|
112
|
+
});
|
|
108
113
|
async function handleRemove() {
|
|
109
114
|
try {
|
|
110
|
-
await remove.mutateAsync(
|
|
111
|
-
|
|
115
|
+
await remove.mutateAsync({
|
|
116
|
+
userId: member.userId
|
|
117
|
+
});
|
|
118
|
+
toast.success(`Removed ${member.email} from ${workspaceName}.`);
|
|
112
119
|
setOpen(false);
|
|
113
120
|
} catch (error) {
|
|
114
|
-
toast.error(
|
|
121
|
+
toast.error(memberRemovalErrorMessage(error));
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
124
|
return /*#__PURE__*/ _jsxs(TableRow, {
|
|
@@ -119,16 +126,16 @@ function MemberRow({ member, isSelf, workspaceId, workspaceName }) {
|
|
|
119
126
|
children: [
|
|
120
127
|
/*#__PURE__*/ _jsx(TableCell, {
|
|
121
128
|
className: "font-medium",
|
|
122
|
-
children: member.
|
|
129
|
+
children: member.name ?? '—'
|
|
123
130
|
}),
|
|
124
131
|
/*#__PURE__*/ _jsx(TableCell, {
|
|
125
132
|
children: /*#__PURE__*/ _jsx(Code, {
|
|
126
133
|
variant: "paragraph",
|
|
127
|
-
children: member.
|
|
134
|
+
children: member.email
|
|
128
135
|
})
|
|
129
136
|
}),
|
|
130
137
|
/*#__PURE__*/ _jsx(TableCell, {
|
|
131
|
-
children: formatDate(member.
|
|
138
|
+
children: formatDate(member.joinedAt)
|
|
132
139
|
}),
|
|
133
140
|
/*#__PURE__*/ _jsx(TableCell, {
|
|
134
141
|
className: "text-right",
|
|
@@ -142,7 +149,7 @@ function MemberRow({ member, isSelf, workspaceId, workspaceName }) {
|
|
|
142
149
|
size: "sm",
|
|
143
150
|
variant: "transparentMuted",
|
|
144
151
|
"aria-label": "Remove member",
|
|
145
|
-
disabled:
|
|
152
|
+
disabled: restriction !== undefined,
|
|
146
153
|
isLoading: remove.isPending,
|
|
147
154
|
className: remove.isPending ? 'opacity-100 transition-opacity' : 'opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100',
|
|
148
155
|
children: /*#__PURE__*/ _jsx(Icon, {
|
|
@@ -162,7 +169,7 @@ function MemberRow({ member, isSelf, workspaceId, workspaceName }) {
|
|
|
162
169
|
size: "lg",
|
|
163
170
|
children: [
|
|
164
171
|
"Remove ",
|
|
165
|
-
member.
|
|
172
|
+
member.name ?? member.email,
|
|
166
173
|
" from ",
|
|
167
174
|
workspaceName,
|
|
168
175
|
"?"
|
|
@@ -274,11 +281,12 @@ function PendingInvitationsSection({ workspaceId, workspaceName }) {
|
|
|
274
281
|
function InvitationRow({ invitation, workspaceId }) {
|
|
275
282
|
const [open, setOpen] = useState(false);
|
|
276
283
|
const revoke = useRevokeInvitation(workspaceId);
|
|
277
|
-
const
|
|
278
|
-
const expiresSoon = expiresAt.getTime() - Date.now() < EXPIRES_SOON_MS;
|
|
284
|
+
const expiry = getInvitationExpiry(invitation);
|
|
279
285
|
async function handleRevoke() {
|
|
280
286
|
try {
|
|
281
|
-
await revoke.mutateAsync(
|
|
287
|
+
await revoke.mutateAsync({
|
|
288
|
+
invitationId: invitation.id
|
|
289
|
+
});
|
|
282
290
|
toast.success(`Invitation to ${invitation.email} revoked.`);
|
|
283
291
|
setOpen(false);
|
|
284
292
|
} catch {
|
|
@@ -305,9 +313,13 @@ function InvitationRow({ invitation, workspaceId }) {
|
|
|
305
313
|
size: "sm",
|
|
306
314
|
children: formatDate(invitation.expiresAt)
|
|
307
315
|
}),
|
|
308
|
-
|
|
316
|
+
expiry === 'expires-soon' ? /*#__PURE__*/ _jsx(Badge, {
|
|
309
317
|
variant: "warning",
|
|
310
318
|
children: "Soon"
|
|
319
|
+
}) : null,
|
|
320
|
+
expiry === 'expired' ? /*#__PURE__*/ _jsx(Badge, {
|
|
321
|
+
variant: "error",
|
|
322
|
+
children: "Expired"
|
|
311
323
|
}) : null
|
|
312
324
|
]
|
|
313
325
|
})
|
|
@@ -528,14 +540,5 @@ function TableSkeleton({ rows, cols }) {
|
|
|
528
540
|
}, rowIdx))
|
|
529
541
|
});
|
|
530
542
|
}
|
|
531
|
-
function removeMemberErrorMessage(error) {
|
|
532
|
-
if (error instanceof ApiError) {
|
|
533
|
-
if (error.code === 'self-removal-not-allowed') return "You can't remove yourself.";
|
|
534
|
-
if (error.code === 'last-member') return 'Cannot remove the last member.';
|
|
535
|
-
return error.message;
|
|
536
|
-
}
|
|
537
|
-
if (error instanceof Error) return error.message;
|
|
538
|
-
return 'Could not remove member.';
|
|
539
|
-
}
|
|
540
543
|
|
|
541
544
|
//# sourceMappingURL=workspace-members-section.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/members/workspace-members-section.tsx"],"sourcesContent":["import {createInvitationBodySchema, type MembershipWithUserDto} from '@shipfox/api-workspaces-dto';\nimport {ApiError} from '@shipfox/client-api';\nimport {useAuthState} from '@shipfox/client-auth';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Badge} from '@shipfox/react-ui/badge';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {\n Modal,\n ModalBody,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalTitle,\n ModalTrigger,\n} from '@shipfox/react-ui/modal';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@shipfox/react-ui/table';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Code, Header, Text} from '@shipfox/react-ui/typography';\nimport {formatDate} from '@shipfox/react-ui/utils';\nimport {useForm} from '@tanstack/react-form';\nimport {useState} from 'react';\nimport type {Invitation} from '#core/invitation.js';\nimport {useCreateInvitation} from '#hooks/api/create-invitation.js';\nimport {useListInvitations} from '#hooks/api/list-invitations.js';\nimport {useListMembers} from '#hooks/api/list-members.js';\nimport {useRemoveMember} from '#hooks/api/remove-member.js';\nimport {useRevokeInvitation} from '#hooks/api/revoke-invitation.js';\nimport {invitationErrorToFormError} from './form-errors.js';\n\nconst EXPIRES_SOON_MS = 24 * 60 * 60 * 1000;\n\nexport function WorkspaceMembersSettingsSection({\n workspaceId,\n workspaceName,\n}: {\n workspaceId: string;\n workspaceName: string;\n}) {\n return (\n <div className=\"flex min-w-0 flex-col gap-32\">\n <MembersSection workspaceId={workspaceId} workspaceName={workspaceName} />\n <PendingInvitationsSection workspaceId={workspaceId} workspaceName={workspaceName} />\n </div>\n );\n}\n\nfunction MembersSection({\n workspaceId,\n workspaceName,\n}: {\n workspaceId: string;\n workspaceName: string;\n}) {\n const auth = useAuthState();\n const query = useListMembers(workspaceId);\n const members = query.data?.members ?? [];\n\n return (\n <section className=\"flex flex-col gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Members</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {query.isPending\n ? 'Loading members…'\n : `${members.length} ${members.length === 1 ? 'member' : 'members'}`}\n </Text>\n </div>\n\n {query.isPending ? <TableSkeleton rows={3} cols={3} /> : null}\n\n {query.isError && query.data === undefined ? (\n <QueryLoadError query={query} subject=\"members\" />\n ) : null}\n\n {members.length > 0 ? (\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Email</TableHead>\n <TableHead>Joined</TableHead>\n <TableHead className=\"w-80 text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {members.map((member) => (\n <MemberRow\n key={member.id}\n member={member}\n isSelf={member.user_id === auth.user?.id}\n workspaceId={workspaceId}\n workspaceName={workspaceName}\n />\n ))}\n </TableBody>\n </Table>\n ) : null}\n </section>\n );\n}\n\nfunction MemberRow({\n member,\n isSelf,\n workspaceId,\n workspaceName,\n}: {\n member: MembershipWithUserDto;\n isSelf: boolean;\n workspaceId: string;\n workspaceName: string;\n}) {\n const [open, setOpen] = useState(false);\n const remove = useRemoveMember(workspaceId);\n\n async function handleRemove() {\n try {\n await remove.mutateAsync(member.user_id);\n toast.success(`Removed ${member.user_email} from ${workspaceName}.`);\n setOpen(false);\n } catch (error) {\n toast.error(removeMemberErrorMessage(error));\n }\n }\n\n return (\n <TableRow className={remove.isPending ? 'opacity-60' : undefined}>\n <TableCell className=\"font-medium\">{member.user_name ?? '—'}</TableCell>\n <TableCell>\n <Code variant=\"paragraph\">{member.user_email}</Code>\n </TableCell>\n <TableCell>{formatDate(member.created_at)}</TableCell>\n <TableCell className=\"text-right\">\n <Modal open={open} onOpenChange={setOpen}>\n <ModalTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"transparentMuted\"\n aria-label=\"Remove member\"\n disabled={isSelf}\n isLoading={remove.isPending}\n className={\n remove.isPending\n ? 'opacity-100 transition-opacity'\n : 'opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100'\n }\n >\n <Icon name=\"userUnfollowLine\" className=\"size-16\" />\n </Button>\n </ModalTrigger>\n <ModalContent>\n <ModalTitle className=\"sr-only\">Remove member</ModalTitle>\n <ModalHeader>\n <Text size=\"lg\">\n Remove {member.user_name ?? member.user_email} from {workspaceName}?\n </Text>\n </ModalHeader>\n <ModalBody>\n <Text size=\"sm\">\n They will lose access immediately. They can be re-invited later.\n </Text>\n </ModalBody>\n <ModalFooter>\n <Button variant=\"secondary\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button variant=\"danger\" onClick={handleRemove} isLoading={remove.isPending}>\n Remove\n </Button>\n </ModalFooter>\n </ModalContent>\n </Modal>\n </TableCell>\n </TableRow>\n );\n}\n\nfunction PendingInvitationsSection({\n workspaceId,\n workspaceName,\n}: {\n workspaceId: string;\n workspaceName: string;\n}) {\n const query = useListInvitations(workspaceId);\n const invitations = query.data ?? [];\n const [inviteOpen, setInviteOpen] = useState(false);\n\n return (\n <section className=\"flex flex-col gap-16\">\n <div className=\"flex items-center justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Pending invitations</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {query.isPending\n ? 'Loading invitations…'\n : `${invitations.length} ${invitations.length === 1 ? 'invitation' : 'invitations'}`}\n </Text>\n </div>\n <InviteMemberModal\n open={inviteOpen}\n onOpenChange={setInviteOpen}\n workspaceId={workspaceId}\n workspaceName={workspaceName}\n />\n </div>\n\n {query.isPending ? <TableSkeleton rows={2} cols={3} /> : null}\n\n {query.isError && query.data === undefined ? (\n <QueryLoadError query={query} subject=\"invitations\" />\n ) : null}\n\n {query.data !== undefined && invitations.length === 0 ? <EmptyInvitations /> : null}\n\n {invitations.length > 0 ? (\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Email</TableHead>\n <TableHead>Invited by</TableHead>\n <TableHead>Expires</TableHead>\n <TableHead className=\"w-80 text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {invitations.map((invitation) => (\n <InvitationRow\n key={invitation.id}\n invitation={invitation}\n workspaceId={workspaceId}\n />\n ))}\n </TableBody>\n </Table>\n ) : null}\n </section>\n );\n}\n\nfunction InvitationRow({invitation, workspaceId}: {invitation: Invitation; workspaceId: string}) {\n const [open, setOpen] = useState(false);\n const revoke = useRevokeInvitation(workspaceId);\n const expiresAt = new Date(invitation.expiresAt);\n const expiresSoon = expiresAt.getTime() - Date.now() < EXPIRES_SOON_MS;\n\n async function handleRevoke() {\n try {\n await revoke.mutateAsync(invitation.id);\n toast.success(`Invitation to ${invitation.email} revoked.`);\n setOpen(false);\n } catch {\n toast.error('Could not revoke invitation.');\n }\n }\n\n return (\n <TableRow className={revoke.isPending ? 'opacity-60' : undefined}>\n <TableCell>\n <Code variant=\"paragraph\">{invitation.email}</Code>\n </TableCell>\n <TableCell>{invitation.invitedByDisplay ?? '—'}</TableCell>\n <TableCell>\n <div className=\"flex items-center gap-8\">\n <Text size=\"sm\">{formatDate(invitation.expiresAt)}</Text>\n {expiresSoon ? <Badge variant=\"warning\">Soon</Badge> : null}\n </div>\n </TableCell>\n <TableCell className=\"text-right\">\n <Modal open={open} onOpenChange={setOpen}>\n <ModalTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"transparentMuted\"\n aria-label=\"Revoke invitation\"\n isLoading={revoke.isPending}\n className={\n revoke.isPending\n ? 'opacity-100 transition-opacity'\n : 'opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100'\n }\n >\n <Icon name=\"closeLine\" className=\"size-16\" />\n </Button>\n </ModalTrigger>\n <ModalContent>\n <ModalTitle className=\"sr-only\">Revoke invitation</ModalTitle>\n <ModalHeader>\n <Text size=\"lg\">Revoke invitation to {invitation.email}?</Text>\n </ModalHeader>\n <ModalBody>\n <Text size=\"sm\">They will no longer be able to use the link from their email.</Text>\n </ModalBody>\n <ModalFooter>\n <Button variant=\"secondary\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button variant=\"danger\" onClick={handleRevoke} isLoading={revoke.isPending}>\n Revoke\n </Button>\n </ModalFooter>\n </ModalContent>\n </Modal>\n </TableCell>\n </TableRow>\n );\n}\n\nfunction InviteMemberModal({\n open,\n onOpenChange,\n workspaceId,\n workspaceName,\n}: {\n open: boolean;\n onOpenChange: (next: boolean) => void;\n workspaceId: string;\n workspaceName: string;\n}) {\n const create = useCreateInvitation(workspaceId);\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {email: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const result = await create.mutateAsync({email: value.email});\n toast.success(`Invitation sent to ${result.email}.`);\n // Go through handleOpenChange so form.reset() fires and reopening the\n // modal does not show stale form state.\n handleOpenChange(false);\n } catch (error) {\n const mapped = invitationErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n function handleOpenChange(nextOpen: boolean) {\n if (!nextOpen) {\n form.reset();\n setFormError(undefined);\n create.reset();\n }\n onOpenChange(nextOpen);\n }\n\n return (\n <Modal open={open} onOpenChange={handleOpenChange}>\n <ModalTrigger asChild>\n <Button size=\"sm\" variant=\"secondary\">\n Invite member\n </Button>\n </ModalTrigger>\n <ModalContent>\n <ModalTitle className=\"sr-only\">Invite a member</ModalTitle>\n <ModalHeader>\n <Text size=\"lg\">Invite a member</Text>\n </ModalHeader>\n <ModalBody className=\"gap-16\">\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form\n id=\"invite-member-form\"\n className=\"flex flex-col gap-16\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <form.Field\n name=\"email\"\n validators={{\n onBlur: createInvitationBodySchema.shape.email,\n onSubmit: createInvitationBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField\n label=\"Email\"\n id=\"invite-email\"\n error={fieldError(field)}\n description={`They'll receive an email with a link to join ${workspaceName}.`}\n >\n <FormFieldInput\n autoComplete=\"email\"\n autoFocus\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </form>\n </ModalBody>\n <ModalFooter>\n <Button variant=\"secondary\" onClick={() => handleOpenChange(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" form=\"invite-member-form\" isLoading={create.isPending}>\n Send invitation\n </Button>\n </ModalFooter>\n </ModalContent>\n </Modal>\n );\n}\n\nfunction EmptyInvitations() {\n return (\n <EmptyState\n icon=\"mailLine\"\n title=\"No pending invitations.\"\n description=\"Invite someone above to grow your workspace.\"\n />\n );\n}\n\nfunction TableSkeleton({rows, cols}: {rows: number; cols: number}) {\n return (\n <div className=\"flex flex-col gap-12\">\n {Array.from({length: rows}).map((_, rowIdx) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder rows\n <div key={rowIdx} className=\"grid grid-cols-3 gap-16\">\n {Array.from({length: cols}).map((__, colIdx) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder cells\n <Skeleton key={colIdx} className=\"h-20\" />\n ))}\n </div>\n ))}\n </div>\n );\n}\n\nfunction removeMemberErrorMessage(error: unknown): string {\n if (error instanceof ApiError) {\n if (error.code === 'self-removal-not-allowed') return \"You can't remove yourself.\";\n if (error.code === 'last-member') return 'Cannot remove the last member.';\n return error.message;\n }\n if (error instanceof Error) return error.message;\n return 'Could not remove member.';\n}\n"],"names":["createInvitationBodySchema","ApiError","useAuthState","QueryLoadError","Badge","Button","Callout","EmptyState","FormField","FormFieldInput","fieldError","Icon","Modal","ModalBody","ModalContent","ModalFooter","ModalHeader","ModalTitle","ModalTrigger","Skeleton","Table","TableBody","TableCell","TableHead","TableHeader","TableRow","toast","Code","Header","Text","formatDate","useForm","useState","useCreateInvitation","useListInvitations","useListMembers","useRemoveMember","useRevokeInvitation","invitationErrorToFormError","EXPIRES_SOON_MS","WorkspaceMembersSettingsSection","workspaceId","workspaceName","div","className","MembersSection","PendingInvitationsSection","auth","query","members","data","section","variant","size","isPending","length","TableSkeleton","rows","cols","isError","undefined","subject","map","member","MemberRow","isSelf","user_id","user","id","open","setOpen","remove","handleRemove","mutateAsync","success","user_email","error","removeMemberErrorMessage","user_name","created_at","onOpenChange","asChild","aria-label","disabled","isLoading","name","onClick","invitations","inviteOpen","setInviteOpen","InviteMemberModal","EmptyInvitations","invitation","InvitationRow","revoke","expiresAt","Date","expiresSoon","getTime","now","handleRevoke","email","invitedByDisplay","create","formError","setFormError","form","defaultValues","onSubmit","value","result","handleOpenChange","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","nextOpen","reset","role","type","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","shape","label","description","autoComplete","autoFocus","state","onChange","handleChange","target","handleBlur","icon","title","Array","from","_","rowIdx","__","colIdx","code","Error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/members/workspace-members-section.tsx"],"sourcesContent":["import {createInvitationBodySchema} from '@shipfox/api-workspaces-dto';\nimport {useAuthState} from '@shipfox/client-auth';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Badge} from '@shipfox/react-ui/badge';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {\n Modal,\n ModalBody,\n ModalContent,\n ModalFooter,\n ModalHeader,\n ModalTitle,\n ModalTrigger,\n} from '@shipfox/react-ui/modal';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@shipfox/react-ui/table';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Code, Header, Text} from '@shipfox/react-ui/typography';\nimport {formatDate} from '@shipfox/react-ui/utils';\nimport {useForm} from '@tanstack/react-form';\nimport {useState} from 'react';\nimport {\n getInvitationExpiry,\n getMemberRemovalRestriction,\n memberCount,\n type PendingInvitation,\n type WorkspaceMember,\n} from '#core/membership.js';\nimport {useCreateInvitation} from '#hooks/api/create-invitation.js';\nimport {useListInvitations} from '#hooks/api/list-invitations.js';\nimport {useListMembers} from '#hooks/api/list-members.js';\nimport {useRemoveMember} from '#hooks/api/remove-member.js';\nimport {useRevokeInvitation} from '#hooks/api/revoke-invitation.js';\nimport {invitationErrorToFormError, memberRemovalErrorMessage} from './form-errors.js';\n\nexport function WorkspaceMembersSettingsSection({\n workspaceId,\n workspaceName,\n}: {\n workspaceId: string;\n workspaceName: string;\n}) {\n return (\n <div className=\"flex min-w-0 flex-col gap-32\">\n <MembersSection workspaceId={workspaceId} workspaceName={workspaceName} />\n <PendingInvitationsSection workspaceId={workspaceId} workspaceName={workspaceName} />\n </div>\n );\n}\n\nfunction MembersSection({\n workspaceId,\n workspaceName,\n}: {\n workspaceId: string;\n workspaceName: string;\n}) {\n const auth = useAuthState();\n const query = useListMembers(workspaceId);\n const members = query.data ?? [];\n\n return (\n <section className=\"flex flex-col gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Members</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {query.isPending\n ? 'Loading members…'\n : `${memberCount(members)} ${memberCount(members) === 1 ? 'member' : 'members'}`}\n </Text>\n </div>\n\n {query.isPending ? <TableSkeleton rows={3} cols={3} /> : null}\n\n {query.isError && query.data === undefined ? (\n <QueryLoadError query={query} subject=\"members\" />\n ) : null}\n\n {members.length > 0 ? (\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Email</TableHead>\n <TableHead>Joined</TableHead>\n <TableHead className=\"w-80 text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {members.map((member) => (\n <MemberRow\n key={member.id}\n member={member}\n members={members}\n currentUserId={auth.user?.id}\n workspaceId={workspaceId}\n workspaceName={workspaceName}\n />\n ))}\n </TableBody>\n </Table>\n ) : null}\n </section>\n );\n}\n\nfunction MemberRow({\n member,\n members,\n currentUserId,\n workspaceId,\n workspaceName,\n}: {\n member: WorkspaceMember;\n members: readonly WorkspaceMember[];\n currentUserId: string | undefined;\n workspaceId: string;\n workspaceName: string;\n}) {\n const [open, setOpen] = useState(false);\n const remove = useRemoveMember(workspaceId);\n const restriction = getMemberRemovalRestriction({member, members, currentUserId});\n\n async function handleRemove() {\n try {\n await remove.mutateAsync({userId: member.userId});\n toast.success(`Removed ${member.email} from ${workspaceName}.`);\n setOpen(false);\n } catch (error) {\n toast.error(memberRemovalErrorMessage(error));\n }\n }\n\n return (\n <TableRow className={remove.isPending ? 'opacity-60' : undefined}>\n <TableCell className=\"font-medium\">{member.name ?? '—'}</TableCell>\n <TableCell>\n <Code variant=\"paragraph\">{member.email}</Code>\n </TableCell>\n <TableCell>{formatDate(member.joinedAt)}</TableCell>\n <TableCell className=\"text-right\">\n <Modal open={open} onOpenChange={setOpen}>\n <ModalTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"transparentMuted\"\n aria-label=\"Remove member\"\n disabled={restriction !== undefined}\n isLoading={remove.isPending}\n className={\n remove.isPending\n ? 'opacity-100 transition-opacity'\n : 'opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100'\n }\n >\n <Icon name=\"userUnfollowLine\" className=\"size-16\" />\n </Button>\n </ModalTrigger>\n <ModalContent>\n <ModalTitle className=\"sr-only\">Remove member</ModalTitle>\n <ModalHeader>\n <Text size=\"lg\">\n Remove {member.name ?? member.email} from {workspaceName}?\n </Text>\n </ModalHeader>\n <ModalBody>\n <Text size=\"sm\">\n They will lose access immediately. They can be re-invited later.\n </Text>\n </ModalBody>\n <ModalFooter>\n <Button variant=\"secondary\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button variant=\"danger\" onClick={handleRemove} isLoading={remove.isPending}>\n Remove\n </Button>\n </ModalFooter>\n </ModalContent>\n </Modal>\n </TableCell>\n </TableRow>\n );\n}\n\nfunction PendingInvitationsSection({\n workspaceId,\n workspaceName,\n}: {\n workspaceId: string;\n workspaceName: string;\n}) {\n const query = useListInvitations(workspaceId);\n const invitations = query.data ?? [];\n const [inviteOpen, setInviteOpen] = useState(false);\n\n return (\n <section className=\"flex flex-col gap-16\">\n <div className=\"flex items-center justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Pending invitations</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {query.isPending\n ? 'Loading invitations…'\n : `${invitations.length} ${invitations.length === 1 ? 'invitation' : 'invitations'}`}\n </Text>\n </div>\n <InviteMemberModal\n open={inviteOpen}\n onOpenChange={setInviteOpen}\n workspaceId={workspaceId}\n workspaceName={workspaceName}\n />\n </div>\n\n {query.isPending ? <TableSkeleton rows={2} cols={3} /> : null}\n\n {query.isError && query.data === undefined ? (\n <QueryLoadError query={query} subject=\"invitations\" />\n ) : null}\n\n {query.data !== undefined && invitations.length === 0 ? <EmptyInvitations /> : null}\n\n {invitations.length > 0 ? (\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Email</TableHead>\n <TableHead>Invited by</TableHead>\n <TableHead>Expires</TableHead>\n <TableHead className=\"w-80 text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {invitations.map((invitation) => (\n <InvitationRow\n key={invitation.id}\n invitation={invitation}\n workspaceId={workspaceId}\n />\n ))}\n </TableBody>\n </Table>\n ) : null}\n </section>\n );\n}\n\nfunction InvitationRow({\n invitation,\n workspaceId,\n}: {\n invitation: PendingInvitation;\n workspaceId: string;\n}) {\n const [open, setOpen] = useState(false);\n const revoke = useRevokeInvitation(workspaceId);\n const expiry = getInvitationExpiry(invitation);\n\n async function handleRevoke() {\n try {\n await revoke.mutateAsync({invitationId: invitation.id});\n toast.success(`Invitation to ${invitation.email} revoked.`);\n setOpen(false);\n } catch {\n toast.error('Could not revoke invitation.');\n }\n }\n\n return (\n <TableRow className={revoke.isPending ? 'opacity-60' : undefined}>\n <TableCell>\n <Code variant=\"paragraph\">{invitation.email}</Code>\n </TableCell>\n <TableCell>{invitation.invitedByDisplay ?? '—'}</TableCell>\n <TableCell>\n <div className=\"flex items-center gap-8\">\n <Text size=\"sm\">{formatDate(invitation.expiresAt)}</Text>\n {expiry === 'expires-soon' ? <Badge variant=\"warning\">Soon</Badge> : null}\n {expiry === 'expired' ? <Badge variant=\"error\">Expired</Badge> : null}\n </div>\n </TableCell>\n <TableCell className=\"text-right\">\n <Modal open={open} onOpenChange={setOpen}>\n <ModalTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"transparentMuted\"\n aria-label=\"Revoke invitation\"\n isLoading={revoke.isPending}\n className={\n revoke.isPending\n ? 'opacity-100 transition-opacity'\n : 'opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100'\n }\n >\n <Icon name=\"closeLine\" className=\"size-16\" />\n </Button>\n </ModalTrigger>\n <ModalContent>\n <ModalTitle className=\"sr-only\">Revoke invitation</ModalTitle>\n <ModalHeader>\n <Text size=\"lg\">Revoke invitation to {invitation.email}?</Text>\n </ModalHeader>\n <ModalBody>\n <Text size=\"sm\">They will no longer be able to use the link from their email.</Text>\n </ModalBody>\n <ModalFooter>\n <Button variant=\"secondary\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button variant=\"danger\" onClick={handleRevoke} isLoading={revoke.isPending}>\n Revoke\n </Button>\n </ModalFooter>\n </ModalContent>\n </Modal>\n </TableCell>\n </TableRow>\n );\n}\n\nfunction InviteMemberModal({\n open,\n onOpenChange,\n workspaceId,\n workspaceName,\n}: {\n open: boolean;\n onOpenChange: (next: boolean) => void;\n workspaceId: string;\n workspaceName: string;\n}) {\n const create = useCreateInvitation(workspaceId);\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {email: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const result = await create.mutateAsync({email: value.email});\n toast.success(`Invitation sent to ${result.email}.`);\n // Go through handleOpenChange so form.reset() fires and reopening the\n // modal does not show stale form state.\n handleOpenChange(false);\n } catch (error) {\n const mapped = invitationErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n function handleOpenChange(nextOpen: boolean) {\n if (!nextOpen) {\n form.reset();\n setFormError(undefined);\n create.reset();\n }\n onOpenChange(nextOpen);\n }\n\n return (\n <Modal open={open} onOpenChange={handleOpenChange}>\n <ModalTrigger asChild>\n <Button size=\"sm\" variant=\"secondary\">\n Invite member\n </Button>\n </ModalTrigger>\n <ModalContent>\n <ModalTitle className=\"sr-only\">Invite a member</ModalTitle>\n <ModalHeader>\n <Text size=\"lg\">Invite a member</Text>\n </ModalHeader>\n <ModalBody className=\"gap-16\">\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form\n id=\"invite-member-form\"\n className=\"flex flex-col gap-16\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <form.Field\n name=\"email\"\n validators={{\n onBlur: createInvitationBodySchema.shape.email,\n onSubmit: createInvitationBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField\n label=\"Email\"\n id=\"invite-email\"\n error={fieldError(field)}\n description={`They'll receive an email with a link to join ${workspaceName}.`}\n >\n <FormFieldInput\n autoComplete=\"email\"\n autoFocus\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </form>\n </ModalBody>\n <ModalFooter>\n <Button variant=\"secondary\" onClick={() => handleOpenChange(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" form=\"invite-member-form\" isLoading={create.isPending}>\n Send invitation\n </Button>\n </ModalFooter>\n </ModalContent>\n </Modal>\n );\n}\n\nfunction EmptyInvitations() {\n return (\n <EmptyState\n icon=\"mailLine\"\n title=\"No pending invitations.\"\n description=\"Invite someone above to grow your workspace.\"\n />\n );\n}\n\nfunction TableSkeleton({rows, cols}: {rows: number; cols: number}) {\n return (\n <div className=\"flex flex-col gap-12\">\n {Array.from({length: rows}).map((_, rowIdx) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder rows\n <div key={rowIdx} className=\"grid grid-cols-3 gap-16\">\n {Array.from({length: cols}).map((__, colIdx) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder cells\n <Skeleton key={colIdx} className=\"h-20\" />\n ))}\n </div>\n ))}\n </div>\n );\n}\n"],"names":["createInvitationBodySchema","useAuthState","QueryLoadError","Badge","Button","Callout","EmptyState","FormField","FormFieldInput","fieldError","Icon","Modal","ModalBody","ModalContent","ModalFooter","ModalHeader","ModalTitle","ModalTrigger","Skeleton","Table","TableBody","TableCell","TableHead","TableHeader","TableRow","toast","Code","Header","Text","formatDate","useForm","useState","getInvitationExpiry","getMemberRemovalRestriction","memberCount","useCreateInvitation","useListInvitations","useListMembers","useRemoveMember","useRevokeInvitation","invitationErrorToFormError","memberRemovalErrorMessage","WorkspaceMembersSettingsSection","workspaceId","workspaceName","div","className","MembersSection","PendingInvitationsSection","auth","query","members","data","section","variant","size","isPending","TableSkeleton","rows","cols","isError","undefined","subject","length","map","member","MemberRow","currentUserId","user","id","open","setOpen","remove","restriction","handleRemove","mutateAsync","userId","success","email","error","name","joinedAt","onOpenChange","asChild","aria-label","disabled","isLoading","onClick","invitations","inviteOpen","setInviteOpen","InviteMemberModal","EmptyInvitations","invitation","InvitationRow","revoke","expiry","handleRevoke","invitationId","invitedByDisplay","expiresAt","create","formError","setFormError","form","defaultValues","onSubmit","value","result","handleOpenChange","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","nextOpen","reset","role","type","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","shape","label","description","autoComplete","autoFocus","state","onChange","handleChange","target","handleBlur","icon","title","Array","from","_","rowIdx","__","colIdx"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type WorkspaceMemberRole = 'admin';
|
|
2
|
+
export interface WorkspaceMember {
|
|
3
|
+
id: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
email: string;
|
|
7
|
+
name: string | null;
|
|
8
|
+
role: WorkspaceMemberRole;
|
|
9
|
+
joinedAt: string;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PendingInvitation {
|
|
13
|
+
id: string;
|
|
14
|
+
workspaceId: string;
|
|
15
|
+
email: string;
|
|
16
|
+
expiresAt: string;
|
|
17
|
+
acceptedAt: string | null;
|
|
18
|
+
invitedByUserId: string;
|
|
19
|
+
invitedByDisplay: string | null;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
}
|
|
23
|
+
export interface CreateInvitationCommand {
|
|
24
|
+
email: string;
|
|
25
|
+
}
|
|
26
|
+
export interface RevokeInvitationCommand {
|
|
27
|
+
invitationId: string;
|
|
28
|
+
}
|
|
29
|
+
export interface RemoveWorkspaceMemberCommand {
|
|
30
|
+
userId: string;
|
|
31
|
+
}
|
|
32
|
+
export type InvitationExpiry = 'active' | 'expires-soon' | 'expired';
|
|
33
|
+
export declare function getInvitationExpiry(invitation: Pick<PendingInvitation, 'expiresAt'>, now?: number): InvitationExpiry;
|
|
34
|
+
export declare function memberCount(members: readonly WorkspaceMember[]): number;
|
|
35
|
+
export type MemberRemovalRestriction = 'self' | 'last-member';
|
|
36
|
+
export declare function getMemberRemovalRestriction(params: {
|
|
37
|
+
member: Pick<WorkspaceMember, 'userId'>;
|
|
38
|
+
currentUserId: string | undefined;
|
|
39
|
+
members: readonly WorkspaceMember[];
|
|
40
|
+
}): MemberRemovalRestriction | undefined;
|
|
41
|
+
//# sourceMappingURL=membership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"membership.d.ts","sourceRoot":"","sources":["../../src/core/membership.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,cAAc,GAAG,SAAS,CAAC;AAErE,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,EAChD,GAAG,SAAa,GACf,gBAAgB,CAKlB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,CAEvE;AAED,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,aAAa,CAAC;AAE9D,wBAAgB,2BAA2B,CAAC,MAAM,EAAE;IAClD,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IACxC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;CACrC,GAAG,wBAAwB,GAAG,SAAS,CAIvC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function getInvitationExpiry(invitation, now = Date.now()) {
|
|
2
|
+
const expiresAt = Date.parse(invitation.expiresAt);
|
|
3
|
+
if (expiresAt <= now) return 'expired';
|
|
4
|
+
if (expiresAt - now < 24 * 60 * 60 * 1000) return 'expires-soon';
|
|
5
|
+
return 'active';
|
|
6
|
+
}
|
|
7
|
+
export function memberCount(members) {
|
|
8
|
+
return members.length;
|
|
9
|
+
}
|
|
10
|
+
export function getMemberRemovalRestriction(params) {
|
|
11
|
+
if (params.member.userId === params.currentUserId) return 'self';
|
|
12
|
+
if (memberCount(params.members) <= 1) return 'last-member';
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=membership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/membership.ts"],"sourcesContent":["export type WorkspaceMemberRole = 'admin';\n\nexport interface WorkspaceMember {\n id: string;\n userId: string;\n workspaceId: string;\n email: string;\n name: string | null;\n role: WorkspaceMemberRole;\n joinedAt: string;\n updatedAt: string;\n}\n\nexport interface PendingInvitation {\n id: string;\n workspaceId: string;\n email: string;\n expiresAt: string;\n acceptedAt: string | null;\n invitedByUserId: string;\n invitedByDisplay: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface CreateInvitationCommand {\n email: string;\n}\n\nexport interface RevokeInvitationCommand {\n invitationId: string;\n}\n\nexport interface RemoveWorkspaceMemberCommand {\n userId: string;\n}\n\nexport type InvitationExpiry = 'active' | 'expires-soon' | 'expired';\n\nexport function getInvitationExpiry(\n invitation: Pick<PendingInvitation, 'expiresAt'>,\n now = Date.now(),\n): InvitationExpiry {\n const expiresAt = Date.parse(invitation.expiresAt);\n if (expiresAt <= now) return 'expired';\n if (expiresAt - now < 24 * 60 * 60 * 1000) return 'expires-soon';\n return 'active';\n}\n\nexport function memberCount(members: readonly WorkspaceMember[]): number {\n return members.length;\n}\n\nexport type MemberRemovalRestriction = 'self' | 'last-member';\n\nexport function getMemberRemovalRestriction(params: {\n member: Pick<WorkspaceMember, 'userId'>;\n currentUserId: string | undefined;\n members: readonly WorkspaceMember[];\n}): MemberRemovalRestriction | undefined {\n if (params.member.userId === params.currentUserId) return 'self';\n if (memberCount(params.members) <= 1) return 'last-member';\n return undefined;\n}\n"],"names":["getInvitationExpiry","invitation","now","Date","expiresAt","parse","memberCount","members","length","getMemberRemovalRestriction","params","member","userId","currentUserId","undefined"],"mappings":"AAuCA,OAAO,SAASA,oBACdC,UAAgD,EAChDC,MAAMC,KAAKD,GAAG,EAAE;IAEhB,MAAME,YAAYD,KAAKE,KAAK,CAACJ,WAAWG,SAAS;IACjD,IAAIA,aAAaF,KAAK,OAAO;IAC7B,IAAIE,YAAYF,MAAM,KAAK,KAAK,KAAK,MAAM,OAAO;IAClD,OAAO;AACT;AAEA,OAAO,SAASI,YAAYC,OAAmC;IAC7D,OAAOA,QAAQC,MAAM;AACvB;AAIA,OAAO,SAASC,4BAA4BC,MAI3C;IACC,IAAIA,OAAOC,MAAM,CAACC,MAAM,KAAKF,OAAOG,aAAa,EAAE,OAAO;IAC1D,IAAIP,YAAYI,OAAOH,OAAO,KAAK,GAAG,OAAO;IAC7C,OAAOO;AACT"}
|
package/dist/feature.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
export declare const workspaceSettingsNavigation: readonly [{
|
|
2
|
+
readonly id: "nav.settings";
|
|
3
|
+
readonly scope: "workspace";
|
|
4
|
+
readonly label: "Settings";
|
|
5
|
+
readonly to: "/workspaces/$wid/settings";
|
|
6
|
+
readonly order: 200;
|
|
7
|
+
}];
|
|
8
|
+
export declare const workspaceSettingsSections: readonly [{
|
|
9
|
+
readonly id: "settings.members";
|
|
10
|
+
readonly pathSegment: "members";
|
|
11
|
+
readonly label: "Members";
|
|
12
|
+
readonly icon: "userLine";
|
|
13
|
+
readonly order: 100;
|
|
14
|
+
}];
|
|
2
15
|
export declare const workspaceSettingsFeature: {
|
|
3
16
|
readonly id: "shipfox.workspace-settings";
|
|
4
17
|
readonly routes: readonly [{
|
|
@@ -21,7 +34,7 @@ export declare const workspaceSettingsFeature: {
|
|
|
21
34
|
readonly id: "settings.members";
|
|
22
35
|
readonly pathSegment: "members";
|
|
23
36
|
readonly label: "Members";
|
|
24
|
-
readonly icon:
|
|
37
|
+
readonly icon: "userLine";
|
|
25
38
|
readonly order: 100;
|
|
26
39
|
}];
|
|
27
40
|
};
|
package/dist/feature.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,2BAA2B;;;;;;EAQG,CAAC;AAE5C,eAAO,MAAM,yBAAyB;;;;;;EAQc,CAAC;AAErD,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;CAgBnC,CAAC"}
|
package/dist/feature.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { defineClientFeature } from '@shipfox/client-shell';
|
|
2
|
+
export const workspaceSettingsNavigation = [
|
|
3
|
+
{
|
|
4
|
+
id: 'nav.settings',
|
|
5
|
+
scope: 'workspace',
|
|
6
|
+
label: 'Settings',
|
|
7
|
+
to: '/workspaces/$wid/settings',
|
|
8
|
+
order: 200
|
|
9
|
+
}
|
|
10
|
+
];
|
|
11
|
+
export const workspaceSettingsSections = [
|
|
12
|
+
{
|
|
13
|
+
id: 'settings.members',
|
|
14
|
+
pathSegment: 'members',
|
|
15
|
+
label: 'Members',
|
|
16
|
+
icon: 'userLine',
|
|
17
|
+
order: 100
|
|
18
|
+
}
|
|
19
|
+
];
|
|
2
20
|
export const workspaceSettingsFeature = defineClientFeature({
|
|
3
21
|
id: 'shipfox.workspace-settings',
|
|
4
22
|
routes: [
|
|
@@ -13,24 +31,8 @@ export const workspaceSettingsFeature = defineClientFeature({
|
|
|
13
31
|
impl: '@shipfox/client-workspace-settings/routes/members'
|
|
14
32
|
}
|
|
15
33
|
],
|
|
16
|
-
navigation:
|
|
17
|
-
|
|
18
|
-
id: 'nav.settings',
|
|
19
|
-
scope: 'workspace',
|
|
20
|
-
label: 'Settings',
|
|
21
|
-
to: '/workspaces/$wid/settings',
|
|
22
|
-
order: 200
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
settingsSections: [
|
|
26
|
-
{
|
|
27
|
-
id: 'settings.members',
|
|
28
|
-
pathSegment: 'members',
|
|
29
|
-
label: 'Members',
|
|
30
|
-
icon: 'userLine',
|
|
31
|
-
order: 100
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
+
navigation: workspaceSettingsNavigation,
|
|
35
|
+
settingsSections: workspaceSettingsSections
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
//# sourceMappingURL=feature.js.map
|
package/dist/feature.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/feature.ts"],"sourcesContent":["import {defineClientFeature} from '@shipfox/client-shell';\
|
|
1
|
+
{"version":3,"sources":["../src/feature.ts"],"sourcesContent":["import {\n defineClientFeature,\n type NavTabEntry,\n type SettingsSectionEntry,\n} from '@shipfox/client-shell';\n\nexport const workspaceSettingsNavigation = [\n {\n id: 'nav.settings',\n scope: 'workspace',\n label: 'Settings',\n to: '/workspaces/$wid/settings',\n order: 200,\n },\n] as const satisfies readonly NavTabEntry[];\n\nexport const workspaceSettingsSections = [\n {\n id: 'settings.members',\n pathSegment: 'members',\n label: 'Members',\n icon: 'userLine',\n order: 100,\n },\n] as const satisfies readonly SettingsSectionEntry[];\n\nexport const workspaceSettingsFeature = defineClientFeature({\n id: 'shipfox.workspace-settings',\n routes: [\n {\n path: '/workspaces/$wid/settings',\n parent: 'workspaceSettings',\n impl: '@shipfox/client-workspace-settings/routes/index',\n },\n {\n path: '/workspaces/$wid/settings/members',\n parent: 'workspaceSettings',\n impl: '@shipfox/client-workspace-settings/routes/members',\n },\n ],\n navigation: workspaceSettingsNavigation,\n settingsSections: workspaceSettingsSections,\n});\n"],"names":["defineClientFeature","workspaceSettingsNavigation","id","scope","label","to","order","workspaceSettingsSections","pathSegment","icon","workspaceSettingsFeature","routes","path","parent","impl","navigation","settingsSections"],"mappings":"AAAA,SACEA,mBAAmB,QAGd,wBAAwB;AAE/B,OAAO,MAAMC,8BAA8B;IACzC;QACEC,IAAI;QACJC,OAAO;QACPC,OAAO;QACPC,IAAI;QACJC,OAAO;IACT;CACD,CAA2C;AAE5C,OAAO,MAAMC,4BAA4B;IACvC;QACEL,IAAI;QACJM,aAAa;QACbJ,OAAO;QACPK,MAAM;QACNH,OAAO;IACT;CACD,CAAoD;AAErD,OAAO,MAAMI,2BAA2BV,oBAAoB;IAC1DE,IAAI;IACJS,QAAQ;QACN;YACEC,MAAM;YACNC,QAAQ;YACRC,MAAM;QACR;QACA;YACEF,MAAM;YACNC,QAAQ;YACRC,MAAM;QACR;KACD;IACDC,YAAYd;IACZe,kBAAkBT;AACpB,GAAG"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { type CreateInvitationBodyDto } from '@shipfox/api-workspaces-dto';
|
|
2
|
+
import type { CreateInvitationCommand, PendingInvitation } from '#core/membership.js';
|
|
3
|
+
export declare function toCreateInvitationBody(command: CreateInvitationCommand): CreateInvitationBodyDto;
|
|
4
|
+
export declare function useCreateInvitation(workspaceId: string): import("@tanstack/react-query").UseMutationResult<PendingInvitation, Error, CreateInvitationCommand, unknown>;
|
|
3
5
|
//# sourceMappingURL=create-invitation.d.ts.map
|