@shipfox/client-workspace-settings 14.0.0 → 16.0.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 +1 -1
- package/CHANGELOG.md +26 -0
- package/dist/components/members/workspace-members-section.js +11 -11
- package/dist/components/members/workspace-members-section.js.map +1 -1
- package/dist/components/workspace-settings-shell.d.ts +1 -1
- package/dist/feature.d.ts +24 -24
- package/dist/feature.d.ts.map +1 -1
- package/dist/hooks/api/list-invitations.d.ts +1 -1
- package/dist/hooks/api/list-invitations.d.ts.map +1 -1
- package/dist/hooks/api/list-members.d.ts +1 -1
- package/dist/hooks/api/list-members.d.ts.map +1 -1
- package/dist/pages/general-settings-page.js +3 -3
- package/dist/pages/general-settings-page.js.map +1 -1
- package/dist/routes/general.d.ts.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/members.d.ts.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/components/members/workspace-members-section.tsx +11 -11
- package/src/pages/general-settings-page.tsx +3 -3
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled: 19 files with swc (
|
|
2
|
+
Successfully compiled: 19 files with swc (599.75ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @shipfox/client-workspace-settings
|
|
2
2
|
|
|
3
|
+
## 16.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [80cde6b]
|
|
8
|
+
- @shipfox/react-ui@1.1.0
|
|
9
|
+
- @shipfox/client-ui@16.0.0
|
|
10
|
+
- @shipfox/client-auth@16.0.0
|
|
11
|
+
- @shipfox/client-shell@16.0.0
|
|
12
|
+
|
|
13
|
+
## 14.0.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 88bf8e8: Migrates the shell, auth, secrets, logs, workspace-settings, config, and invitations
|
|
18
|
+
surfaces to semantic spacing roles and brings them under the `no-raw-spacing` Biome
|
|
19
|
+
plugin. Adds shared menu-surface and edge-specific panel roles for existing spacing
|
|
20
|
+
contracts.
|
|
21
|
+
- Updated dependencies [88bf8e8]
|
|
22
|
+
- Updated dependencies [6aa6c7a]
|
|
23
|
+
- Updated dependencies [5c56ba6]
|
|
24
|
+
- @shipfox/react-ui@1.0.0
|
|
25
|
+
- @shipfox/client-shell@14.0.1
|
|
26
|
+
- @shipfox/client-auth@14.0.1
|
|
27
|
+
- @shipfox/client-ui@14.0.1
|
|
28
|
+
|
|
3
29
|
## 14.0.0
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
|
@@ -25,7 +25,7 @@ import { useRevokeInvitation } from '#hooks/api/revoke-invitation.js';
|
|
|
25
25
|
import { invitationErrorToFormError, memberRemovalErrorMessage } from './form-errors.js';
|
|
26
26
|
export function WorkspaceMembersSettingsSection({ workspaceId, workspaceName }) {
|
|
27
27
|
return /*#__PURE__*/ _jsxs("div", {
|
|
28
|
-
className: "flex min-w-0 flex-col gap-
|
|
28
|
+
className: "flex min-w-0 flex-col gap-region",
|
|
29
29
|
children: [
|
|
30
30
|
/*#__PURE__*/ _jsx(MembersSection, {
|
|
31
31
|
workspaceId: workspaceId,
|
|
@@ -43,10 +43,10 @@ function MembersSection({ workspaceId, workspaceName }) {
|
|
|
43
43
|
const query = useListMembers(workspaceId);
|
|
44
44
|
const members = query.data ?? [];
|
|
45
45
|
return /*#__PURE__*/ _jsxs("section", {
|
|
46
|
-
className: "flex flex-col gap-
|
|
46
|
+
className: "flex flex-col gap-group",
|
|
47
47
|
children: [
|
|
48
48
|
/*#__PURE__*/ _jsxs("div", {
|
|
49
|
-
className: "flex flex-col gap-
|
|
49
|
+
className: "flex flex-col gap-tight",
|
|
50
50
|
children: [
|
|
51
51
|
/*#__PURE__*/ _jsx(Header, {
|
|
52
52
|
variant: "h3",
|
|
@@ -210,13 +210,13 @@ function PendingInvitationsSection({ workspaceId, workspaceName }) {
|
|
|
210
210
|
const invitations = query.data ?? [];
|
|
211
211
|
const [inviteOpen, setInviteOpen] = useState(false);
|
|
212
212
|
return /*#__PURE__*/ _jsxs("section", {
|
|
213
|
-
className: "flex flex-col gap-
|
|
213
|
+
className: "flex flex-col gap-group",
|
|
214
214
|
children: [
|
|
215
215
|
/*#__PURE__*/ _jsxs("div", {
|
|
216
|
-
className: "flex items-center justify-between gap-
|
|
216
|
+
className: "flex items-center justify-between gap-group",
|
|
217
217
|
children: [
|
|
218
218
|
/*#__PURE__*/ _jsxs("div", {
|
|
219
|
-
className: "flex flex-col gap-
|
|
219
|
+
className: "flex flex-col gap-tight",
|
|
220
220
|
children: [
|
|
221
221
|
/*#__PURE__*/ _jsx(Header, {
|
|
222
222
|
variant: "h3",
|
|
@@ -307,7 +307,7 @@ function InvitationRow({ invitation, workspaceId }) {
|
|
|
307
307
|
}),
|
|
308
308
|
/*#__PURE__*/ _jsx(TableCell, {
|
|
309
309
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
310
|
-
className: "flex items-center gap-
|
|
310
|
+
className: "flex items-center gap-inline",
|
|
311
311
|
children: [
|
|
312
312
|
/*#__PURE__*/ _jsx(Text, {
|
|
313
313
|
size: "sm",
|
|
@@ -455,7 +455,7 @@ function InviteMemberModal({ open, onOpenChange, workspaceId, workspaceName }) {
|
|
|
455
455
|
})
|
|
456
456
|
}),
|
|
457
457
|
/*#__PURE__*/ _jsxs(ModalBody, {
|
|
458
|
-
className: "gap-
|
|
458
|
+
className: "gap-group",
|
|
459
459
|
children: [
|
|
460
460
|
formError ? /*#__PURE__*/ _jsx(Callout, {
|
|
461
461
|
role: "alert",
|
|
@@ -464,7 +464,7 @@ function InviteMemberModal({ open, onOpenChange, workspaceId, workspaceName }) {
|
|
|
464
464
|
}) : null,
|
|
465
465
|
/*#__PURE__*/ _jsx("form", {
|
|
466
466
|
id: "invite-member-form",
|
|
467
|
-
className: "flex flex-col gap-
|
|
467
|
+
className: "flex flex-col gap-group",
|
|
468
468
|
noValidate: true,
|
|
469
469
|
onSubmit: (event)=>{
|
|
470
470
|
event.preventDefault();
|
|
@@ -525,12 +525,12 @@ function EmptyInvitations() {
|
|
|
525
525
|
}
|
|
526
526
|
function TableSkeleton({ rows, cols }) {
|
|
527
527
|
return /*#__PURE__*/ _jsx("div", {
|
|
528
|
-
className: "flex flex-col gap-
|
|
528
|
+
className: "flex flex-col gap-cluster",
|
|
529
529
|
children: Array.from({
|
|
530
530
|
length: rows
|
|
531
531
|
}).map((_, rowIdx)=>// biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder rows
|
|
532
532
|
/*#__PURE__*/ _jsx("div", {
|
|
533
|
-
className: "grid grid-cols-3 gap-
|
|
533
|
+
className: "grid grid-cols-3 gap-group",
|
|
534
534
|
children: Array.from({
|
|
535
535
|
length: cols
|
|
536
536
|
}).map((__, colIdx)=>// biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder cells
|
|
@@ -1 +1 @@
|
|
|
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 ?? 'N/A'}</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 ?? 'N/A'}</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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"}
|
|
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-region\">\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-group\">\n <div className=\"flex flex-col gap-tight\">\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 ?? 'N/A'}</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-group\">\n <div className=\"flex items-center justify-between gap-group\">\n <div className=\"flex flex-col gap-tight\">\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 ?? 'N/A'}</TableCell>\n <TableCell>\n <div className=\"flex items-center gap-inline\">\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-group\">\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-group\"\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-cluster\">\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-group\">\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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"}
|
|
@@ -3,6 +3,6 @@ import type { ReactNode } from 'react';
|
|
|
3
3
|
interface WorkspaceSettingsShellProps {
|
|
4
4
|
children: (workspace: NonNullable<ReturnType<typeof useMaybeActiveWorkspace>>) => ReactNode;
|
|
5
5
|
}
|
|
6
|
-
export declare function WorkspaceSettingsShell({ children }: WorkspaceSettingsShellProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean |
|
|
6
|
+
export declare function WorkspaceSettingsShell({ children }: WorkspaceSettingsShellProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
|
|
7
7
|
export {};
|
|
8
8
|
//# sourceMappingURL=workspace-settings-shell.d.ts.map
|
package/dist/feature.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
export declare const workspaceSettingsNavigation: readonly [{
|
|
2
|
-
readonly id:
|
|
3
|
-
readonly scope:
|
|
4
|
-
readonly label:
|
|
5
|
-
readonly to:
|
|
2
|
+
readonly id: 'nav.settings';
|
|
3
|
+
readonly scope: 'workspace';
|
|
4
|
+
readonly label: 'Settings';
|
|
5
|
+
readonly to: '/w/$workspaceSlug/settings';
|
|
6
6
|
readonly order: 200;
|
|
7
7
|
}];
|
|
8
8
|
export declare const workspaceSettingsSections: readonly [{
|
|
9
|
-
readonly id:
|
|
10
|
-
readonly pathSegment:
|
|
11
|
-
readonly label:
|
|
12
|
-
readonly icon:
|
|
9
|
+
readonly id: 'settings.general';
|
|
10
|
+
readonly pathSegment: 'general';
|
|
11
|
+
readonly label: 'General';
|
|
12
|
+
readonly icon: 'settings3Line';
|
|
13
13
|
readonly order: 50;
|
|
14
14
|
}, {
|
|
15
|
-
readonly id:
|
|
16
|
-
readonly pathSegment:
|
|
17
|
-
readonly label:
|
|
18
|
-
readonly icon:
|
|
15
|
+
readonly id: 'settings.members';
|
|
16
|
+
readonly pathSegment: 'members';
|
|
17
|
+
readonly label: 'Members';
|
|
18
|
+
readonly icon: 'userLine';
|
|
19
19
|
readonly order: 100;
|
|
20
20
|
}];
|
|
21
21
|
export declare const workspaceSettingsFeature: {
|
|
@@ -34,23 +34,23 @@ export declare const workspaceSettingsFeature: {
|
|
|
34
34
|
readonly impl: "@shipfox/client-workspace-settings/routes/general";
|
|
35
35
|
}];
|
|
36
36
|
readonly navigation: readonly [{
|
|
37
|
-
readonly id:
|
|
38
|
-
readonly scope:
|
|
39
|
-
readonly label:
|
|
40
|
-
readonly to:
|
|
37
|
+
readonly id: 'nav.settings';
|
|
38
|
+
readonly scope: 'workspace';
|
|
39
|
+
readonly label: 'Settings';
|
|
40
|
+
readonly to: '/w/$workspaceSlug/settings';
|
|
41
41
|
readonly order: 200;
|
|
42
42
|
}];
|
|
43
43
|
readonly settingsSections: readonly [{
|
|
44
|
-
readonly id:
|
|
45
|
-
readonly pathSegment:
|
|
46
|
-
readonly label:
|
|
47
|
-
readonly icon:
|
|
44
|
+
readonly id: 'settings.general';
|
|
45
|
+
readonly pathSegment: 'general';
|
|
46
|
+
readonly label: 'General';
|
|
47
|
+
readonly icon: 'settings3Line';
|
|
48
48
|
readonly order: 50;
|
|
49
49
|
}, {
|
|
50
|
-
readonly id:
|
|
51
|
-
readonly pathSegment:
|
|
52
|
-
readonly label:
|
|
53
|
-
readonly icon:
|
|
50
|
+
readonly id: 'settings.members';
|
|
51
|
+
readonly pathSegment: 'members';
|
|
52
|
+
readonly label: 'Members';
|
|
53
|
+
readonly icon: 'userLine';
|
|
54
54
|
readonly order: 100;
|
|
55
55
|
}];
|
|
56
56
|
};
|
package/dist/feature.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,2BAA2B
|
|
1
|
+
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,2BAA2B;iBAEhC,cAAc;oBACX,WAAW;oBACX,UAAU;iBACb,4BAA4B;oBACzB,GAAG;EAE6B,CAAC;AAE5C,eAAO,MAAM,yBAAyB;iBAE9B,kBAAkB;0BACT,SAAS;oBACf,SAAS;mBACV,eAAe;oBACd,EAAE;;iBAGL,kBAAkB;0BACT,SAAS;oBACf,SAAS;mBACV,UAAU;oBACT,GAAG;EAEsC,CAAC;AAErD,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;qBAzB7B,cAAc;wBACX,WAAW;wBACX,UAAU;qBACb,4BAA4B;wBACzB,GAAG;;;qBAMN,kBAAkB;8BACT,SAAS;wBACf,SAAS;uBACV,eAAe;wBACd,EAAE;;qBAGL,kBAAkB;8BACT,SAAS;wBACf,SAAS;uBACV,UAAU;wBACT,GAAG;;CAyBZ,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PendingInvitation } from '#core/membership.js';
|
|
2
|
-
export declare const listInvitationsQueryKey: (workspaceId: string) => readonly [
|
|
2
|
+
export declare const listInvitationsQueryKey: (workspaceId: string) => readonly ['workspaces', string, 'invitations'];
|
|
3
3
|
export declare function listInvitationsQueryOptions(workspaceId: string): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<PendingInvitation[], Error, PendingInvitation[], readonly ["workspaces", string, "invitations"]>, "queryFn"> & {
|
|
4
4
|
queryFn?: import("@tanstack/react-query").QueryFunction<PendingInvitation[], readonly ["workspaces", string, "invitations"], never>;
|
|
5
5
|
} & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-invitations.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/list-invitations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAG3D,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"list-invitations.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/list-invitations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAG3D,eAAO,MAAM,uBAAuB,gBAAiB,MAAM,eACxD,YAAY,UAAe,aAAa,CAAU,CAAC;AAUtD,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM;;;;;;;EAK9D;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,uFAErD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WorkspaceMember } from '#core/membership.js';
|
|
2
|
-
export declare const listMembersQueryKey: (workspaceId: string) => readonly [
|
|
2
|
+
export declare const listMembersQueryKey: (workspaceId: string) => readonly ['workspaces', string, 'members'];
|
|
3
3
|
export declare function listMembersQueryOptions(workspaceId: string): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<WorkspaceMember[], Error, WorkspaceMember[], readonly ["workspaces", string, "members"]>, "queryFn"> & {
|
|
4
4
|
queryFn?: import("@tanstack/react-query").QueryFunction<WorkspaceMember[], readonly ["workspaces", string, "members"], never>;
|
|
5
5
|
} & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-members.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/list-members.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,qBAAqB,CAAC;AAGzD,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"list-members.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/list-members.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,qBAAqB,CAAC;AAGzD,eAAO,MAAM,mBAAmB,gBAAiB,MAAM,eACpD,YAAY,UAAe,SAAS,CAAU,CAAC;AAUlD,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM;;;;;;;EAK1D;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,qFAEjD"}
|
|
@@ -87,10 +87,10 @@ function WorkspaceGeneralForm({ workspace }) {
|
|
|
87
87
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
88
88
|
children: [
|
|
89
89
|
/*#__PURE__*/ _jsxs("div", {
|
|
90
|
-
className: "flex min-w-0 flex-col gap-
|
|
90
|
+
className: "flex min-w-0 flex-col gap-section",
|
|
91
91
|
children: [
|
|
92
92
|
/*#__PURE__*/ _jsxs("header", {
|
|
93
|
-
className: "flex flex-col gap-
|
|
93
|
+
className: "flex flex-col gap-inline",
|
|
94
94
|
children: [
|
|
95
95
|
/*#__PURE__*/ _jsx(Header, {
|
|
96
96
|
variant: "h1",
|
|
@@ -109,7 +109,7 @@ function WorkspaceGeneralForm({ workspace }) {
|
|
|
109
109
|
children: formError
|
|
110
110
|
}) : null,
|
|
111
111
|
/*#__PURE__*/ _jsxs("form", {
|
|
112
|
-
className: "flex max-w-[560px] flex-col gap-
|
|
112
|
+
className: "flex max-w-[560px] flex-col gap-group",
|
|
113
113
|
noValidate: true,
|
|
114
114
|
onSubmit: (event)=>{
|
|
115
115
|
event.preventDefault();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pages/general-settings-page.tsx"],"sourcesContent":["import {createWorkspaceBodySchema} from '@shipfox/api-workspaces-dto';\nimport {\n checkWorkspaceSlugAvailability,\n type useActiveWorkspace,\n useUpdateWorkspaceMutation,\n} from '@shipfox/client-auth';\nimport {displayNameFieldError, SlugChangeWarning, SlugField} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {workspaceGeneralErrorToFormError} from '#components/general/form-errors.js';\nimport {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';\n\ninterface WorkspaceGeneralValues {\n name: string;\n slug: string;\n}\n\nfunction isSlugValid(value: string): boolean {\n return createWorkspaceBodySchema.shape.slug.safeParse(value).success;\n}\n\nexport function GeneralSettingsPage() {\n return (\n <WorkspaceSettingsShell>\n {(workspace) => (\n <WorkspaceGeneralForm\n key={`${workspace.id}:${workspace.name}:${workspace.slug}`}\n workspace={workspace}\n />\n )}\n </WorkspaceSettingsShell>\n );\n}\n\nfunction WorkspaceGeneralForm({workspace}: {workspace: ReturnType<typeof useActiveWorkspace>}) {\n const updateWorkspace = useUpdateWorkspaceMutation();\n const navigate = useNavigate();\n const [warningOpen, setWarningOpen] = useState(false);\n const [pendingValues, setPendingValues] = useState<WorkspaceGeneralValues>();\n const [formError, setFormError] = useState<string>();\n\n const form = useForm({\n defaultValues: {name: workspace.name, slug: workspace.slug},\n onSubmit: async ({value}) => {\n if (value.slug !== workspace.slug) {\n setPendingValues(value);\n setWarningOpen(true);\n return;\n }\n await save(value);\n },\n });\n\n async function save(values: WorkspaceGeneralValues) {\n setFormError(undefined);\n const nameChanged = values.name !== workspace.name;\n const slugChanged = values.slug !== workspace.slug;\n if (!nameChanged && !slugChanged) return;\n const command = {\n workspaceId: workspace.id,\n ...(nameChanged ? {name: values.name} : {}),\n ...(slugChanged ? {slug: values.slug} : {}),\n };\n\n try {\n const updated = await updateWorkspace.mutateAsync(command);\n setPendingValues(undefined);\n setWarningOpen(false);\n toast.success('Workspace settings saved.');\n await navigate({\n to: '/w/$workspaceSlug/settings/general',\n params: {workspaceSlug: updated.slug},\n });\n } catch (error) {\n setPendingValues(undefined);\n setWarningOpen(false);\n const mapped = workspaceGeneralErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (previous) => ({\n ...previous,\n errorMap: {...previous.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n }\n\n return (\n <>\n <div className=\"flex min-w-0 flex-col gap-24\">\n <header className=\"flex flex-col gap-6\">\n <Header variant=\"h1\">General</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Update the workspace name and the slug used in its URLs.\n </Text>\n </header>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <form\n className=\"flex max-w-[560px] 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=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(\n value,\n 'Workspace name',\n createWorkspaceBodySchema.shape.name,\n ),\n onSubmit: ({value}) =>\n displayNameFieldError(\n value,\n 'Workspace name',\n createWorkspaceBodySchema.shape.name,\n ),\n }}\n >\n {(field) => (\n <FormField\n label=\"Workspace name\"\n id=\"workspace-settings-name\"\n error={fieldError(field)}\n >\n <FormFieldInput\n name=\"name\"\n type=\"text\"\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\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createWorkspaceBodySchema.shape.slug,\n onSubmit: createWorkspaceBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"workspace-settings-slug\"\n label=\"Workspace slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => field.handleChange(value)}\n onBlur={field.handleBlur}\n error={fieldError(field)}\n description={<span className=\"break-all font-code\">/w/{field.state.value}</span>}\n placeholder=\"acme\"\n className=\"font-code\"\n currentSlug={workspace.slug}\n checkEnabled\n isValid={isSlugValid}\n checkAvailability={checkWorkspaceSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button type=\"submit\" isLoading={updateWorkspace.isPending} className=\"self-start\">\n Save changes\n </Button>\n </form>\n </div>\n\n <SlugChangeWarning\n open={warningOpen}\n onOpenChange={setWarningOpen}\n entityLabel=\"workspace\"\n isLoading={updateWorkspace.isPending}\n onConfirm={() => {\n if (pendingValues) void save(pendingValues);\n }}\n />\n </>\n );\n}\n"],"names":["createWorkspaceBodySchema","checkWorkspaceSlugAvailability","useUpdateWorkspaceMutation","displayNameFieldError","SlugChangeWarning","SlugField","Button","Callout","FormField","FormFieldInput","fieldError","toast","Header","Text","useForm","useNavigate","useState","workspaceGeneralErrorToFormError","WorkspaceSettingsShell","isSlugValid","value","shape","slug","safeParse","success","GeneralSettingsPage","workspace","WorkspaceGeneralForm","id","name","updateWorkspace","navigate","warningOpen","setWarningOpen","pendingValues","setPendingValues","formError","setFormError","form","defaultValues","onSubmit","save","values","undefined","nameChanged","slugChanged","command","workspaceId","updated","mutateAsync","to","params","workspaceSlug","error","mapped","kind","setFieldMeta","field","previous","errorMap","onServer","message","div","className","header","variant","size","role","type","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","label","state","onChange","handleChange","target","handleBlur","description","span","placeholder","currentSlug","checkEnabled","isValid","checkAvailability","isLoading","isPending","open","onOpenChange","entityLabel","onConfirm"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/pages/general-settings-page.tsx"],"sourcesContent":["import {createWorkspaceBodySchema} from '@shipfox/api-workspaces-dto';\nimport {\n checkWorkspaceSlugAvailability,\n type useActiveWorkspace,\n useUpdateWorkspaceMutation,\n} from '@shipfox/client-auth';\nimport {displayNameFieldError, SlugChangeWarning, SlugField} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {workspaceGeneralErrorToFormError} from '#components/general/form-errors.js';\nimport {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';\n\ninterface WorkspaceGeneralValues {\n name: string;\n slug: string;\n}\n\nfunction isSlugValid(value: string): boolean {\n return createWorkspaceBodySchema.shape.slug.safeParse(value).success;\n}\n\nexport function GeneralSettingsPage() {\n return (\n <WorkspaceSettingsShell>\n {(workspace) => (\n <WorkspaceGeneralForm\n key={`${workspace.id}:${workspace.name}:${workspace.slug}`}\n workspace={workspace}\n />\n )}\n </WorkspaceSettingsShell>\n );\n}\n\nfunction WorkspaceGeneralForm({workspace}: {workspace: ReturnType<typeof useActiveWorkspace>}) {\n const updateWorkspace = useUpdateWorkspaceMutation();\n const navigate = useNavigate();\n const [warningOpen, setWarningOpen] = useState(false);\n const [pendingValues, setPendingValues] = useState<WorkspaceGeneralValues>();\n const [formError, setFormError] = useState<string>();\n\n const form = useForm({\n defaultValues: {name: workspace.name, slug: workspace.slug},\n onSubmit: async ({value}) => {\n if (value.slug !== workspace.slug) {\n setPendingValues(value);\n setWarningOpen(true);\n return;\n }\n await save(value);\n },\n });\n\n async function save(values: WorkspaceGeneralValues) {\n setFormError(undefined);\n const nameChanged = values.name !== workspace.name;\n const slugChanged = values.slug !== workspace.slug;\n if (!nameChanged && !slugChanged) return;\n const command = {\n workspaceId: workspace.id,\n ...(nameChanged ? {name: values.name} : {}),\n ...(slugChanged ? {slug: values.slug} : {}),\n };\n\n try {\n const updated = await updateWorkspace.mutateAsync(command);\n setPendingValues(undefined);\n setWarningOpen(false);\n toast.success('Workspace settings saved.');\n await navigate({\n to: '/w/$workspaceSlug/settings/general',\n params: {workspaceSlug: updated.slug},\n });\n } catch (error) {\n setPendingValues(undefined);\n setWarningOpen(false);\n const mapped = workspaceGeneralErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (previous) => ({\n ...previous,\n errorMap: {...previous.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n }\n\n return (\n <>\n <div className=\"flex min-w-0 flex-col gap-section\">\n <header className=\"flex flex-col gap-inline\">\n <Header variant=\"h1\">General</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Update the workspace name and the slug used in its URLs.\n </Text>\n </header>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <form\n className=\"flex max-w-[560px] flex-col gap-group\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(\n value,\n 'Workspace name',\n createWorkspaceBodySchema.shape.name,\n ),\n onSubmit: ({value}) =>\n displayNameFieldError(\n value,\n 'Workspace name',\n createWorkspaceBodySchema.shape.name,\n ),\n }}\n >\n {(field) => (\n <FormField\n label=\"Workspace name\"\n id=\"workspace-settings-name\"\n error={fieldError(field)}\n >\n <FormFieldInput\n name=\"name\"\n type=\"text\"\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\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createWorkspaceBodySchema.shape.slug,\n onSubmit: createWorkspaceBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"workspace-settings-slug\"\n label=\"Workspace slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => field.handleChange(value)}\n onBlur={field.handleBlur}\n error={fieldError(field)}\n description={<span className=\"break-all font-code\">/w/{field.state.value}</span>}\n placeholder=\"acme\"\n className=\"font-code\"\n currentSlug={workspace.slug}\n checkEnabled\n isValid={isSlugValid}\n checkAvailability={checkWorkspaceSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button type=\"submit\" isLoading={updateWorkspace.isPending} className=\"self-start\">\n Save changes\n </Button>\n </form>\n </div>\n\n <SlugChangeWarning\n open={warningOpen}\n onOpenChange={setWarningOpen}\n entityLabel=\"workspace\"\n isLoading={updateWorkspace.isPending}\n onConfirm={() => {\n if (pendingValues) void save(pendingValues);\n }}\n />\n </>\n );\n}\n"],"names":["createWorkspaceBodySchema","checkWorkspaceSlugAvailability","useUpdateWorkspaceMutation","displayNameFieldError","SlugChangeWarning","SlugField","Button","Callout","FormField","FormFieldInput","fieldError","toast","Header","Text","useForm","useNavigate","useState","workspaceGeneralErrorToFormError","WorkspaceSettingsShell","isSlugValid","value","shape","slug","safeParse","success","GeneralSettingsPage","workspace","WorkspaceGeneralForm","id","name","updateWorkspace","navigate","warningOpen","setWarningOpen","pendingValues","setPendingValues","formError","setFormError","form","defaultValues","onSubmit","save","values","undefined","nameChanged","slugChanged","command","workspaceId","updated","mutateAsync","to","params","workspaceSlug","error","mapped","kind","setFieldMeta","field","previous","errorMap","onServer","message","div","className","header","variant","size","role","type","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","label","state","onChange","handleChange","target","handleBlur","description","span","placeholder","currentSlug","checkEnabled","isValid","checkAvailability","isLoading","isPending","open","onOpenChange","entityLabel","onConfirm"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"general.d.ts","sourceRoot":"","sources":["../../src/routes/general.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC
|
|
1
|
+
{"version":3,"file":"general.d.ts","sourceRoot":"","sources":["../../src/routes/general.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.tsx"],"names":[],"mappings":";sCAIyB;QAAC,MAAM,EAAE;YAAC,aAAa,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.tsx"],"names":[],"mappings":";sCAIyB;QAAC,MAAM,EAAE;YAAC,aAAa,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"members.d.ts","sourceRoot":"","sources":["../../src/routes/members.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC
|
|
1
|
+
{"version":3,"file":"members.d.ts","sourceRoot":"","sources":["../../src/routes/members.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC"}
|