@shipfox/client-workspace-settings 21.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.
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 19 files with swc (87.01ms)
2
+ Successfully compiled: 19 files with swc (315.8ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
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
+
12
+ ## 22.0.0
13
+
14
+ ### Patch Changes
15
+
16
+ - 7693eb3: Render empty and load-error states inside bordered panel bodies and keep loading placeholders aligned with their data regions.
17
+ - Updated dependencies [50b3867]
18
+ - Updated dependencies [00c1cb8]
19
+ - Updated dependencies [7693eb3]
20
+ - Updated dependencies [00c1cb8]
21
+ - Updated dependencies [56f4526]
22
+ - @shipfox/client-auth@22.0.0
23
+ - @shipfox/client-shell@22.0.0
24
+ - @shipfox/react-ui@2.1.0
25
+ - @shipfox/client-ui@22.0.0
26
+
3
27
  ## 21.0.0
4
28
 
5
29
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-members-section.d.ts","sourceRoot":"","sources":["../../../src/components/members/workspace-members-section.tsx"],"names":[],"mappings":"AA+CA,wBAAgB,+BAA+B,CAAC,EAC9C,WAAW,EACX,aAAa,GACd,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,+BAOA"}
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, memberCount } from '#core/membership.js';
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,27 +46,24 @@ function MembersSection({ workspaceId, workspaceName }) {
46
46
  return /*#__PURE__*/ _jsxs("section", {
47
47
  className: "flex flex-col gap-group",
48
48
  children: [
49
- /*#__PURE__*/ _jsxs("div", {
49
+ /*#__PURE__*/ _jsx("div", {
50
50
  className: "flex flex-col gap-tight",
51
- children: [
52
- /*#__PURE__*/ _jsx(Header, {
53
- variant: "h3",
54
- children: "Members"
55
- }),
56
- /*#__PURE__*/ _jsx(Text, {
57
- size: "sm",
58
- className: "text-foreground-neutral-muted",
59
- children: query.isPending ? 'Loading members…' : `${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,
65
- cols: 3
58
+ cols: 3,
59
+ label: "Loading members"
66
60
  }) : null,
67
- query.isError && query.data === undefined ? /*#__PURE__*/ _jsx(QueryLoadError, {
68
- query: query,
69
- subject: "members"
61
+ query.isError && query.data === undefined ? /*#__PURE__*/ _jsx(Panel, {
62
+ children: /*#__PURE__*/ _jsx(QueryLoadError, {
63
+ query: query,
64
+ subject: "members",
65
+ variant: "panel"
66
+ })
70
67
  }) : null,
71
68
  members.length > 0 ? /*#__PURE__*/ _jsx(Panel, {
72
69
  children: /*#__PURE__*/ _jsxs(Table, {
@@ -218,19 +215,12 @@ function PendingInvitationsSection({ workspaceId, workspaceName }) {
218
215
  /*#__PURE__*/ _jsxs("div", {
219
216
  className: "flex items-center justify-between gap-group",
220
217
  children: [
221
- /*#__PURE__*/ _jsxs("div", {
218
+ /*#__PURE__*/ _jsx("div", {
222
219
  className: "flex flex-col gap-tight",
223
- children: [
224
- /*#__PURE__*/ _jsx(Header, {
225
- variant: "h3",
226
- children: "Pending invitations"
227
- }),
228
- /*#__PURE__*/ _jsx(Text, {
229
- size: "sm",
230
- className: "text-foreground-neutral-muted",
231
- children: query.isPending ? 'Loading invitations…' : `${invitations.length} ${invitations.length === 1 ? 'invitation' : 'invitations'}`
232
- })
233
- ]
220
+ children: /*#__PURE__*/ _jsx(Header, {
221
+ variant: "h3",
222
+ children: "Pending invitations"
223
+ })
234
224
  }),
235
225
  /*#__PURE__*/ _jsx(InviteMemberModal, {
236
226
  open: inviteOpen,
@@ -242,13 +232,19 @@ function PendingInvitationsSection({ workspaceId, workspaceName }) {
242
232
  }),
243
233
  query.isPending ? /*#__PURE__*/ _jsx(TableSkeleton, {
244
234
  rows: 2,
245
- cols: 3
235
+ cols: 3,
236
+ label: "Loading invitations"
237
+ }) : null,
238
+ query.isError && query.data === undefined ? /*#__PURE__*/ _jsx(Panel, {
239
+ children: /*#__PURE__*/ _jsx(QueryLoadError, {
240
+ query: query,
241
+ subject: "invitations",
242
+ variant: "panel"
243
+ })
246
244
  }) : null,
247
- query.isError && query.data === undefined ? /*#__PURE__*/ _jsx(QueryLoadError, {
248
- query: query,
249
- subject: "invitations"
245
+ query.data !== undefined && invitations.length === 0 ? /*#__PURE__*/ _jsx(Panel, {
246
+ children: /*#__PURE__*/ _jsx(EmptyInvitations, {})
250
247
  }) : null,
251
- query.data !== undefined && invitations.length === 0 ? /*#__PURE__*/ _jsx(EmptyInvitations, {}) : null,
252
248
  invitations.length > 0 ? /*#__PURE__*/ _jsx(Panel, {
253
249
  children: /*#__PURE__*/ _jsxs(Table, {
254
250
  children: [
@@ -525,17 +521,19 @@ function EmptyInvitations() {
525
521
  return /*#__PURE__*/ _jsx(EmptyState, {
526
522
  icon: "mailLine",
527
523
  title: "No pending invitations.",
528
- description: "Invite someone above to grow your workspace."
524
+ description: "Invite someone above to grow your workspace.",
525
+ variant: "panel"
529
526
  });
530
527
  }
531
- function TableSkeleton({ rows, cols }) {
532
- return /*#__PURE__*/ _jsx("div", {
533
- className: "flex flex-col gap-cluster",
528
+ function TableSkeleton({ rows, cols, label }) {
529
+ return /*#__PURE__*/ _jsx(Panel, {
530
+ role: "status",
531
+ "aria-label": label,
532
+ className: "divide-y",
534
533
  children: Array.from({
535
534
  length: rows
536
- }).map((_, rowIdx)=>// biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder rows
537
- /*#__PURE__*/ _jsx("div", {
538
- className: "grid grid-cols-3 gap-group",
535
+ }).map((_, rowIdx)=>/*#__PURE__*/ _jsx("div", {
536
+ className: "grid min-h-44 grid-cols-3 gap-group px-row py-row",
539
537
  children: Array.from({
540
538
  length: cols
541
539
  }).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 {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} /> : null}\n\n {query.isError && query.data === undefined ? (\n <QueryLoadError query={query} subject=\"members\" />\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} /> : 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 <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 />\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","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","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 {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,EAAC,uBAAuB,EAAC,MAAM,+BAA+B,CAAC;AAEtE,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,+TAK7E"}
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 (!workspace) return /*#__PURE__*/ _jsx(FullPageLoader, {});
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 <FullPageLoader />;\n\n return children(workspace);\n}\n"],"names":["useMaybeActiveWorkspace","FullPageLoader","WorkspaceSettingsShell","children","workspace"],"mappings":";AAAA,SAAQA,uBAAuB,QAAO,gCAAgC;AACtE,SAAQC,cAAc,QAAO,2BAA2B;AAOxD,OAAO,SAASC,uBAAuB,EAACC,QAAQ,EAA8B;IAC5E,MAAMC,YAAYJ;IAClB,IAAI,CAACI,WAAW,qBAAO,KAACH;IAExB,OAAOE,SAASC;AAClB"}
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":"AA2BA,wBAAgB,mBAAmB,gCAWlC"}
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, Text } from '@shipfox/react-ui/typography';
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__*/ _jsxs("header", {
93
- className: "flex flex-col gap-inline",
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
- formError ? /*#__PURE__*/ _jsx(Callout, {
107
- role: "alert",
108
- type: "error",
109
- children: formError
110
- }) : null,
111
- /*#__PURE__*/ _jsxs("form", {
112
- className: "flex max-w-[560px] flex-col gap-group",
113
- noValidate: true,
114
- onSubmit: (event)=>{
115
- event.preventDefault();
116
- event.stopPropagation();
117
- void form.handleSubmit();
118
- },
119
- children: [
120
- /*#__PURE__*/ _jsx(form.Field, {
121
- name: "name",
122
- validators: {
123
- onBlur: ({ value })=>displayNameFieldError(value, 'Workspace name', createWorkspaceBodySchema.shape.name),
124
- onSubmit: ({ value })=>displayNameFieldError(value, 'Workspace name', createWorkspaceBodySchema.shape.name)
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
- type: "text",
133
- value: field.state.value,
134
- onChange: (event)=>field.handleChange(event.target.value),
135
- onBlur: field.handleBlur
136
- })
137
- })
138
- }),
139
- /*#__PURE__*/ _jsx(form.Field, {
140
- name: "slug",
141
- validators: {
142
- onBlur: createWorkspaceBodySchema.shape.slug,
143
- onSubmit: createWorkspaceBodySchema.shape.slug
144
- },
145
- children: (field)=>/*#__PURE__*/ _jsx(SlugField, {
146
- id: "workspace-settings-slug",
147
- label: "Workspace slug",
148
- name: "slug",
149
- value: field.state.value,
150
- onChange: (value)=>field.handleChange(value),
151
- onBlur: field.handleBlur,
152
- error: fieldError(field),
153
- description: /*#__PURE__*/ _jsxs("span", {
154
- className: "break-all font-code",
155
- children: [
156
- "/w/",
157
- field.state.value
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
- placeholder: "acme",
161
- className: "font-code",
162
- currentSlug: workspace.slug,
163
- checkEnabled: true,
164
- isValid: isSlugValid,
165
- checkAvailability: checkWorkspaceSlugAvailability
166
- })
167
- }),
168
- /*#__PURE__*/ _jsx(Button, {
169
- type: "submit",
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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"}