@shipfox/client-workspace-settings 0.2.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/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +197 -0
- package/LICENSE +21 -0
- package/dist/components/members/form-errors.d.ts +11 -0
- package/dist/components/members/form-errors.d.ts.map +1 -0
- package/dist/components/members/form-errors.js +28 -0
- package/dist/components/members/form-errors.js.map +1 -0
- package/dist/components/members/workspace-members-section.d.ts +5 -0
- package/dist/components/members/workspace-members-section.d.ts.map +1 -0
- package/dist/components/members/workspace-members-section.js +541 -0
- package/dist/components/members/workspace-members-section.js.map +1 -0
- package/dist/components/workspace-settings-shell.d.ts +8 -0
- package/dist/components/workspace-settings-shell.d.ts.map +1 -0
- package/dist/components/workspace-settings-shell.js +7 -0
- package/dist/components/workspace-settings-shell.js.map +1 -0
- package/dist/feature.d.ts +98 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +120 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/create-invitation.d.ts +14 -0
- package/dist/hooks/api/create-invitation.d.ts.map +1 -0
- package/dist/hooks/api/create-invitation.js +25 -0
- package/dist/hooks/api/create-invitation.js.map +1 -0
- package/dist/hooks/api/list-invitations.d.ts +15 -0
- package/dist/hooks/api/list-invitations.d.ts.map +1 -0
- package/dist/hooks/api/list-invitations.js +18 -0
- package/dist/hooks/api/list-invitations.js.map +1 -0
- package/dist/hooks/api/list-members.d.ts +13 -0
- package/dist/hooks/api/list-members.d.ts.map +1 -0
- package/dist/hooks/api/list-members.js +18 -0
- package/dist/hooks/api/list-members.js.map +1 -0
- package/dist/hooks/api/remove-member.d.ts +2 -0
- package/dist/hooks/api/remove-member.d.ts.map +1 -0
- package/dist/hooks/api/remove-member.js +24 -0
- package/dist/hooks/api/remove-member.js.map +1 -0
- package/dist/hooks/api/revoke-invitation.d.ts +2 -0
- package/dist/hooks/api/revoke-invitation.d.ts.map +1 -0
- package/dist/hooks/api/revoke-invitation.js +24 -0
- package/dist/hooks/api/revoke-invitation.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/events-settings-page.d.ts +2 -0
- package/dist/pages/events-settings-page.d.ts.map +1 -0
- package/dist/pages/events-settings-page.js +30 -0
- package/dist/pages/events-settings-page.js.map +1 -0
- package/dist/pages/integrations-settings-page.d.ts +2 -0
- package/dist/pages/integrations-settings-page.d.ts.map +1 -0
- package/dist/pages/integrations-settings-page.js +10 -0
- package/dist/pages/integrations-settings-page.js.map +1 -0
- package/dist/pages/members-settings-page.d.ts +2 -0
- package/dist/pages/members-settings-page.d.ts.map +1 -0
- package/dist/pages/members-settings-page.js +15 -0
- package/dist/pages/members-settings-page.js.map +1 -0
- package/dist/pages/model-providers-settings-page.d.ts +2 -0
- package/dist/pages/model-providers-settings-page.d.ts.map +1 -0
- package/dist/pages/model-providers-settings-page.js +20 -0
- package/dist/pages/model-providers-settings-page.js.map +1 -0
- package/dist/pages/provisioners-settings-page.d.ts +2 -0
- package/dist/pages/provisioners-settings-page.d.ts.map +1 -0
- package/dist/pages/provisioners-settings-page.js +14 -0
- package/dist/pages/provisioners-settings-page.js.map +1 -0
- package/dist/pages/runners-settings-page.d.ts +2 -0
- package/dist/pages/runners-settings-page.d.ts.map +1 -0
- package/dist/pages/runners-settings-page.js +14 -0
- package/dist/pages/runners-settings-page.js.map +1 -0
- package/dist/pages/secrets-settings-page.d.ts +2 -0
- package/dist/pages/secrets-settings-page.d.ts.map +1 -0
- package/dist/pages/secrets-settings-page.js +14 -0
- package/dist/pages/secrets-settings-page.js.map +1 -0
- package/dist/pages/variables-settings-page.d.ts +2 -0
- package/dist/pages/variables-settings-page.d.ts.map +1 -0
- package/dist/pages/variables-settings-page.js +14 -0
- package/dist/pages/variables-settings-page.js.map +1 -0
- package/dist/routes/agents.d.ts +6 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +7 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/events.d.ts +8 -0
- package/dist/routes/events.d.ts.map +1 -0
- package/dist/routes/events.js +9 -0
- package/dist/routes/events.js.map +1 -0
- package/dist/routes/index.d.ts +9 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +14 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/integrations.d.ts +6 -0
- package/dist/routes/integrations.d.ts.map +1 -0
- package/dist/routes/integrations.js +7 -0
- package/dist/routes/integrations.js.map +1 -0
- package/dist/routes/members.d.ts +6 -0
- package/dist/routes/members.d.ts.map +1 -0
- package/dist/routes/members.js +7 -0
- package/dist/routes/members.js.map +1 -0
- package/dist/routes/provisioners.d.ts +6 -0
- package/dist/routes/provisioners.d.ts.map +1 -0
- package/dist/routes/provisioners.js +7 -0
- package/dist/routes/provisioners.js.map +1 -0
- package/dist/routes/runners.d.ts +6 -0
- package/dist/routes/runners.d.ts.map +1 -0
- package/dist/routes/runners.js +7 -0
- package/dist/routes/runners.js.map +1 -0
- package/dist/routes/secrets.d.ts +6 -0
- package/dist/routes/secrets.d.ts.map +1 -0
- package/dist/routes/secrets.js +7 -0
- package/dist/routes/secrets.js.map +1 -0
- package/dist/routes/variables.d.ts +6 -0
- package/dist/routes/variables.d.ts.map +1 -0
- package/dist/routes/variables.js +7 -0
- package/dist/routes/variables.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +104 -0
- package/src/components/members/form-errors.test.ts +40 -0
- package/src/components/members/form-errors.ts +24 -0
- package/src/components/members/workspace-members-section.tsx +476 -0
- package/src/components/workspace-settings-shell.tsx +12 -0
- package/src/feature.ts +120 -0
- package/src/hooks/api/create-invitation.ts +21 -0
- package/src/hooks/api/list-invitations.ts +17 -0
- package/src/hooks/api/list-members.ts +17 -0
- package/src/hooks/api/remove-member.ts +19 -0
- package/src/hooks/api/revoke-invitation.ts +19 -0
- package/src/index.ts +8 -0
- package/src/pages/events-settings-page.tsx +25 -0
- package/src/pages/integrations-settings-page.test.tsx +72 -0
- package/src/pages/integrations-settings-page.tsx +6 -0
- package/src/pages/members-settings-page.tsx +17 -0
- package/src/pages/model-providers-settings-page.test.tsx +48 -0
- package/src/pages/model-providers-settings-page.tsx +15 -0
- package/src/pages/provisioners-settings-page.test.tsx +29 -0
- package/src/pages/provisioners-settings-page.tsx +14 -0
- package/src/pages/runners-settings-page.test.tsx +23 -0
- package/src/pages/runners-settings-page.tsx +14 -0
- package/src/pages/secrets-settings-page.tsx +14 -0
- package/src/pages/variables-settings-page.tsx +14 -0
- package/src/routes/agents.tsx +3 -0
- package/src/routes/events.tsx +7 -0
- package/src/routes/index.tsx +8 -0
- package/src/routes/integrations.tsx +3 -0
- package/src/routes/members.tsx +3 -0
- package/src/routes/provisioners.tsx +3 -0
- package/src/routes/runners.tsx +3 -0
- package/src/routes/secrets.tsx +3 -0
- package/src/routes/variables.tsx +3 -0
- package/test/pages.tsx +101 -0
- package/test/setup.ts +3 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createInvitationBodySchema } from '@shipfox/api-workspaces-dto';
|
|
3
|
+
import { ApiError } from '@shipfox/client-api';
|
|
4
|
+
import { useAuthState } from '@shipfox/client-auth';
|
|
5
|
+
import { QueryLoadError } from '@shipfox/client-ui';
|
|
6
|
+
import { Badge } from '@shipfox/react-ui/badge';
|
|
7
|
+
import { Button } from '@shipfox/react-ui/button';
|
|
8
|
+
import { Callout } from '@shipfox/react-ui/callout';
|
|
9
|
+
import { EmptyState } from '@shipfox/react-ui/empty-state';
|
|
10
|
+
import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
|
|
11
|
+
import { Icon } from '@shipfox/react-ui/icon';
|
|
12
|
+
import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ModalTitle, ModalTrigger } from '@shipfox/react-ui/modal';
|
|
13
|
+
import { Skeleton } from '@shipfox/react-ui/skeleton';
|
|
14
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@shipfox/react-ui/table';
|
|
15
|
+
import { toast } from '@shipfox/react-ui/toast';
|
|
16
|
+
import { Code, Header, Text } from '@shipfox/react-ui/typography';
|
|
17
|
+
import { formatDate } from '@shipfox/react-ui/utils';
|
|
18
|
+
import { useForm } from '@tanstack/react-form';
|
|
19
|
+
import { useState } from 'react';
|
|
20
|
+
import { useCreateInvitation } from '#hooks/api/create-invitation.js';
|
|
21
|
+
import { useListInvitations } from '#hooks/api/list-invitations.js';
|
|
22
|
+
import { useListMembers } from '#hooks/api/list-members.js';
|
|
23
|
+
import { useRemoveMember } from '#hooks/api/remove-member.js';
|
|
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;
|
|
27
|
+
export function WorkspaceMembersSettingsSection({ workspaceId, workspaceName }) {
|
|
28
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
29
|
+
className: "flex min-w-0 flex-col gap-32",
|
|
30
|
+
children: [
|
|
31
|
+
/*#__PURE__*/ _jsx(MembersSection, {
|
|
32
|
+
workspaceId: workspaceId,
|
|
33
|
+
workspaceName: workspaceName
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ _jsx(PendingInvitationsSection, {
|
|
36
|
+
workspaceId: workspaceId,
|
|
37
|
+
workspaceName: workspaceName
|
|
38
|
+
})
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function MembersSection({ workspaceId, workspaceName }) {
|
|
43
|
+
const auth = useAuthState();
|
|
44
|
+
const query = useListMembers(workspaceId);
|
|
45
|
+
const members = query.data?.members ?? [];
|
|
46
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
47
|
+
className: "flex flex-col gap-16",
|
|
48
|
+
children: [
|
|
49
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
50
|
+
className: "flex flex-col gap-4",
|
|
51
|
+
children: [
|
|
52
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
53
|
+
variant: "h3",
|
|
54
|
+
children: "Members"
|
|
55
|
+
}),
|
|
56
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
57
|
+
size: "sm",
|
|
58
|
+
className: "text-foreground-neutral-muted",
|
|
59
|
+
children: query.isPending ? 'Loading members…' : `${members.length} ${members.length === 1 ? 'member' : 'members'}`
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
}),
|
|
63
|
+
query.isPending ? /*#__PURE__*/ _jsx(TableSkeleton, {
|
|
64
|
+
rows: 3,
|
|
65
|
+
cols: 3
|
|
66
|
+
}) : null,
|
|
67
|
+
query.isError && query.data === undefined ? /*#__PURE__*/ _jsx(QueryLoadError, {
|
|
68
|
+
query: query,
|
|
69
|
+
subject: "members"
|
|
70
|
+
}) : null,
|
|
71
|
+
members.length > 0 ? /*#__PURE__*/ _jsxs(Table, {
|
|
72
|
+
children: [
|
|
73
|
+
/*#__PURE__*/ _jsx(TableHeader, {
|
|
74
|
+
children: /*#__PURE__*/ _jsxs(TableRow, {
|
|
75
|
+
children: [
|
|
76
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
77
|
+
children: "Name"
|
|
78
|
+
}),
|
|
79
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
80
|
+
children: "Email"
|
|
81
|
+
}),
|
|
82
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
83
|
+
children: "Joined"
|
|
84
|
+
}),
|
|
85
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
86
|
+
className: "w-80 text-right",
|
|
87
|
+
children: "Actions"
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
})
|
|
91
|
+
}),
|
|
92
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
93
|
+
children: members.map((member)=>/*#__PURE__*/ _jsx(MemberRow, {
|
|
94
|
+
member: member,
|
|
95
|
+
isSelf: member.user_id === auth.user?.id,
|
|
96
|
+
workspaceId: workspaceId,
|
|
97
|
+
workspaceName: workspaceName
|
|
98
|
+
}, member.id))
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
}) : null
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function MemberRow({ member, isSelf, workspaceId, workspaceName }) {
|
|
106
|
+
const [open, setOpen] = useState(false);
|
|
107
|
+
const remove = useRemoveMember(workspaceId);
|
|
108
|
+
async function handleRemove() {
|
|
109
|
+
try {
|
|
110
|
+
await remove.mutateAsync(member.user_id);
|
|
111
|
+
toast.success(`Removed ${member.user_email} from ${workspaceName}.`);
|
|
112
|
+
setOpen(false);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
toast.error(removeMemberErrorMessage(error));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return /*#__PURE__*/ _jsxs(TableRow, {
|
|
118
|
+
className: remove.isPending ? 'opacity-60' : undefined,
|
|
119
|
+
children: [
|
|
120
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
121
|
+
className: "font-medium",
|
|
122
|
+
children: member.user_name ?? '—'
|
|
123
|
+
}),
|
|
124
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
125
|
+
children: /*#__PURE__*/ _jsx(Code, {
|
|
126
|
+
variant: "paragraph",
|
|
127
|
+
children: member.user_email
|
|
128
|
+
})
|
|
129
|
+
}),
|
|
130
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
131
|
+
children: formatDate(member.created_at)
|
|
132
|
+
}),
|
|
133
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
134
|
+
className: "text-right",
|
|
135
|
+
children: /*#__PURE__*/ _jsxs(Modal, {
|
|
136
|
+
open: open,
|
|
137
|
+
onOpenChange: setOpen,
|
|
138
|
+
children: [
|
|
139
|
+
/*#__PURE__*/ _jsx(ModalTrigger, {
|
|
140
|
+
asChild: true,
|
|
141
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
142
|
+
size: "sm",
|
|
143
|
+
variant: "transparentMuted",
|
|
144
|
+
"aria-label": "Remove member",
|
|
145
|
+
disabled: isSelf,
|
|
146
|
+
isLoading: remove.isPending,
|
|
147
|
+
className: remove.isPending ? 'opacity-100 transition-opacity' : 'opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100',
|
|
148
|
+
children: /*#__PURE__*/ _jsx(Icon, {
|
|
149
|
+
name: "userUnfollowLine",
|
|
150
|
+
className: "size-16"
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
}),
|
|
154
|
+
/*#__PURE__*/ _jsxs(ModalContent, {
|
|
155
|
+
children: [
|
|
156
|
+
/*#__PURE__*/ _jsx(ModalTitle, {
|
|
157
|
+
className: "sr-only",
|
|
158
|
+
children: "Remove member"
|
|
159
|
+
}),
|
|
160
|
+
/*#__PURE__*/ _jsx(ModalHeader, {
|
|
161
|
+
children: /*#__PURE__*/ _jsxs(Text, {
|
|
162
|
+
size: "lg",
|
|
163
|
+
children: [
|
|
164
|
+
"Remove ",
|
|
165
|
+
member.user_name ?? member.user_email,
|
|
166
|
+
" from ",
|
|
167
|
+
workspaceName,
|
|
168
|
+
"?"
|
|
169
|
+
]
|
|
170
|
+
})
|
|
171
|
+
}),
|
|
172
|
+
/*#__PURE__*/ _jsx(ModalBody, {
|
|
173
|
+
children: /*#__PURE__*/ _jsx(Text, {
|
|
174
|
+
size: "sm",
|
|
175
|
+
children: "They will lose access immediately. They can be re-invited later."
|
|
176
|
+
})
|
|
177
|
+
}),
|
|
178
|
+
/*#__PURE__*/ _jsxs(ModalFooter, {
|
|
179
|
+
children: [
|
|
180
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
181
|
+
variant: "secondary",
|
|
182
|
+
onClick: ()=>setOpen(false),
|
|
183
|
+
children: "Cancel"
|
|
184
|
+
}),
|
|
185
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
186
|
+
variant: "danger",
|
|
187
|
+
onClick: handleRemove,
|
|
188
|
+
isLoading: remove.isPending,
|
|
189
|
+
children: "Remove"
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
})
|
|
193
|
+
]
|
|
194
|
+
})
|
|
195
|
+
]
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
]
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function PendingInvitationsSection({ workspaceId, workspaceName }) {
|
|
202
|
+
const query = useListInvitations(workspaceId);
|
|
203
|
+
const invitations = query.data?.invitations ?? [];
|
|
204
|
+
const [inviteOpen, setInviteOpen] = useState(false);
|
|
205
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
206
|
+
className: "flex flex-col gap-16",
|
|
207
|
+
children: [
|
|
208
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
209
|
+
className: "flex items-center justify-between gap-16",
|
|
210
|
+
children: [
|
|
211
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
212
|
+
className: "flex flex-col gap-4",
|
|
213
|
+
children: [
|
|
214
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
215
|
+
variant: "h3",
|
|
216
|
+
children: "Pending invitations"
|
|
217
|
+
}),
|
|
218
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
219
|
+
size: "sm",
|
|
220
|
+
className: "text-foreground-neutral-muted",
|
|
221
|
+
children: query.isPending ? 'Loading invitations…' : `${invitations.length} ${invitations.length === 1 ? 'invitation' : 'invitations'}`
|
|
222
|
+
})
|
|
223
|
+
]
|
|
224
|
+
}),
|
|
225
|
+
/*#__PURE__*/ _jsx(InviteMemberModal, {
|
|
226
|
+
open: inviteOpen,
|
|
227
|
+
onOpenChange: setInviteOpen,
|
|
228
|
+
workspaceId: workspaceId,
|
|
229
|
+
workspaceName: workspaceName
|
|
230
|
+
})
|
|
231
|
+
]
|
|
232
|
+
}),
|
|
233
|
+
query.isPending ? /*#__PURE__*/ _jsx(TableSkeleton, {
|
|
234
|
+
rows: 2,
|
|
235
|
+
cols: 3
|
|
236
|
+
}) : null,
|
|
237
|
+
query.isError && query.data === undefined ? /*#__PURE__*/ _jsx(QueryLoadError, {
|
|
238
|
+
query: query,
|
|
239
|
+
subject: "invitations"
|
|
240
|
+
}) : null,
|
|
241
|
+
query.data !== undefined && invitations.length === 0 ? /*#__PURE__*/ _jsx(EmptyInvitations, {}) : null,
|
|
242
|
+
invitations.length > 0 ? /*#__PURE__*/ _jsxs(Table, {
|
|
243
|
+
children: [
|
|
244
|
+
/*#__PURE__*/ _jsx(TableHeader, {
|
|
245
|
+
children: /*#__PURE__*/ _jsxs(TableRow, {
|
|
246
|
+
children: [
|
|
247
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
248
|
+
children: "Email"
|
|
249
|
+
}),
|
|
250
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
251
|
+
children: "Invited by"
|
|
252
|
+
}),
|
|
253
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
254
|
+
children: "Expires"
|
|
255
|
+
}),
|
|
256
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
257
|
+
className: "w-80 text-right",
|
|
258
|
+
children: "Actions"
|
|
259
|
+
})
|
|
260
|
+
]
|
|
261
|
+
})
|
|
262
|
+
}),
|
|
263
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
264
|
+
children: invitations.map((invitation)=>/*#__PURE__*/ _jsx(InvitationRow, {
|
|
265
|
+
invitation: invitation,
|
|
266
|
+
workspaceId: workspaceId
|
|
267
|
+
}, invitation.id))
|
|
268
|
+
})
|
|
269
|
+
]
|
|
270
|
+
}) : null
|
|
271
|
+
]
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function InvitationRow({ invitation, workspaceId }) {
|
|
275
|
+
const [open, setOpen] = useState(false);
|
|
276
|
+
const revoke = useRevokeInvitation(workspaceId);
|
|
277
|
+
const expiresAt = new Date(invitation.expires_at);
|
|
278
|
+
const expiresSoon = expiresAt.getTime() - Date.now() < EXPIRES_SOON_MS;
|
|
279
|
+
async function handleRevoke() {
|
|
280
|
+
try {
|
|
281
|
+
await revoke.mutateAsync(invitation.id);
|
|
282
|
+
toast.success(`Invitation to ${invitation.email} revoked.`);
|
|
283
|
+
setOpen(false);
|
|
284
|
+
} catch {
|
|
285
|
+
toast.error('Could not revoke invitation.');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return /*#__PURE__*/ _jsxs(TableRow, {
|
|
289
|
+
className: revoke.isPending ? 'opacity-60' : undefined,
|
|
290
|
+
children: [
|
|
291
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
292
|
+
children: /*#__PURE__*/ _jsx(Code, {
|
|
293
|
+
variant: "paragraph",
|
|
294
|
+
children: invitation.email
|
|
295
|
+
})
|
|
296
|
+
}),
|
|
297
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
298
|
+
children: invitation.invited_by_display ?? '—'
|
|
299
|
+
}),
|
|
300
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
301
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
302
|
+
className: "flex items-center gap-8",
|
|
303
|
+
children: [
|
|
304
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
305
|
+
size: "sm",
|
|
306
|
+
children: formatDate(invitation.expires_at)
|
|
307
|
+
}),
|
|
308
|
+
expiresSoon ? /*#__PURE__*/ _jsx(Badge, {
|
|
309
|
+
variant: "warning",
|
|
310
|
+
children: "Soon"
|
|
311
|
+
}) : null
|
|
312
|
+
]
|
|
313
|
+
})
|
|
314
|
+
}),
|
|
315
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
316
|
+
className: "text-right",
|
|
317
|
+
children: /*#__PURE__*/ _jsxs(Modal, {
|
|
318
|
+
open: open,
|
|
319
|
+
onOpenChange: setOpen,
|
|
320
|
+
children: [
|
|
321
|
+
/*#__PURE__*/ _jsx(ModalTrigger, {
|
|
322
|
+
asChild: true,
|
|
323
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
324
|
+
size: "sm",
|
|
325
|
+
variant: "transparentMuted",
|
|
326
|
+
"aria-label": "Revoke invitation",
|
|
327
|
+
isLoading: revoke.isPending,
|
|
328
|
+
className: revoke.isPending ? 'opacity-100 transition-opacity' : 'opacity-0 transition-opacity group-hover/row:opacity-100 focus-visible:opacity-100',
|
|
329
|
+
children: /*#__PURE__*/ _jsx(Icon, {
|
|
330
|
+
name: "closeLine",
|
|
331
|
+
className: "size-16"
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
}),
|
|
335
|
+
/*#__PURE__*/ _jsxs(ModalContent, {
|
|
336
|
+
children: [
|
|
337
|
+
/*#__PURE__*/ _jsx(ModalTitle, {
|
|
338
|
+
className: "sr-only",
|
|
339
|
+
children: "Revoke invitation"
|
|
340
|
+
}),
|
|
341
|
+
/*#__PURE__*/ _jsx(ModalHeader, {
|
|
342
|
+
children: /*#__PURE__*/ _jsxs(Text, {
|
|
343
|
+
size: "lg",
|
|
344
|
+
children: [
|
|
345
|
+
"Revoke invitation to ",
|
|
346
|
+
invitation.email,
|
|
347
|
+
"?"
|
|
348
|
+
]
|
|
349
|
+
})
|
|
350
|
+
}),
|
|
351
|
+
/*#__PURE__*/ _jsx(ModalBody, {
|
|
352
|
+
children: /*#__PURE__*/ _jsx(Text, {
|
|
353
|
+
size: "sm",
|
|
354
|
+
children: "They will no longer be able to use the link from their email."
|
|
355
|
+
})
|
|
356
|
+
}),
|
|
357
|
+
/*#__PURE__*/ _jsxs(ModalFooter, {
|
|
358
|
+
children: [
|
|
359
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
360
|
+
variant: "secondary",
|
|
361
|
+
onClick: ()=>setOpen(false),
|
|
362
|
+
children: "Cancel"
|
|
363
|
+
}),
|
|
364
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
365
|
+
variant: "danger",
|
|
366
|
+
onClick: handleRevoke,
|
|
367
|
+
isLoading: revoke.isPending,
|
|
368
|
+
children: "Revoke"
|
|
369
|
+
})
|
|
370
|
+
]
|
|
371
|
+
})
|
|
372
|
+
]
|
|
373
|
+
})
|
|
374
|
+
]
|
|
375
|
+
})
|
|
376
|
+
})
|
|
377
|
+
]
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function InviteMemberModal({ open, onOpenChange, workspaceId, workspaceName }) {
|
|
381
|
+
const create = useCreateInvitation(workspaceId);
|
|
382
|
+
const [formError, setFormError] = useState();
|
|
383
|
+
const form = useForm({
|
|
384
|
+
defaultValues: {
|
|
385
|
+
email: ''
|
|
386
|
+
},
|
|
387
|
+
onSubmit: async ({ value })=>{
|
|
388
|
+
setFormError(undefined);
|
|
389
|
+
try {
|
|
390
|
+
const result = await create.mutateAsync({
|
|
391
|
+
email: value.email
|
|
392
|
+
});
|
|
393
|
+
toast.success(`Invitation sent to ${result.email}.`);
|
|
394
|
+
// Go through handleOpenChange so form.reset() fires and reopening the
|
|
395
|
+
// modal does not show stale form state.
|
|
396
|
+
handleOpenChange(false);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
const mapped = invitationErrorToFormError(error);
|
|
399
|
+
if (mapped.kind === 'field') {
|
|
400
|
+
form.setFieldMeta(mapped.field, (prev)=>({
|
|
401
|
+
...prev,
|
|
402
|
+
errorMap: {
|
|
403
|
+
...prev.errorMap,
|
|
404
|
+
onServer: mapped.message
|
|
405
|
+
}
|
|
406
|
+
}));
|
|
407
|
+
} else {
|
|
408
|
+
setFormError(mapped.message);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
function handleOpenChange(nextOpen) {
|
|
414
|
+
if (!nextOpen) {
|
|
415
|
+
form.reset();
|
|
416
|
+
setFormError(undefined);
|
|
417
|
+
create.reset();
|
|
418
|
+
}
|
|
419
|
+
onOpenChange(nextOpen);
|
|
420
|
+
}
|
|
421
|
+
return /*#__PURE__*/ _jsxs(Modal, {
|
|
422
|
+
open: open,
|
|
423
|
+
onOpenChange: handleOpenChange,
|
|
424
|
+
children: [
|
|
425
|
+
/*#__PURE__*/ _jsx(ModalTrigger, {
|
|
426
|
+
asChild: true,
|
|
427
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
428
|
+
size: "sm",
|
|
429
|
+
variant: "secondary",
|
|
430
|
+
children: "Invite member"
|
|
431
|
+
})
|
|
432
|
+
}),
|
|
433
|
+
/*#__PURE__*/ _jsxs(ModalContent, {
|
|
434
|
+
children: [
|
|
435
|
+
/*#__PURE__*/ _jsx(ModalTitle, {
|
|
436
|
+
className: "sr-only",
|
|
437
|
+
children: "Invite a member"
|
|
438
|
+
}),
|
|
439
|
+
/*#__PURE__*/ _jsx(ModalHeader, {
|
|
440
|
+
children: /*#__PURE__*/ _jsx(Text, {
|
|
441
|
+
size: "lg",
|
|
442
|
+
children: "Invite a member"
|
|
443
|
+
})
|
|
444
|
+
}),
|
|
445
|
+
/*#__PURE__*/ _jsxs(ModalBody, {
|
|
446
|
+
className: "gap-16",
|
|
447
|
+
children: [
|
|
448
|
+
formError ? /*#__PURE__*/ _jsx(Callout, {
|
|
449
|
+
role: "alert",
|
|
450
|
+
type: "error",
|
|
451
|
+
children: formError
|
|
452
|
+
}) : null,
|
|
453
|
+
/*#__PURE__*/ _jsx("form", {
|
|
454
|
+
id: "invite-member-form",
|
|
455
|
+
className: "flex flex-col gap-16",
|
|
456
|
+
noValidate: true,
|
|
457
|
+
onSubmit: (event)=>{
|
|
458
|
+
event.preventDefault();
|
|
459
|
+
event.stopPropagation();
|
|
460
|
+
void form.handleSubmit();
|
|
461
|
+
},
|
|
462
|
+
children: /*#__PURE__*/ _jsx(form.Field, {
|
|
463
|
+
name: "email",
|
|
464
|
+
validators: {
|
|
465
|
+
onBlur: createInvitationBodySchema.shape.email,
|
|
466
|
+
onSubmit: createInvitationBodySchema.shape.email
|
|
467
|
+
},
|
|
468
|
+
children: (field)=>/*#__PURE__*/ _jsx(FormField, {
|
|
469
|
+
label: "Email",
|
|
470
|
+
id: "invite-email",
|
|
471
|
+
error: fieldError(field),
|
|
472
|
+
description: `They'll receive an email with a link to join ${workspaceName}.`,
|
|
473
|
+
children: /*#__PURE__*/ _jsx(FormFieldInput, {
|
|
474
|
+
autoComplete: "email",
|
|
475
|
+
autoFocus: true,
|
|
476
|
+
name: "email",
|
|
477
|
+
type: "email",
|
|
478
|
+
value: field.state.value,
|
|
479
|
+
onChange: (event)=>field.handleChange(event.target.value),
|
|
480
|
+
onBlur: field.handleBlur
|
|
481
|
+
})
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
})
|
|
485
|
+
]
|
|
486
|
+
}),
|
|
487
|
+
/*#__PURE__*/ _jsxs(ModalFooter, {
|
|
488
|
+
children: [
|
|
489
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
490
|
+
variant: "secondary",
|
|
491
|
+
onClick: ()=>handleOpenChange(false),
|
|
492
|
+
children: "Cancel"
|
|
493
|
+
}),
|
|
494
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
495
|
+
type: "submit",
|
|
496
|
+
form: "invite-member-form",
|
|
497
|
+
isLoading: create.isPending,
|
|
498
|
+
children: "Send invitation"
|
|
499
|
+
})
|
|
500
|
+
]
|
|
501
|
+
})
|
|
502
|
+
]
|
|
503
|
+
})
|
|
504
|
+
]
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
function EmptyInvitations() {
|
|
508
|
+
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
509
|
+
icon: "mailLine",
|
|
510
|
+
title: "No pending invitations.",
|
|
511
|
+
description: "Invite someone above to grow your workspace."
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
function TableSkeleton({ rows, cols }) {
|
|
515
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
516
|
+
className: "flex flex-col gap-12",
|
|
517
|
+
children: Array.from({
|
|
518
|
+
length: rows
|
|
519
|
+
}).map((_, rowIdx)=>// biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder rows
|
|
520
|
+
/*#__PURE__*/ _jsx("div", {
|
|
521
|
+
className: "grid grid-cols-3 gap-16",
|
|
522
|
+
children: Array.from({
|
|
523
|
+
length: cols
|
|
524
|
+
}).map((__, colIdx)=>// biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder cells
|
|
525
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
526
|
+
className: "h-20"
|
|
527
|
+
}, colIdx))
|
|
528
|
+
}, rowIdx))
|
|
529
|
+
});
|
|
530
|
+
}
|
|
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
|
+
|
|
541
|
+
//# sourceMappingURL=workspace-members-section.js.map
|
|
@@ -0,0 +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 {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?.invitations ?? [];\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: import('@shipfox/api-workspaces-dto').InvitationDto;\n workspaceId: string;\n}) {\n const [open, setOpen] = useState(false);\n const revoke = useRevokeInvitation(workspaceId);\n const expiresAt = new Date(invitation.expires_at);\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.invited_by_display ?? '—'}</TableCell>\n <TableCell>\n <div className=\"flex items-center gap-8\">\n <Text size=\"sm\">{formatDate(invitation.expires_at)}</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","expires_at","expiresSoon","getTime","now","handleRevoke","email","invited_by_display","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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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useActiveWorkspace } from '@shipfox/client-shell/runtime';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
interface WorkspaceSettingsShellProps {
|
|
4
|
+
children: (workspace: ReturnType<typeof useActiveWorkspace>) => ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export declare function WorkspaceSettingsShell({ children }: WorkspaceSettingsShellProps): ReactNode;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=workspace-settings-shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-settings-shell.d.ts","sourceRoot":"","sources":["../../src/components/workspace-settings-shell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,kBAAkB,EAAC,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,UAAU,2BAA2B;IACnC,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,KAAK,SAAS,CAAC;CAC3E;AAED,wBAAgB,sBAAsB,CAAC,EAAC,QAAQ,EAAC,EAAE,2BAA2B,aAI7E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/workspace-settings-shell.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-shell/runtime';\nimport type {ReactNode} from 'react';\n\ninterface WorkspaceSettingsShellProps {\n children: (workspace: ReturnType<typeof useActiveWorkspace>) => ReactNode;\n}\n\nexport function WorkspaceSettingsShell({children}: WorkspaceSettingsShellProps) {\n const workspace = useActiveWorkspace();\n\n return children(workspace);\n}\n"],"names":["useActiveWorkspace","WorkspaceSettingsShell","children","workspace"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,gCAAgC;AAOjE,OAAO,SAASC,uBAAuB,EAACC,QAAQ,EAA8B;IAC5E,MAAMC,YAAYH;IAElB,OAAOE,SAASC;AAClB"}
|