@shipfox/client-workspace-settings 22.0.0 → 22.0.1
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/.turbo/turbo-type.log +0 -1
- package/CHANGELOG.md +9 -0
- package/dist/components/members/workspace-members-section.d.ts.map +1 -1
- package/dist/components/members/workspace-members-section.js +11 -25
- package/dist/components/members/workspace-members-section.js.map +1 -1
- package/dist/components/workspace-settings-shell.d.ts.map +1 -1
- package/dist/components/workspace-settings-shell.js +9 -2
- package/dist/components/workspace-settings-shell.js.map +1 -1
- package/dist/pages/general-settings-page.d.ts.map +1 -1
- package/dist/pages/general-settings-page.js +79 -81
- package/dist/pages/general-settings-page.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/components/members/workspace-members-section.tsx +1 -12
- package/src/components/workspace-settings-shell.tsx +10 -2
- package/src/pages/general-settings-page.tsx +84 -82
- 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 (315.8ms)
|
package/.turbo/turbo-type.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @shipfox/client-workspace-settings
|
|
2
2
|
|
|
3
|
+
## 22.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e92517f: Reorganizes workspace settings into a compact sidebar rail with section panels and relocates Events settings into the new workspace layout.
|
|
8
|
+
- Updated dependencies [e92517f]
|
|
9
|
+
- @shipfox/client-shell@22.0.1
|
|
10
|
+
- @shipfox/client-auth@22.0.1
|
|
11
|
+
|
|
3
12
|
## 22.0.0
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-members-section.d.ts","sourceRoot":"","sources":["../../../src/components/members/workspace-members-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workspace-members-section.d.ts","sourceRoot":"","sources":["../../../src/components/members/workspace-members-section.tsx"],"names":[],"mappings":"AA8CA,wBAAgB,+BAA+B,CAAC,EAC9C,WAAW,EACX,aAAa,GACd,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,+BAOA"}
|
|
@@ -17,7 +17,7 @@ import { Code, Header, Text } from '@shipfox/react-ui/typography';
|
|
|
17
17
|
import { formatDate } from '@shipfox/react-ui/utils';
|
|
18
18
|
import { useForm } from '@tanstack/react-form';
|
|
19
19
|
import { useState } from 'react';
|
|
20
|
-
import { getInvitationExpiry, getMemberRemovalRestriction
|
|
20
|
+
import { getInvitationExpiry, getMemberRemovalRestriction } from '#core/membership.js';
|
|
21
21
|
import { useCreateInvitation } from '#hooks/api/create-invitation.js';
|
|
22
22
|
import { useListInvitations } from '#hooks/api/list-invitations.js';
|
|
23
23
|
import { useListMembers } from '#hooks/api/list-members.js';
|
|
@@ -46,19 +46,12 @@ function MembersSection({ workspaceId, workspaceName }) {
|
|
|
46
46
|
return /*#__PURE__*/ _jsxs("section", {
|
|
47
47
|
className: "flex flex-col gap-group",
|
|
48
48
|
children: [
|
|
49
|
-
/*#__PURE__*/
|
|
49
|
+
/*#__PURE__*/ _jsx("div", {
|
|
50
50
|
className: "flex flex-col gap-tight",
|
|
51
|
-
children:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}),
|
|
56
|
-
/*#__PURE__*/ _jsx(Text, {
|
|
57
|
-
size: "sm",
|
|
58
|
-
className: "text-foreground-neutral-muted",
|
|
59
|
-
children: query.isPending ? 'Loading members…' : `${memberCount(members)} ${memberCount(members) === 1 ? 'member' : 'members'}`
|
|
60
|
-
})
|
|
61
|
-
]
|
|
51
|
+
children: /*#__PURE__*/ _jsx(Header, {
|
|
52
|
+
variant: "h1",
|
|
53
|
+
children: "Members"
|
|
54
|
+
})
|
|
62
55
|
}),
|
|
63
56
|
query.isPending ? /*#__PURE__*/ _jsx(TableSkeleton, {
|
|
64
57
|
rows: 3,
|
|
@@ -222,19 +215,12 @@ function PendingInvitationsSection({ workspaceId, workspaceName }) {
|
|
|
222
215
|
/*#__PURE__*/ _jsxs("div", {
|
|
223
216
|
className: "flex items-center justify-between gap-group",
|
|
224
217
|
children: [
|
|
225
|
-
/*#__PURE__*/
|
|
218
|
+
/*#__PURE__*/ _jsx("div", {
|
|
226
219
|
className: "flex flex-col gap-tight",
|
|
227
|
-
children:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}),
|
|
232
|
-
/*#__PURE__*/ _jsx(Text, {
|
|
233
|
-
size: "sm",
|
|
234
|
-
className: "text-foreground-neutral-muted",
|
|
235
|
-
children: query.isPending ? 'Loading invitations…' : `${invitations.length} ${invitations.length === 1 ? 'invitation' : 'invitations'}`
|
|
236
|
-
})
|
|
237
|
-
]
|
|
220
|
+
children: /*#__PURE__*/ _jsx(Header, {
|
|
221
|
+
variant: "h3",
|
|
222
|
+
children: "Pending invitations"
|
|
223
|
+
})
|
|
238
224
|
}),
|
|
239
225
|
/*#__PURE__*/ _jsx(InviteMemberModal, {
|
|
240
226
|
open: inviteOpen,
|
|
@@ -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 {Panel} from '@shipfox/react-ui/panel';\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} label=\"Loading members\" /> : null}\n\n {query.isError && query.data === undefined ? (\n <Panel>\n <QueryLoadError query={query} subject=\"members\" variant=\"panel\" />\n </Panel>\n ) : null}\n\n {members.length > 0 ? (\n <Panel>\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 </Panel>\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} label=\"Loading invitations\" /> : null}\n\n {query.isError && query.data === undefined ? (\n <Panel>\n <QueryLoadError query={query} subject=\"invitations\" variant=\"panel\" />\n </Panel>\n ) : null}\n\n {query.data !== undefined && invitations.length === 0 ? (\n <Panel>\n <EmptyInvitations />\n </Panel>\n ) : null}\n\n {invitations.length > 0 ? (\n <Panel>\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 </Panel>\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 variant=\"panel\"\n />\n );\n}\n\nfunction TableSkeleton({rows, cols, label}: {rows: number; cols: number; label: string}) {\n return (\n <Panel role=\"status\" aria-label={label} className=\"divide-y\">\n {Array.from({length: rows}).map((_, rowIdx) => (\n <div\n // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder rows\n key={rowIdx}\n className=\"grid min-h-44 grid-cols-3 gap-group px-row py-row\"\n >\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 </Panel>\n );\n}\n"],"names":["createInvitationBodySchema","useAuthState","QueryLoadError","Badge","Button","Callout","EmptyState","FormField","FormFieldInput","fieldError","Icon","Modal","ModalBody","ModalContent","ModalFooter","ModalHeader","ModalTitle","ModalTrigger","Panel","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","label","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","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 {Panel} from '@shipfox/react-ui/panel';\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 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=\"h1\">Members</Header>\n </div>\n\n {query.isPending ? <TableSkeleton rows={3} cols={3} label=\"Loading members\" /> : null}\n\n {query.isError && query.data === undefined ? (\n <Panel>\n <QueryLoadError query={query} subject=\"members\" variant=\"panel\" />\n </Panel>\n ) : null}\n\n {members.length > 0 ? (\n <Panel>\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 </Panel>\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 </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} label=\"Loading invitations\" /> : null}\n\n {query.isError && query.data === undefined ? (\n <Panel>\n <QueryLoadError query={query} subject=\"invitations\" variant=\"panel\" />\n </Panel>\n ) : null}\n\n {query.data !== undefined && invitations.length === 0 ? (\n <Panel>\n <EmptyInvitations />\n </Panel>\n ) : null}\n\n {invitations.length > 0 ? (\n <Panel>\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 </Panel>\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 variant=\"panel\"\n />\n );\n}\n\nfunction TableSkeleton({rows, cols, label}: {rows: number; cols: number; label: string}) {\n return (\n <Panel role=\"status\" aria-label={label} className=\"divide-y\">\n {Array.from({length: rows}).map((_, rowIdx) => (\n <div\n // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder rows\n key={rowIdx}\n className=\"grid min-h-44 grid-cols-3 gap-group px-row py-row\"\n >\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 </Panel>\n );\n}\n"],"names":["createInvitationBodySchema","useAuthState","QueryLoadError","Badge","Button","Callout","EmptyState","FormField","FormFieldInput","fieldError","Icon","Modal","ModalBody","ModalContent","ModalFooter","ModalHeader","ModalTitle","ModalTrigger","Panel","Skeleton","Table","TableBody","TableCell","TableHead","TableHeader","TableRow","toast","Code","Header","Text","formatDate","useForm","useState","getInvitationExpiry","getMemberRemovalRestriction","useCreateInvitation","useListInvitations","useListMembers","useRemoveMember","useRevokeInvitation","invitationErrorToFormError","memberRemovalErrorMessage","WorkspaceMembersSettingsSection","workspaceId","workspaceName","div","className","MembersSection","PendingInvitationsSection","auth","query","members","data","section","variant","isPending","TableSkeleton","rows","cols","label","isError","undefined","subject","length","map","member","MemberRow","currentUserId","user","id","open","setOpen","remove","restriction","handleRemove","mutateAsync","userId","success","email","error","name","joinedAt","onOpenChange","asChild","size","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","description","autoComplete","autoFocus","state","onChange","handleChange","target","handleBlur","icon","title","Array","from","_","rowIdx","__","colIdx"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-settings-shell.d.ts","sourceRoot":"","sources":["../../src/components/workspace-settings-shell.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"workspace-settings-shell.d.ts","sourceRoot":"","sources":["../../src/components/workspace-settings-shell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,uBAAuB,EAExB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,UAAU,2BAA2B;IACnC,QAAQ,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,KAAK,SAAS,CAAC;CAC7F;AAED,wBAAgB,sBAAsB,CAAC,EAAC,QAAQ,EAAC,EAAE,2BAA2B,+TAQ7E"}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMaybeActiveWorkspace } from '@shipfox/client-shell/runtime';
|
|
2
|
+
import { useAuthState, useMaybeActiveWorkspace, WorkspaceUnavailablePage } from '@shipfox/client-shell/runtime';
|
|
3
3
|
import { FullPageLoader } from '@shipfox/react-ui/loader';
|
|
4
|
+
import { Navigate } from '@tanstack/react-router';
|
|
4
5
|
export function WorkspaceSettingsShell({ children }) {
|
|
6
|
+
const auth = useAuthState();
|
|
5
7
|
const workspace = useMaybeActiveWorkspace();
|
|
6
|
-
if (
|
|
8
|
+
if (auth.isLoading) return /*#__PURE__*/ _jsx(FullPageLoader, {});
|
|
9
|
+
if (!auth.isAuthenticated) return /*#__PURE__*/ _jsx(Navigate, {
|
|
10
|
+
to: '/auth/login',
|
|
11
|
+
replace: true
|
|
12
|
+
});
|
|
13
|
+
if (!workspace) return /*#__PURE__*/ _jsx(WorkspaceUnavailablePage, {});
|
|
7
14
|
return children(workspace);
|
|
8
15
|
}
|
|
9
16
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/workspace-settings-shell.tsx"],"sourcesContent":["import {useMaybeActiveWorkspace} from '@shipfox/client-shell/runtime';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport type {ReactNode} from 'react';\n\ninterface WorkspaceSettingsShellProps {\n children: (workspace: NonNullable<ReturnType<typeof useMaybeActiveWorkspace>>) => ReactNode;\n}\n\nexport function WorkspaceSettingsShell({children}: WorkspaceSettingsShellProps) {\n const workspace = useMaybeActiveWorkspace();\n if (!workspace) return <
|
|
1
|
+
{"version":3,"sources":["../../src/components/workspace-settings-shell.tsx"],"sourcesContent":["import {\n useAuthState,\n useMaybeActiveWorkspace,\n WorkspaceUnavailablePage,\n} from '@shipfox/client-shell/runtime';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Navigate} from '@tanstack/react-router';\nimport type {ReactNode} from 'react';\n\ninterface WorkspaceSettingsShellProps {\n children: (workspace: NonNullable<ReturnType<typeof useMaybeActiveWorkspace>>) => ReactNode;\n}\n\nexport function WorkspaceSettingsShell({children}: WorkspaceSettingsShellProps) {\n const auth = useAuthState();\n const workspace = useMaybeActiveWorkspace();\n if (auth.isLoading) return <FullPageLoader />;\n if (!auth.isAuthenticated) return <Navigate to={'/auth/login' as never} replace />;\n if (!workspace) return <WorkspaceUnavailablePage />;\n\n return children(workspace);\n}\n"],"names":["useAuthState","useMaybeActiveWorkspace","WorkspaceUnavailablePage","FullPageLoader","Navigate","WorkspaceSettingsShell","children","auth","workspace","isLoading","isAuthenticated","to","replace"],"mappings":";AAAA,SACEA,YAAY,EACZC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,QAAQ,QAAO,yBAAyB;AAOhD,OAAO,SAASC,uBAAuB,EAACC,QAAQ,EAA8B;IAC5E,MAAMC,OAAOP;IACb,MAAMQ,YAAYP;IAClB,IAAIM,KAAKE,SAAS,EAAE,qBAAO,KAACN;IAC5B,IAAI,CAACI,KAAKG,eAAe,EAAE,qBAAO,KAACN;QAASO,IAAI;QAAwBC,OAAO;;IAC/E,IAAI,CAACJ,WAAW,qBAAO,KAACN;IAExB,OAAOI,SAASE;AAClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"general-settings-page.d.ts","sourceRoot":"","sources":["../../src/pages/general-settings-page.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"general-settings-page.d.ts","sourceRoot":"","sources":["../../src/pages/general-settings-page.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,mBAAmB,gCAWlC"}
|
|
@@ -5,8 +5,9 @@ import { displayNameFieldError, SlugChangeWarning, SlugField } from '@shipfox/cl
|
|
|
5
5
|
import { Button } from '@shipfox/react-ui/button';
|
|
6
6
|
import { Callout } from '@shipfox/react-ui/callout';
|
|
7
7
|
import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
|
|
8
|
+
import { Panel, PanelBody } from '@shipfox/react-ui/panel';
|
|
8
9
|
import { toast } from '@shipfox/react-ui/toast';
|
|
9
|
-
import { Header
|
|
10
|
+
import { Header } from '@shipfox/react-ui/typography';
|
|
10
11
|
import { useForm } from '@tanstack/react-form';
|
|
11
12
|
import { useNavigate } from '@tanstack/react-router';
|
|
12
13
|
import { useState } from 'react';
|
|
@@ -89,89 +90,86 @@ function WorkspaceGeneralForm({ workspace }) {
|
|
|
89
90
|
/*#__PURE__*/ _jsxs("div", {
|
|
90
91
|
className: "flex min-w-0 flex-col gap-section",
|
|
91
92
|
children: [
|
|
92
|
-
/*#__PURE__*/
|
|
93
|
-
|
|
94
|
-
children:
|
|
95
|
-
/*#__PURE__*/ _jsx(Header, {
|
|
96
|
-
variant: "h1",
|
|
97
|
-
children: "General"
|
|
98
|
-
}),
|
|
99
|
-
/*#__PURE__*/ _jsx(Text, {
|
|
100
|
-
size: "sm",
|
|
101
|
-
className: "text-foreground-neutral-muted",
|
|
102
|
-
children: "Update the workspace name and the slug used in its URLs."
|
|
103
|
-
})
|
|
104
|
-
]
|
|
93
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
94
|
+
variant: "h1",
|
|
95
|
+
children: "General"
|
|
105
96
|
}),
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
},
|
|
126
|
-
children: (field)=>/*#__PURE__*/ _jsx(FormField, {
|
|
127
|
-
label: "Workspace name",
|
|
128
|
-
id: "workspace-settings-name",
|
|
129
|
-
error: fieldError(field),
|
|
130
|
-
children: /*#__PURE__*/ _jsx(FormFieldInput, {
|
|
97
|
+
/*#__PURE__*/ _jsx(Panel, {
|
|
98
|
+
children: /*#__PURE__*/ _jsxs(PanelBody, {
|
|
99
|
+
className: "gap-group p-panel",
|
|
100
|
+
children: [
|
|
101
|
+
formError ? /*#__PURE__*/ _jsx(Callout, {
|
|
102
|
+
role: "alert",
|
|
103
|
+
type: "error",
|
|
104
|
+
children: formError
|
|
105
|
+
}) : null,
|
|
106
|
+
/*#__PURE__*/ _jsxs("form", {
|
|
107
|
+
className: "flex w-full max-w-[560px] flex-col gap-group",
|
|
108
|
+
noValidate: true,
|
|
109
|
+
onSubmit: (event)=>{
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
void form.handleSubmit();
|
|
113
|
+
},
|
|
114
|
+
children: [
|
|
115
|
+
/*#__PURE__*/ _jsx(form.Field, {
|
|
131
116
|
name: "name",
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
children:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
117
|
+
validators: {
|
|
118
|
+
onBlur: ({ value })=>displayNameFieldError(value, 'Workspace name', createWorkspaceBodySchema.shape.name),
|
|
119
|
+
onSubmit: ({ value })=>displayNameFieldError(value, 'Workspace name', createWorkspaceBodySchema.shape.name)
|
|
120
|
+
},
|
|
121
|
+
children: (field)=>/*#__PURE__*/ _jsx(FormField, {
|
|
122
|
+
label: "Workspace name",
|
|
123
|
+
id: "workspace-settings-name",
|
|
124
|
+
error: fieldError(field),
|
|
125
|
+
children: /*#__PURE__*/ _jsx(FormFieldInput, {
|
|
126
|
+
name: "name",
|
|
127
|
+
type: "text",
|
|
128
|
+
value: field.state.value,
|
|
129
|
+
onChange: (event)=>field.handleChange(event.target.value),
|
|
130
|
+
onBlur: field.handleBlur
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
}),
|
|
134
|
+
/*#__PURE__*/ _jsx(form.Field, {
|
|
135
|
+
name: "slug",
|
|
136
|
+
validators: {
|
|
137
|
+
onBlur: createWorkspaceBodySchema.shape.slug,
|
|
138
|
+
onSubmit: createWorkspaceBodySchema.shape.slug
|
|
139
|
+
},
|
|
140
|
+
children: (field)=>/*#__PURE__*/ _jsx(SlugField, {
|
|
141
|
+
id: "workspace-settings-slug",
|
|
142
|
+
label: "Workspace slug",
|
|
143
|
+
name: "slug",
|
|
144
|
+
value: field.state.value,
|
|
145
|
+
onChange: (value)=>field.handleChange(value),
|
|
146
|
+
onBlur: field.handleBlur,
|
|
147
|
+
error: fieldError(field),
|
|
148
|
+
description: /*#__PURE__*/ _jsxs("span", {
|
|
149
|
+
className: "break-all font-code",
|
|
150
|
+
children: [
|
|
151
|
+
"/w/",
|
|
152
|
+
field.state.value
|
|
153
|
+
]
|
|
154
|
+
}),
|
|
155
|
+
placeholder: "acme",
|
|
156
|
+
className: "font-code",
|
|
157
|
+
currentSlug: workspace.slug,
|
|
158
|
+
checkEnabled: true,
|
|
159
|
+
isValid: isSlugValid,
|
|
160
|
+
checkAvailability: checkWorkspaceSlugAvailability
|
|
161
|
+
})
|
|
159
162
|
}),
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
isLoading: updateWorkspace.isPending,
|
|
171
|
-
className: "self-start",
|
|
172
|
-
children: "Save changes"
|
|
173
|
-
})
|
|
174
|
-
]
|
|
163
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
164
|
+
type: "submit",
|
|
165
|
+
isLoading: updateWorkspace.isPending,
|
|
166
|
+
className: "self-start",
|
|
167
|
+
children: "Save changes"
|
|
168
|
+
})
|
|
169
|
+
]
|
|
170
|
+
})
|
|
171
|
+
]
|
|
172
|
+
})
|
|
175
173
|
})
|
|
176
174
|
]
|
|
177
175
|
}),
|
|
@@ -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-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
|
+
{"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 {Panel, PanelBody} from '@shipfox/react-ui/panel';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header} 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 variant=\"h1\">General</Header>\n\n <Panel>\n <PanelBody className=\"gap-group p-panel\">\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <form\n className=\"flex w-full 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={\n <span className=\"break-all font-code\">/w/{field.state.value}</span>\n }\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 </PanelBody>\n </Panel>\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","Panel","PanelBody","toast","Header","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","variant","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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"}
|