@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.
Files changed (154) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +197 -0
  5. package/LICENSE +21 -0
  6. package/dist/components/members/form-errors.d.ts +11 -0
  7. package/dist/components/members/form-errors.d.ts.map +1 -0
  8. package/dist/components/members/form-errors.js +28 -0
  9. package/dist/components/members/form-errors.js.map +1 -0
  10. package/dist/components/members/workspace-members-section.d.ts +5 -0
  11. package/dist/components/members/workspace-members-section.d.ts.map +1 -0
  12. package/dist/components/members/workspace-members-section.js +541 -0
  13. package/dist/components/members/workspace-members-section.js.map +1 -0
  14. package/dist/components/workspace-settings-shell.d.ts +8 -0
  15. package/dist/components/workspace-settings-shell.d.ts.map +1 -0
  16. package/dist/components/workspace-settings-shell.js +7 -0
  17. package/dist/components/workspace-settings-shell.js.map +1 -0
  18. package/dist/feature.d.ts +98 -0
  19. package/dist/feature.d.ts.map +1 -0
  20. package/dist/feature.js +120 -0
  21. package/dist/feature.js.map +1 -0
  22. package/dist/hooks/api/create-invitation.d.ts +14 -0
  23. package/dist/hooks/api/create-invitation.d.ts.map +1 -0
  24. package/dist/hooks/api/create-invitation.js +25 -0
  25. package/dist/hooks/api/create-invitation.js.map +1 -0
  26. package/dist/hooks/api/list-invitations.d.ts +15 -0
  27. package/dist/hooks/api/list-invitations.d.ts.map +1 -0
  28. package/dist/hooks/api/list-invitations.js +18 -0
  29. package/dist/hooks/api/list-invitations.js.map +1 -0
  30. package/dist/hooks/api/list-members.d.ts +13 -0
  31. package/dist/hooks/api/list-members.d.ts.map +1 -0
  32. package/dist/hooks/api/list-members.js +18 -0
  33. package/dist/hooks/api/list-members.js.map +1 -0
  34. package/dist/hooks/api/remove-member.d.ts +2 -0
  35. package/dist/hooks/api/remove-member.d.ts.map +1 -0
  36. package/dist/hooks/api/remove-member.js +24 -0
  37. package/dist/hooks/api/remove-member.js.map +1 -0
  38. package/dist/hooks/api/revoke-invitation.d.ts +2 -0
  39. package/dist/hooks/api/revoke-invitation.d.ts.map +1 -0
  40. package/dist/hooks/api/revoke-invitation.js +24 -0
  41. package/dist/hooks/api/revoke-invitation.js.map +1 -0
  42. package/dist/index.d.ts +9 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +10 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/pages/events-settings-page.d.ts +2 -0
  47. package/dist/pages/events-settings-page.d.ts.map +1 -0
  48. package/dist/pages/events-settings-page.js +30 -0
  49. package/dist/pages/events-settings-page.js.map +1 -0
  50. package/dist/pages/integrations-settings-page.d.ts +2 -0
  51. package/dist/pages/integrations-settings-page.d.ts.map +1 -0
  52. package/dist/pages/integrations-settings-page.js +10 -0
  53. package/dist/pages/integrations-settings-page.js.map +1 -0
  54. package/dist/pages/members-settings-page.d.ts +2 -0
  55. package/dist/pages/members-settings-page.d.ts.map +1 -0
  56. package/dist/pages/members-settings-page.js +15 -0
  57. package/dist/pages/members-settings-page.js.map +1 -0
  58. package/dist/pages/model-providers-settings-page.d.ts +2 -0
  59. package/dist/pages/model-providers-settings-page.d.ts.map +1 -0
  60. package/dist/pages/model-providers-settings-page.js +20 -0
  61. package/dist/pages/model-providers-settings-page.js.map +1 -0
  62. package/dist/pages/provisioners-settings-page.d.ts +2 -0
  63. package/dist/pages/provisioners-settings-page.d.ts.map +1 -0
  64. package/dist/pages/provisioners-settings-page.js +14 -0
  65. package/dist/pages/provisioners-settings-page.js.map +1 -0
  66. package/dist/pages/runners-settings-page.d.ts +2 -0
  67. package/dist/pages/runners-settings-page.d.ts.map +1 -0
  68. package/dist/pages/runners-settings-page.js +14 -0
  69. package/dist/pages/runners-settings-page.js.map +1 -0
  70. package/dist/pages/secrets-settings-page.d.ts +2 -0
  71. package/dist/pages/secrets-settings-page.d.ts.map +1 -0
  72. package/dist/pages/secrets-settings-page.js +14 -0
  73. package/dist/pages/secrets-settings-page.js.map +1 -0
  74. package/dist/pages/variables-settings-page.d.ts +2 -0
  75. package/dist/pages/variables-settings-page.d.ts.map +1 -0
  76. package/dist/pages/variables-settings-page.js +14 -0
  77. package/dist/pages/variables-settings-page.js.map +1 -0
  78. package/dist/routes/agents.d.ts +6 -0
  79. package/dist/routes/agents.d.ts.map +1 -0
  80. package/dist/routes/agents.js +7 -0
  81. package/dist/routes/agents.js.map +1 -0
  82. package/dist/routes/events.d.ts +8 -0
  83. package/dist/routes/events.d.ts.map +1 -0
  84. package/dist/routes/events.js +9 -0
  85. package/dist/routes/events.js.map +1 -0
  86. package/dist/routes/index.d.ts +9 -0
  87. package/dist/routes/index.d.ts.map +1 -0
  88. package/dist/routes/index.js +14 -0
  89. package/dist/routes/index.js.map +1 -0
  90. package/dist/routes/integrations.d.ts +6 -0
  91. package/dist/routes/integrations.d.ts.map +1 -0
  92. package/dist/routes/integrations.js +7 -0
  93. package/dist/routes/integrations.js.map +1 -0
  94. package/dist/routes/members.d.ts +6 -0
  95. package/dist/routes/members.d.ts.map +1 -0
  96. package/dist/routes/members.js +7 -0
  97. package/dist/routes/members.js.map +1 -0
  98. package/dist/routes/provisioners.d.ts +6 -0
  99. package/dist/routes/provisioners.d.ts.map +1 -0
  100. package/dist/routes/provisioners.js +7 -0
  101. package/dist/routes/provisioners.js.map +1 -0
  102. package/dist/routes/runners.d.ts +6 -0
  103. package/dist/routes/runners.d.ts.map +1 -0
  104. package/dist/routes/runners.js +7 -0
  105. package/dist/routes/runners.js.map +1 -0
  106. package/dist/routes/secrets.d.ts +6 -0
  107. package/dist/routes/secrets.d.ts.map +1 -0
  108. package/dist/routes/secrets.js +7 -0
  109. package/dist/routes/secrets.js.map +1 -0
  110. package/dist/routes/variables.d.ts +6 -0
  111. package/dist/routes/variables.d.ts.map +1 -0
  112. package/dist/routes/variables.js +7 -0
  113. package/dist/routes/variables.js.map +1 -0
  114. package/dist/tsconfig.test.tsbuildinfo +1 -0
  115. package/package.json +104 -0
  116. package/src/components/members/form-errors.test.ts +40 -0
  117. package/src/components/members/form-errors.ts +24 -0
  118. package/src/components/members/workspace-members-section.tsx +476 -0
  119. package/src/components/workspace-settings-shell.tsx +12 -0
  120. package/src/feature.ts +120 -0
  121. package/src/hooks/api/create-invitation.ts +21 -0
  122. package/src/hooks/api/list-invitations.ts +17 -0
  123. package/src/hooks/api/list-members.ts +17 -0
  124. package/src/hooks/api/remove-member.ts +19 -0
  125. package/src/hooks/api/revoke-invitation.ts +19 -0
  126. package/src/index.ts +8 -0
  127. package/src/pages/events-settings-page.tsx +25 -0
  128. package/src/pages/integrations-settings-page.test.tsx +72 -0
  129. package/src/pages/integrations-settings-page.tsx +6 -0
  130. package/src/pages/members-settings-page.tsx +17 -0
  131. package/src/pages/model-providers-settings-page.test.tsx +48 -0
  132. package/src/pages/model-providers-settings-page.tsx +15 -0
  133. package/src/pages/provisioners-settings-page.test.tsx +29 -0
  134. package/src/pages/provisioners-settings-page.tsx +14 -0
  135. package/src/pages/runners-settings-page.test.tsx +23 -0
  136. package/src/pages/runners-settings-page.tsx +14 -0
  137. package/src/pages/secrets-settings-page.tsx +14 -0
  138. package/src/pages/variables-settings-page.tsx +14 -0
  139. package/src/routes/agents.tsx +3 -0
  140. package/src/routes/events.tsx +7 -0
  141. package/src/routes/index.tsx +8 -0
  142. package/src/routes/integrations.tsx +3 -0
  143. package/src/routes/members.tsx +3 -0
  144. package/src/routes/provisioners.tsx +3 -0
  145. package/src/routes/runners.tsx +3 -0
  146. package/src/routes/secrets.tsx +3 -0
  147. package/src/routes/variables.tsx +3 -0
  148. package/test/pages.tsx +101 -0
  149. package/test/setup.ts +3 -0
  150. package/tsconfig.build.json +9 -0
  151. package/tsconfig.build.tsbuildinfo +1 -0
  152. package/tsconfig.json +3 -0
  153. package/tsconfig.test.json +8 -0
  154. 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,7 @@
1
+ import { useActiveWorkspace } from '@shipfox/client-shell/runtime';
2
+ export function WorkspaceSettingsShell({ children }) {
3
+ const workspace = useActiveWorkspace();
4
+ return children(workspace);
5
+ }
6
+
7
+ //# sourceMappingURL=workspace-settings-shell.js.map
@@ -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"}