@shipfox/client-auth 5.0.0 → 6.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.
Files changed (109) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +43 -0
  3. package/dist/components/auth-guard.d.ts.map +1 -1
  4. package/dist/components/auth-guard.js +4 -4
  5. package/dist/components/auth-guard.js.map +1 -1
  6. package/dist/components/redirect-target.d.ts +7 -0
  7. package/dist/components/redirect-target.d.ts.map +1 -1
  8. package/dist/components/redirect-target.js +35 -4
  9. package/dist/components/redirect-target.js.map +1 -1
  10. package/dist/core/auth.d.ts +57 -0
  11. package/dist/core/auth.d.ts.map +1 -0
  12. package/dist/core/auth.js +3 -0
  13. package/dist/core/auth.js.map +1 -0
  14. package/dist/hooks/api/auth-mapper.d.ts +2 -17
  15. package/dist/hooks/api/auth-mapper.d.ts.map +1 -1
  16. package/dist/hooks/api/auth-mapper.js.map +1 -1
  17. package/dist/hooks/api/login-auth.d.ts +3 -6
  18. package/dist/hooks/api/login-auth.d.ts.map +1 -1
  19. package/dist/hooks/api/login-auth.js +2 -2
  20. package/dist/hooks/api/login-auth.js.map +1 -1
  21. package/dist/hooks/api/logout-auth.js +2 -2
  22. package/dist/hooks/api/logout-auth.js.map +1 -1
  23. package/dist/hooks/api/password-reset-auth.d.ts +5 -10
  24. package/dist/hooks/api/password-reset-auth.d.ts.map +1 -1
  25. package/dist/hooks/api/password-reset-auth.js +9 -6
  26. package/dist/hooks/api/password-reset-auth.js.map +1 -1
  27. package/dist/hooks/api/refresh-auth.d.ts +1 -1
  28. package/dist/hooks/api/refresh-auth.d.ts.map +1 -1
  29. package/dist/hooks/api/refresh-auth.js +1 -1
  30. package/dist/hooks/api/refresh-auth.js.map +1 -1
  31. package/dist/hooks/api/signup-auth.d.ts +2 -6
  32. package/dist/hooks/api/signup-auth.d.ts.map +1 -1
  33. package/dist/hooks/api/signup-auth.js +9 -2
  34. package/dist/hooks/api/signup-auth.js.map +1 -1
  35. package/dist/hooks/api/verify-email-auth.d.ts +4 -10
  36. package/dist/hooks/api/verify-email-auth.d.ts.map +1 -1
  37. package/dist/hooks/api/verify-email-auth.js +15 -5
  38. package/dist/hooks/api/verify-email-auth.js.map +1 -1
  39. package/dist/hooks/api/workspace-auth.d.ts +5 -7
  40. package/dist/hooks/api/workspace-auth.d.ts.map +1 -1
  41. package/dist/hooks/api/workspace-auth.js +6 -4
  42. package/dist/hooks/api/workspace-auth.js.map +1 -1
  43. package/dist/hooks/api/workspace-mapper.d.ts +1 -5
  44. package/dist/hooks/api/workspace-mapper.d.ts.map +1 -1
  45. package/dist/hooks/api/workspace-mapper.js.map +1 -1
  46. package/dist/pages/login-page.d.ts.map +1 -1
  47. package/dist/pages/login-page.js +4 -5
  48. package/dist/pages/login-page.js.map +1 -1
  49. package/dist/pages/logout-page.d.ts.map +1 -1
  50. package/dist/pages/logout-page.js +8 -18
  51. package/dist/pages/logout-page.js.map +1 -1
  52. package/dist/pages/password-reset-page.d.ts.map +1 -1
  53. package/dist/pages/password-reset-page.js +8 -7
  54. package/dist/pages/password-reset-page.js.map +1 -1
  55. package/dist/pages/signup-page.d.ts.map +1 -1
  56. package/dist/pages/signup-page.js +16 -10
  57. package/dist/pages/signup-page.js.map +1 -1
  58. package/dist/pages/workspace-onboarding-page.js +2 -2
  59. package/dist/pages/workspace-onboarding-page.js.map +1 -1
  60. package/dist/routes/inputs.d.ts +9 -0
  61. package/dist/routes/inputs.d.ts.map +1 -0
  62. package/dist/routes/inputs.js +17 -0
  63. package/dist/routes/inputs.js.map +1 -0
  64. package/dist/routes/login.d.ts +2 -0
  65. package/dist/routes/login.d.ts.map +1 -1
  66. package/dist/routes/login.js +2 -0
  67. package/dist/routes/login.js.map +1 -1
  68. package/dist/routes/logout.d.ts +2 -0
  69. package/dist/routes/logout.d.ts.map +1 -1
  70. package/dist/routes/logout.js +2 -0
  71. package/dist/routes/logout.js.map +1 -1
  72. package/dist/routes/reset.d.ts +2 -0
  73. package/dist/routes/reset.d.ts.map +1 -1
  74. package/dist/routes/reset.js +2 -0
  75. package/dist/routes/reset.js.map +1 -1
  76. package/dist/routes/signup.d.ts +2 -0
  77. package/dist/routes/signup.d.ts.map +1 -1
  78. package/dist/routes/signup.js +2 -0
  79. package/dist/routes/signup.js.map +1 -1
  80. package/dist/tsconfig.test.tsbuildinfo +1 -1
  81. package/package.json +11 -12
  82. package/src/components/auth-guard.tsx +4 -2
  83. package/src/components/redirect-target.test.ts +32 -1
  84. package/src/components/redirect-target.ts +41 -4
  85. package/src/core/auth.ts +55 -0
  86. package/src/hooks/api/auth-mapper.ts +2 -12
  87. package/src/hooks/api/login-auth.ts +4 -3
  88. package/src/hooks/api/logout-auth.ts +2 -2
  89. package/src/hooks/api/password-reset-auth.test.ts +2 -2
  90. package/src/hooks/api/password-reset-auth.ts +10 -10
  91. package/src/hooks/api/refresh-auth.ts +1 -0
  92. package/src/hooks/api/signup-auth.ts +9 -3
  93. package/src/hooks/api/verify-email-auth.ts +19 -9
  94. package/src/hooks/api/workspace-auth.ts +10 -5
  95. package/src/hooks/api/workspace-mapper.ts +1 -6
  96. package/src/pages/login-page.tsx +4 -3
  97. package/src/pages/logout-page.test.tsx +75 -0
  98. package/src/pages/logout-page.tsx +9 -13
  99. package/src/pages/password-reset-page.tsx +5 -5
  100. package/src/pages/signup-page.tsx +14 -8
  101. package/src/pages/workspace-onboarding-page.tsx +2 -2
  102. package/src/routes/inputs.test.ts +17 -0
  103. package/src/routes/inputs.ts +21 -0
  104. package/src/routes/login.tsx +2 -0
  105. package/src/routes/logout.tsx +2 -1
  106. package/src/routes/reset.tsx +2 -0
  107. package/src/routes/signup.tsx +2 -0
  108. package/tsconfig.build.tsbuildinfo +1 -1
  109. package/vercel.json +5 -0
@@ -1,8 +1,6 @@
1
- import { type CreateWorkspaceBodyDto } from '@shipfox/api-workspaces-dto';
2
- import { listUserWorkspaces, userWorkspacesQueryKey } from '@shipfox/client-shell/runtime';
3
- export declare function createWorkspace(body: CreateWorkspaceBodyDto): Promise<import("./workspace-mapper.js").Workspace>;
4
- export { listUserWorkspaces, userWorkspacesQueryKey };
5
- export declare function useCreateWorkspaceAuth(): import("@tanstack/react-query").UseMutationResult<import("./workspace-mapper.js").Workspace, Error, {
6
- name: string;
7
- }, unknown>;
1
+ import { listUserWorkspaces, userWorkspacesQueryKey, userWorkspacesQueryOptions } from '@shipfox/client-shell/runtime';
2
+ import type { WorkspaceCreateCommand } from '#core/auth.js';
3
+ export declare function createWorkspace(command: WorkspaceCreateCommand): Promise<import("#core/auth.js").Workspace>;
4
+ export { listUserWorkspaces, userWorkspacesQueryKey, userWorkspacesQueryOptions };
5
+ export declare function useCreateWorkspaceAuth(): import("@tanstack/react-query").UseMutationResult<import("#core/auth.js").Workspace, Error, WorkspaceCreateCommand, unknown>;
8
6
  //# sourceMappingURL=workspace-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/workspace-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,sBAAsB,EAA0B,MAAM,6BAA6B,CAAC;AAEjG,OAAO,EAAC,kBAAkB,EAAE,sBAAsB,EAAC,MAAM,+BAA+B,CAAC;AAKzF,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,sDAMjE;AAED,OAAO,EAAC,kBAAkB,EAAE,sBAAsB,EAAC,CAAC;AAEpD,wBAAgB,sBAAsB;;YAYrC"}
1
+ {"version":3,"file":"workspace-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/workspace-auth.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC3B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,eAAe,CAAC;AAI1D,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,8CAMpE;AAED,OAAO,EAAC,kBAAkB,EAAE,sBAAsB,EAAE,0BAA0B,EAAC,CAAC;AAEhF,wBAAgB,sBAAsB,iIAYrC"}
@@ -1,17 +1,19 @@
1
1
  import { workspaceResponseSchema } from '@shipfox/api-workspaces-dto';
2
2
  import { checkedApiRequest } from '@shipfox/client-api';
3
- import { listUserWorkspaces, userWorkspacesQueryKey } from '@shipfox/client-shell/runtime';
3
+ import { listUserWorkspaces, userWorkspacesQueryKey, userWorkspacesQueryOptions } from '@shipfox/client-shell/runtime';
4
4
  import { useMutation } from '@tanstack/react-query';
5
5
  import { useRefreshAuth } from './refresh-auth.js';
6
6
  import { toWorkspace } from './workspace-mapper.js';
7
- export async function createWorkspace(body) {
7
+ export async function createWorkspace(command) {
8
8
  const response = await checkedApiRequest(workspaceResponseSchema, '/workspaces', {
9
9
  method: 'POST',
10
- body
10
+ body: {
11
+ name: command.name
12
+ }
11
13
  });
12
14
  return toWorkspace(response);
13
15
  }
14
- export { listUserWorkspaces, userWorkspacesQueryKey };
16
+ export { listUserWorkspaces, userWorkspacesQueryKey, userWorkspacesQueryOptions };
15
17
  export function useCreateWorkspaceAuth() {
16
18
  const refreshAuth = useRefreshAuth();
17
19
  return useMutation({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/workspace-auth.ts"],"sourcesContent":["import {type CreateWorkspaceBodyDto, workspaceResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {listUserWorkspaces, userWorkspacesQueryKey} from '@shipfox/client-shell/runtime';\nimport {useMutation} from '@tanstack/react-query';\nimport {useRefreshAuth} from './refresh-auth.js';\nimport {toWorkspace} from './workspace-mapper.js';\n\nexport async function createWorkspace(body: CreateWorkspaceBodyDto) {\n const response = await checkedApiRequest(workspaceResponseSchema, '/workspaces', {\n method: 'POST',\n body,\n });\n return toWorkspace(response);\n}\n\nexport {listUserWorkspaces, userWorkspacesQueryKey};\n\nexport function useCreateWorkspaceAuth() {\n const refreshAuth = useRefreshAuth();\n\n return useMutation({\n mutationFn: createWorkspace,\n onSuccess: async () => {\n // The new workspace introduces a membership the existing access token\n // doesn't carry. Refresh so the next request includes it in the JWT\n // claim and passes the in-memory canAccess() check on the server.\n await refreshAuth();\n },\n });\n}\n"],"names":["workspaceResponseSchema","checkedApiRequest","listUserWorkspaces","userWorkspacesQueryKey","useMutation","useRefreshAuth","toWorkspace","createWorkspace","body","response","method","useCreateWorkspaceAuth","refreshAuth","mutationFn","onSuccess"],"mappings":"AAAA,SAAqCA,uBAAuB,QAAO,8BAA8B;AACjG,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAQC,kBAAkB,EAAEC,sBAAsB,QAAO,gCAAgC;AACzF,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,WAAW,QAAO,wBAAwB;AAElD,OAAO,eAAeC,gBAAgBC,IAA4B;IAChE,MAAMC,WAAW,MAAMR,kBAAkBD,yBAAyB,eAAe;QAC/EU,QAAQ;QACRF;IACF;IACA,OAAOF,YAAYG;AACrB;AAEA,SAAQP,kBAAkB,EAAEC,sBAAsB,GAAE;AAEpD,OAAO,SAASQ;IACd,MAAMC,cAAcP;IAEpB,OAAOD,YAAY;QACjBS,YAAYN;QACZO,WAAW;YACT,sEAAsE;YACtE,oEAAoE;YACpE,kEAAkE;YAClE,MAAMF;QACR;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/hooks/api/workspace-auth.ts"],"sourcesContent":["import {workspaceResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {\n listUserWorkspaces,\n userWorkspacesQueryKey,\n userWorkspacesQueryOptions,\n} from '@shipfox/client-shell/runtime';\nimport {useMutation} from '@tanstack/react-query';\nimport type {WorkspaceCreateCommand} from '#core/auth.js';\nimport {useRefreshAuth} from './refresh-auth.js';\nimport {toWorkspace} from './workspace-mapper.js';\n\nexport async function createWorkspace(command: WorkspaceCreateCommand) {\n const response = await checkedApiRequest(workspaceResponseSchema, '/workspaces', {\n method: 'POST',\n body: {name: command.name},\n });\n return toWorkspace(response);\n}\n\nexport {listUserWorkspaces, userWorkspacesQueryKey, userWorkspacesQueryOptions};\n\nexport function useCreateWorkspaceAuth() {\n const refreshAuth = useRefreshAuth();\n\n return useMutation({\n mutationFn: createWorkspace,\n onSuccess: async () => {\n // The new workspace introduces a membership the existing access token\n // doesn't carry. Refresh so the next request includes it in the JWT\n // claim and passes the in-memory canAccess() check on the server.\n await refreshAuth();\n },\n });\n}\n"],"names":["workspaceResponseSchema","checkedApiRequest","listUserWorkspaces","userWorkspacesQueryKey","userWorkspacesQueryOptions","useMutation","useRefreshAuth","toWorkspace","createWorkspace","command","response","method","body","name","useCreateWorkspaceAuth","refreshAuth","mutationFn","onSuccess"],"mappings":"AAAA,SAAQA,uBAAuB,QAAO,8BAA8B;AACpE,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SACEC,kBAAkB,EAClBC,sBAAsB,EACtBC,0BAA0B,QACrB,gCAAgC;AACvC,SAAQC,WAAW,QAAO,wBAAwB;AAElD,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,WAAW,QAAO,wBAAwB;AAElD,OAAO,eAAeC,gBAAgBC,OAA+B;IACnE,MAAMC,WAAW,MAAMT,kBAAkBD,yBAAyB,eAAe;QAC/EW,QAAQ;QACRC,MAAM;YAACC,MAAMJ,QAAQI,IAAI;QAAA;IAC3B;IACA,OAAON,YAAYG;AACrB;AAEA,SAAQR,kBAAkB,EAAEC,sBAAsB,EAAEC,0BAA0B,GAAE;AAEhF,OAAO,SAASU;IACd,MAAMC,cAAcT;IAEpB,OAAOD,YAAY;QACjBW,YAAYR;QACZS,WAAW;YACT,sEAAsE;YACtE,oEAAoE;YACpE,kEAAkE;YAClE,MAAMF;QACR;IACF;AACF"}
@@ -1,8 +1,4 @@
1
1
  import type { WorkspaceResponseDto } from '@shipfox/api-workspaces-dto';
2
- export interface Workspace {
3
- id: string;
4
- name: string;
5
- status: 'active' | 'suspended' | 'deleted';
6
- }
2
+ import type { Workspace } from '#core/auth.js';
7
3
  export declare function toWorkspace(dto: WorkspaceResponseDto): Workspace;
8
4
  //# sourceMappingURL=workspace-mapper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-mapper.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/workspace-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,6BAA6B,CAAC;AAEtE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;CAC5C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,oBAAoB,GAAG,SAAS,CAEhE"}
1
+ {"version":3,"file":"workspace-mapper.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/workspace-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,eAAe,CAAC;AAE7C,wBAAgB,WAAW,CAAC,GAAG,EAAE,oBAAoB,GAAG,SAAS,CAEhE"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/workspace-mapper.ts"],"sourcesContent":["import type {WorkspaceResponseDto} from '@shipfox/api-workspaces-dto';\n\nexport interface Workspace {\n id: string;\n name: string;\n status: 'active' | 'suspended' | 'deleted';\n}\n\nexport function toWorkspace(dto: WorkspaceResponseDto): Workspace {\n return {id: dto.id, name: dto.name, status: dto.status};\n}\n"],"names":["toWorkspace","dto","id","name","status"],"mappings":"AAQA,OAAO,SAASA,YAAYC,GAAyB;IACnD,OAAO;QAACC,IAAID,IAAIC,EAAE;QAAEC,MAAMF,IAAIE,IAAI;QAAEC,QAAQH,IAAIG,MAAM;IAAA;AACxD"}
1
+ {"version":3,"sources":["../../../src/hooks/api/workspace-mapper.ts"],"sourcesContent":["import type {WorkspaceResponseDto} from '@shipfox/api-workspaces-dto';\nimport type {Workspace} from '#core/auth.js';\n\nexport function toWorkspace(dto: WorkspaceResponseDto): Workspace {\n return {id: dto.id, name: dto.name, status: dto.status};\n}\n"],"names":["toWorkspace","dto","id","name","status"],"mappings":"AAGA,OAAO,SAASA,YAAYC,GAAyB;IACnD,OAAO;QAACC,IAAID,IAAIC,EAAE;QAAEC,MAAMF,IAAIE,IAAI;QAAEC,QAAQH,IAAIG,MAAM;IAAA;AACxD"}
@@ -1 +1 @@
1
- {"version":3,"file":"login-page.d.ts","sourceRoot":"","sources":["../../src/pages/login-page.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,SAAS,gCAmKxB"}
1
+ {"version":3,"file":"login-page.d.ts","sourceRoot":"","sources":["../../src/pages/login-page.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,SAAS,gCAmKxB"}
@@ -1,24 +1,23 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { loginBodySchema } from '@shipfox/api-auth-dto';
3
- import { AuthShell } from '@shipfox/client-shell/runtime';
3
+ import { AuthShell, useRouteSearch } from '@shipfox/client-shell/runtime';
4
4
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
5
5
  import { Callout } from '@shipfox/react-ui/callout';
6
6
  import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
7
7
  import { Icon } from '@shipfox/react-ui/icon';
8
8
  import { Text } from '@shipfox/react-ui/typography';
9
9
  import { useForm } from '@tanstack/react-form';
10
- import { Link, useSearch } from '@tanstack/react-router';
10
+ import { Link } from '@tanstack/react-router';
11
11
  import { useAtom } from 'jotai';
12
12
  import { useEffect, useRef, useState } from 'react';
13
13
  import { useLoginAuth } from '#hooks/api/login-auth.js';
14
14
  import { authFormDraftAtom, initialAuthFormDraft } from '#state/auth.js';
15
+ import { validateRedirectSearch } from '../routes/inputs.js';
15
16
  import { loginErrorToFormError } from './form-errors.js';
16
17
  import { extractInvitationToken, pendingInvitation, useInvitationContext } from './invitation-context.js';
17
18
  export function LoginPage() {
18
19
  const login = useLoginAuth();
19
- const search = useSearch({
20
- strict: false
21
- });
20
+ const search = useRouteSearch(validateRedirectSearch);
22
21
  const invitationToken = extractInvitationToken(search.redirect);
23
22
  const invitationPreview = useInvitationContext(invitationToken);
24
23
  const invitationPending = pendingInvitation(invitationPreview.data);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/login-page.tsx"],"sourcesContent":["import {loginBodySchema} from '@shipfox/api-auth-dto';\nimport {AuthShell} from '@shipfox/client-shell/runtime';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, useSearch} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {useLoginAuth} from '#hooks/api/login-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {loginErrorToFormError} from './form-errors.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function LoginPage() {\n const login = useLoginAuth();\n const search = useSearch({strict: false}) as {redirect?: unknown};\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [formError, setFormError] = useState<string | undefined>();\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n // Set just before clearing the draft on success so the unmount cleanup\n // below does not repersist the just-submitted credentials.\n const skipDraftPersistRef = useRef(false);\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n await login.mutateAsync(value);\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n } catch (error) {\n const mapped = loginErrorToFormError(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 // Lock the email field when arriving from an invitation link so the user\n // can't log in as a different account than the one the invitation targets.\n useEffect(() => {\n if (invitationPending && form.state.values.email !== invitationPending.email) {\n form.setFieldValue('email', invitationPending.email);\n setAuthFormDraft((current) => ({...current, email: invitationPending.email}));\n }\n }, [invitationPending, form, setAuthFormDraft]);\n\n // Sync TanStack Form values back into the Jotai draft on unmount so a\n // navigation to /signup or /reset preserves what the user typed. Skipped\n // after a successful login because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Connect to Shipfox';\n const headerDescription = invitationPending\n ? 'Log in to accept your invitation.'\n : 'Log in to access Shipfox.';\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"email\"\n validators={{onBlur: loginBodySchema.shape.email, onSubmit: loginBodySchema.shape.email}}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n readOnly={isInvitationEmailLocked}\n iconRight={\n isInvitationEmailLocked ? (\n <Icon\n aria-hidden=\"true\"\n className=\"size-16 text-foreground-neutral-disabled\"\n name=\"lockLine\"\n />\n ) : undefined\n }\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"password\"\n validators={{\n onBlur: loginBodySchema.shape.password,\n onSubmit: loginBodySchema.shape.password,\n }}\n >\n {(field) => (\n <FormField label=\"Password\" id=\"password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"current-password\"\n name=\"password\"\n type=\"password\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n />\n </FormField>\n )}\n </form.Field>\n <ButtonLink asChild variant=\"subtle\" className=\"-mt-8 self-end\">\n <Link to=\"/auth/reset\">Forgot password?</Link>\n </ButtonLink>\n <Button className=\"w-full\" isLoading={login.isPending} type=\"submit\">\n {login.isPending ? 'Logging in...' : 'Log in'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n New to Shipfox?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link\n to=\"/auth/signup\"\n search={invitationRedirect ? {redirect: invitationRedirect} : undefined}\n >\n Create an account\n </Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n"],"names":["loginBodySchema","AuthShell","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","Text","useForm","Link","useSearch","useAtom","useEffect","useRef","useState","useLoginAuth","authFormDraftAtom","initialAuthFormDraft","loginErrorToFormError","extractInvitationToken","pendingInvitation","useInvitationContext","LoginPage","login","search","strict","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","formError","setFormError","draftRef","current","skipDraftPersistRef","form","defaultValues","email","password","onSubmit","value","undefined","mutateAsync","error","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","state","values","setFieldValue","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","headerTitle","workspaceName","headerDescription","persistDraft","title","description","className","noValidate","event","preventDefault","stopPropagation","handleSubmit","role","type","Field","name","validators","onBlur","shape","label","id","autoComplete","onChange","handleChange","target","handleBlur","readOnly","iconRight","aria-hidden","asChild","variant","to","isLoading","isPending","size","underline"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/login-page.tsx"],"sourcesContent":["import {loginBodySchema} from '@shipfox/api-auth-dto';\nimport {AuthShell, useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {useLoginAuth} from '#hooks/api/login-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {validateRedirectSearch} from '../routes/inputs.js';\nimport {loginErrorToFormError} from './form-errors.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function LoginPage() {\n const login = useLoginAuth();\n const search = useRouteSearch(validateRedirectSearch);\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [formError, setFormError] = useState<string | undefined>();\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n // Set just before clearing the draft on success so the unmount cleanup\n // below does not repersist the just-submitted credentials.\n const skipDraftPersistRef = useRef(false);\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n await login.mutateAsync(value);\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n } catch (error) {\n const mapped = loginErrorToFormError(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 // Lock the email field when arriving from an invitation link so the user\n // can't log in as a different account than the one the invitation targets.\n useEffect(() => {\n if (invitationPending && form.state.values.email !== invitationPending.email) {\n form.setFieldValue('email', invitationPending.email);\n setAuthFormDraft((current) => ({...current, email: invitationPending.email}));\n }\n }, [invitationPending, form, setAuthFormDraft]);\n\n // Sync TanStack Form values back into the Jotai draft on unmount so a\n // navigation to /signup or /reset preserves what the user typed. Skipped\n // after a successful login because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Connect to Shipfox';\n const headerDescription = invitationPending\n ? 'Log in to accept your invitation.'\n : 'Log in to access Shipfox.';\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"email\"\n validators={{onBlur: loginBodySchema.shape.email, onSubmit: loginBodySchema.shape.email}}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n readOnly={isInvitationEmailLocked}\n iconRight={\n isInvitationEmailLocked ? (\n <Icon\n aria-hidden=\"true\"\n className=\"size-16 text-foreground-neutral-disabled\"\n name=\"lockLine\"\n />\n ) : undefined\n }\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"password\"\n validators={{\n onBlur: loginBodySchema.shape.password,\n onSubmit: loginBodySchema.shape.password,\n }}\n >\n {(field) => (\n <FormField label=\"Password\" id=\"password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"current-password\"\n name=\"password\"\n type=\"password\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n />\n </FormField>\n )}\n </form.Field>\n <ButtonLink asChild variant=\"subtle\" className=\"-mt-8 self-end\">\n <Link to=\"/auth/reset\">Forgot password?</Link>\n </ButtonLink>\n <Button className=\"w-full\" isLoading={login.isPending} type=\"submit\">\n {login.isPending ? 'Logging in...' : 'Log in'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n New to Shipfox?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link\n to=\"/auth/signup\"\n search={invitationRedirect ? {redirect: invitationRedirect} : undefined}\n >\n Create an account\n </Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n"],"names":["loginBodySchema","AuthShell","useRouteSearch","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","Text","useForm","Link","useAtom","useEffect","useRef","useState","useLoginAuth","authFormDraftAtom","initialAuthFormDraft","validateRedirectSearch","loginErrorToFormError","extractInvitationToken","pendingInvitation","useInvitationContext","LoginPage","login","search","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","formError","setFormError","draftRef","current","skipDraftPersistRef","form","defaultValues","email","password","onSubmit","value","undefined","mutateAsync","error","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","state","values","setFieldValue","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","headerTitle","workspaceName","headerDescription","persistDraft","title","description","className","noValidate","event","preventDefault","stopPropagation","handleSubmit","role","type","Field","name","validators","onBlur","shape","label","id","autoComplete","onChange","handleChange","target","handleBlur","readOnly","iconRight","aria-hidden","asChild","variant","to","isLoading","isPending","size","underline"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"logout-page.d.ts","sourceRoot":"","sources":["../../src/pages/logout-page.tsx"],"names":[],"mappings":"AAOA,wBAAgB,UAAU,gCAyBzB"}
1
+ {"version":3,"file":"logout-page.d.ts","sourceRoot":"","sources":["../../src/pages/logout-page.tsx"],"names":[],"mappings":"AAQA,wBAAgB,UAAU,gCAoBzB"}
@@ -1,33 +1,23 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { AuthShell } from '@shipfox/client-shell/runtime';
2
+ import { AuthShell, useRouteSearch } from '@shipfox/client-shell/runtime';
3
3
  import { Text } from '@shipfox/react-ui/typography';
4
- import { useNavigate, useRouter, useSearch } from '@tanstack/react-router';
4
+ import { useRouter } from '@tanstack/react-router';
5
5
  import { useEffect } from 'react';
6
- import { sanitizeRedirectPath } from '#/components/redirect-target.js';
6
+ import { sanitizeLogoutRedirectPath } from '#/components/redirect-target.js';
7
7
  import { useLogoutAuth } from '#hooks/api/logout-auth.js';
8
+ import { validateRedirectSearch } from '../routes/inputs.js';
8
9
  export function LogoutPage() {
9
10
  const logout = useLogoutAuth();
10
- const navigate = useNavigate();
11
11
  const router = useRouter();
12
- const search = useSearch({
13
- strict: false
14
- });
15
- const target = sanitizeRedirectPath(search.redirect);
12
+ const search = useRouteSearch(validateRedirectSearch);
13
+ const target = sanitizeLogoutRedirectPath(search.redirect);
16
14
  useEffect(()=>{
17
15
  logout.mutateAsync().finally(()=>{
18
- if (target !== undefined) {
19
- // Same-origin runtime path; let history drive route resolution.
20
- router.history.replace(target);
21
- } else {
22
- navigate({
23
- to: '/auth/login',
24
- replace: true
25
- });
26
- }
16
+ // Same-origin runtime path; let history drive route resolution.
17
+ router.history.replace(target);
27
18
  });
28
19
  }, [
29
20
  logout.mutateAsync,
30
- navigate,
31
21
  router,
32
22
  target
33
23
  ]);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/logout-page.tsx"],"sourcesContent":["import {AuthShell} from '@shipfox/client-shell/runtime';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useNavigate, useRouter, useSearch} from '@tanstack/react-router';\nimport {useEffect} from 'react';\nimport {sanitizeRedirectPath} from '#/components/redirect-target.js';\nimport {useLogoutAuth} from '#hooks/api/logout-auth.js';\n\nexport function LogoutPage() {\n const logout = useLogoutAuth();\n const navigate = useNavigate();\n const router = useRouter();\n const search = useSearch({strict: false}) as {redirect?: unknown};\n const target = sanitizeRedirectPath(search.redirect);\n\n useEffect(() => {\n logout.mutateAsync().finally(() => {\n if (target !== undefined) {\n // Same-origin runtime path; let history drive route resolution.\n router.history.replace(target);\n } else {\n navigate({to: '/auth/login', replace: true});\n }\n });\n }, [logout.mutateAsync, navigate, router, target]);\n\n return (\n <AuthShell title=\"Logging out\" description=\"Ending your Shipfox session.\">\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n You will be sent back to login in a moment.\n </Text>\n </AuthShell>\n );\n}\n"],"names":["AuthShell","Text","useNavigate","useRouter","useSearch","useEffect","sanitizeRedirectPath","useLogoutAuth","LogoutPage","logout","navigate","router","search","strict","target","redirect","mutateAsync","finally","undefined","history","replace","to","title","description","size","className"],"mappings":";AAAA,SAAQA,SAAS,QAAO,gCAAgC;AACxD,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,WAAW,EAAEC,SAAS,EAAEC,SAAS,QAAO,yBAAyB;AACzE,SAAQC,SAAS,QAAO,QAAQ;AAChC,SAAQC,oBAAoB,QAAO,kCAAkC;AACrE,SAAQC,aAAa,QAAO,4BAA4B;AAExD,OAAO,SAASC;IACd,MAAMC,SAASF;IACf,MAAMG,WAAWR;IACjB,MAAMS,SAASR;IACf,MAAMS,SAASR,UAAU;QAACS,QAAQ;IAAK;IACvC,MAAMC,SAASR,qBAAqBM,OAAOG,QAAQ;IAEnDV,UAAU;QACRI,OAAOO,WAAW,GAAGC,OAAO,CAAC;YAC3B,IAAIH,WAAWI,WAAW;gBACxB,gEAAgE;gBAChEP,OAAOQ,OAAO,CAACC,OAAO,CAACN;YACzB,OAAO;gBACLJ,SAAS;oBAACW,IAAI;oBAAeD,SAAS;gBAAI;YAC5C;QACF;IACF,GAAG;QAACX,OAAOO,WAAW;QAAEN;QAAUC;QAAQG;KAAO;IAEjD,qBACE,KAACd;QAAUsB,OAAM;QAAcC,aAAY;kBACzC,cAAA,KAACtB;YAAKuB,MAAK;YAAKC,WAAU;sBAA6C;;;AAK7E"}
1
+ {"version":3,"sources":["../../src/pages/logout-page.tsx"],"sourcesContent":["import {AuthShell, useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useRouter} from '@tanstack/react-router';\nimport {useEffect} from 'react';\nimport {sanitizeLogoutRedirectPath} from '#/components/redirect-target.js';\nimport {useLogoutAuth} from '#hooks/api/logout-auth.js';\nimport {validateRedirectSearch} from '../routes/inputs.js';\n\nexport function LogoutPage() {\n const logout = useLogoutAuth();\n const router = useRouter();\n const search = useRouteSearch(validateRedirectSearch);\n const target = sanitizeLogoutRedirectPath(search.redirect);\n\n useEffect(() => {\n logout.mutateAsync().finally(() => {\n // Same-origin runtime path; let history drive route resolution.\n router.history.replace(target);\n });\n }, [logout.mutateAsync, router, target]);\n\n return (\n <AuthShell title=\"Logging out\" description=\"Ending your Shipfox session.\">\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n You will be sent back to login in a moment.\n </Text>\n </AuthShell>\n );\n}\n"],"names":["AuthShell","useRouteSearch","Text","useRouter","useEffect","sanitizeLogoutRedirectPath","useLogoutAuth","validateRedirectSearch","LogoutPage","logout","router","search","target","redirect","mutateAsync","finally","history","replace","title","description","size","className"],"mappings":";AAAA,SAAQA,SAAS,EAAEC,cAAc,QAAO,gCAAgC;AACxE,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,SAAS,QAAO,yBAAyB;AACjD,SAAQC,SAAS,QAAO,QAAQ;AAChC,SAAQC,0BAA0B,QAAO,kCAAkC;AAC3E,SAAQC,aAAa,QAAO,4BAA4B;AACxD,SAAQC,sBAAsB,QAAO,sBAAsB;AAE3D,OAAO,SAASC;IACd,MAAMC,SAASH;IACf,MAAMI,SAASP;IACf,MAAMQ,SAASV,eAAeM;IAC9B,MAAMK,SAASP,2BAA2BM,OAAOE,QAAQ;IAEzDT,UAAU;QACRK,OAAOK,WAAW,GAAGC,OAAO,CAAC;YAC3B,gEAAgE;YAChEL,OAAOM,OAAO,CAACC,OAAO,CAACL;QACzB;IACF,GAAG;QAACH,OAAOK,WAAW;QAAEJ;QAAQE;KAAO;IAEvC,qBACE,KAACZ;QAAUkB,OAAM;QAAcC,aAAY;kBACzC,cAAA,KAACjB;YAAKkB,MAAK;YAAKC,WAAU;sBAA6C;;;AAK7E"}
@@ -1 +1 @@
1
- {"version":3,"file":"password-reset-page.d.ts","sourceRoot":"","sources":["../../src/pages/password-reset-page.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,iBAAiB,gCAShC"}
1
+ {"version":3,"file":"password-reset-page.d.ts","sourceRoot":"","sources":["../../src/pages/password-reset-page.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,iBAAiB,gCAQhC"}
@@ -1,23 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { passwordResetConfirmBodySchema, passwordResetRequestBodySchema } from '@shipfox/api-auth-dto';
3
- import { AuthShell } from '@shipfox/client-shell/runtime';
3
+ import { AuthShell, useRouteSearch } from '@shipfox/client-shell/runtime';
4
4
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
5
5
  import { Callout } from '@shipfox/react-ui/callout';
6
6
  import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
7
7
  import { toast } from '@shipfox/react-ui/toast';
8
8
  import { Text } from '@shipfox/react-ui/typography';
9
9
  import { useForm } from '@tanstack/react-form';
10
- import { Link, useNavigate, useSearch } from '@tanstack/react-router';
10
+ import { Link, useNavigate } from '@tanstack/react-router';
11
11
  import { useAtom } from 'jotai';
12
12
  import { useEffect, useRef, useState } from 'react';
13
13
  import { useConfirmPasswordResetAuth, useRequestPasswordResetAuth } from '#hooks/api/password-reset-auth.js';
14
14
  import { authFormDraftAtom, initialAuthFormDraft } from '#state/auth.js';
15
+ import { validatePasswordResetSearch } from '../routes/inputs.js';
15
16
  import { passwordResetConfirmErrorToFormError, passwordResetRequestErrorToFormError } from './form-errors.js';
16
17
  export function PasswordResetPage() {
17
- const search = useSearch({
18
- strict: false
19
- });
20
- const token = typeof search.token === 'string' ? search.token : undefined;
18
+ const { token } = useRouteSearch(validatePasswordResetSearch);
21
19
  if (token) {
22
20
  return /*#__PURE__*/ _jsx(PasswordResetConfirm, {
23
21
  token: token
@@ -187,7 +185,10 @@ function PasswordResetConfirm({ token }) {
187
185
  token,
188
186
  new_password: value.new_password
189
187
  });
190
- await confirmPasswordReset.mutateAsync(body);
188
+ await confirmPasswordReset.mutateAsync({
189
+ token: body.token,
190
+ newPassword: body.new_password
191
+ });
191
192
  setAuthFormDraft(initialAuthFormDraft);
192
193
  toast.success('Your password has been changed. You are now logged in.');
193
194
  await navigate({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/password-reset-page.tsx"],"sourcesContent":["import {\n passwordResetConfirmBodySchema,\n passwordResetRequestBodySchema,\n} from '@shipfox/api-auth-dto';\nimport {AuthShell} from '@shipfox/client-shell/runtime';\nimport {Button, ButtonLink} 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 {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, useNavigate, useSearch} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {\n useConfirmPasswordResetAuth,\n useRequestPasswordResetAuth,\n} from '#hooks/api/password-reset-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {\n passwordResetConfirmErrorToFormError,\n passwordResetRequestErrorToFormError,\n} from './form-errors.js';\n\nexport function PasswordResetPage() {\n const search = useSearch({strict: false});\n const token = typeof search.token === 'string' ? search.token : undefined;\n\n if (token) {\n return <PasswordResetConfirm token={token} />;\n }\n\n return <PasswordResetRequest />;\n}\n\nfunction PasswordResetRequest() {\n const requestPasswordReset = useRequestPasswordResetAuth();\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [submittedEmail, setSubmittedEmail] = useState<string | undefined>();\n const [formError, setFormError] = useState<string | undefined>();\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const parsed = passwordResetRequestBodySchema.parse(value);\n await requestPasswordReset.mutateAsync(parsed);\n setSubmittedEmail(parsed.email);\n } catch (error) {\n const mapped = passwordResetRequestErrorToFormError(error);\n setFormError(mapped.message);\n }\n },\n });\n\n useEffect(() => {\n return () => {\n const {email} = form.state.values;\n if (email !== draftRef.current.email) {\n setAuthFormDraft((current) => ({...current, email}));\n }\n };\n }, [form, setAuthFormDraft]);\n\n if (submittedEmail) {\n return (\n <AuthShell\n title=\"Check your email\"\n description={`We sent reset instructions to ${submittedEmail}.`}\n >\n <Callout role=\"alert\" type=\"success\">\n If a Shipfox account exists for that email, the reset link will arrive shortly.\n </Callout>\n <Button\n className=\"w-full\"\n variant=\"secondary\"\n type=\"button\"\n onClick={() => setSubmittedEmail(undefined)}\n >\n Use another email\n </Button>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Remembered it?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n }\n\n return (\n <AuthShell title=\"Reset your password\" description=\"Enter your email to get a reset link.\">\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"email\"\n validators={{\n onBlur: passwordResetRequestBodySchema.shape.email,\n onSubmit: passwordResetRequestBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n setAuthFormDraft((current) => ({...current, email: field.state.value}));\n }}\n />\n </FormField>\n )}\n </form.Field>\n <Button className=\"w-full\" isLoading={requestPasswordReset.isPending} type=\"submit\">\n {requestPasswordReset.isPending ? 'Sending link...' : 'Send reset link'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Remembered it?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n\nfunction PasswordResetConfirm({token}: {token: string}) {\n const confirmPasswordReset = useConfirmPasswordResetAuth();\n const navigate = useNavigate();\n const [, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {new_password: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const body = passwordResetConfirmBodySchema.parse({\n token,\n new_password: value.new_password,\n });\n await confirmPasswordReset.mutateAsync(body);\n setAuthFormDraft(initialAuthFormDraft);\n toast.success('Your password has been changed. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n const mapped = passwordResetConfirmErrorToFormError(error);\n setFormError(mapped.message);\n }\n },\n });\n\n return (\n <AuthShell title=\"Set a new password\" description=\"Choose a password for your Shipfox account.\">\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"new_password\"\n validators={{\n onBlur: passwordResetConfirmBodySchema.shape.new_password,\n onSubmit: passwordResetConfirmBodySchema.shape.new_password,\n }}\n >\n {(field) => (\n <FormField label=\"New password\" id=\"new-password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"new-password\"\n name=\"new_password\"\n type=\"password\"\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 <Button className=\"w-full\" isLoading={confirmPasswordReset.isPending} type=\"submit\">\n {confirmPasswordReset.isPending ? 'Updating password...' : 'Update password'}\n </Button>\n </form>\n <ButtonLink asChild variant=\"subtle\" className=\"self-center\">\n <Link to=\"/auth/login\">Back to login</Link>\n </ButtonLink>\n </AuthShell>\n );\n}\n"],"names":["passwordResetConfirmBodySchema","passwordResetRequestBodySchema","AuthShell","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","toast","Text","useForm","Link","useNavigate","useSearch","useAtom","useEffect","useRef","useState","useConfirmPasswordResetAuth","useRequestPasswordResetAuth","authFormDraftAtom","initialAuthFormDraft","passwordResetConfirmErrorToFormError","passwordResetRequestErrorToFormError","PasswordResetPage","search","strict","token","undefined","PasswordResetConfirm","PasswordResetRequest","requestPasswordReset","authFormDraft","setAuthFormDraft","submittedEmail","setSubmittedEmail","formError","setFormError","draftRef","current","form","defaultValues","email","onSubmit","value","parsed","parse","mutateAsync","error","mapped","message","state","values","title","description","role","type","className","variant","onClick","size","asChild","underline","to","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","name","validators","onBlur","shape","field","label","id","autoComplete","onChange","handleChange","target","handleBlur","isLoading","isPending","confirmPasswordReset","navigate","new_password","body","success","replace"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/password-reset-page.tsx"],"sourcesContent":["import {\n passwordResetConfirmBodySchema,\n passwordResetRequestBodySchema,\n} from '@shipfox/api-auth-dto';\nimport {AuthShell, useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {Button, ButtonLink} 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 {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {\n useConfirmPasswordResetAuth,\n useRequestPasswordResetAuth,\n} from '#hooks/api/password-reset-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {validatePasswordResetSearch} from '../routes/inputs.js';\nimport {\n passwordResetConfirmErrorToFormError,\n passwordResetRequestErrorToFormError,\n} from './form-errors.js';\n\nexport function PasswordResetPage() {\n const {token} = useRouteSearch(validatePasswordResetSearch);\n\n if (token) {\n return <PasswordResetConfirm token={token} />;\n }\n\n return <PasswordResetRequest />;\n}\n\nfunction PasswordResetRequest() {\n const requestPasswordReset = useRequestPasswordResetAuth();\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [submittedEmail, setSubmittedEmail] = useState<string | undefined>();\n const [formError, setFormError] = useState<string | undefined>();\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const parsed = passwordResetRequestBodySchema.parse(value);\n await requestPasswordReset.mutateAsync(parsed);\n setSubmittedEmail(parsed.email);\n } catch (error) {\n const mapped = passwordResetRequestErrorToFormError(error);\n setFormError(mapped.message);\n }\n },\n });\n\n useEffect(() => {\n return () => {\n const {email} = form.state.values;\n if (email !== draftRef.current.email) {\n setAuthFormDraft((current) => ({...current, email}));\n }\n };\n }, [form, setAuthFormDraft]);\n\n if (submittedEmail) {\n return (\n <AuthShell\n title=\"Check your email\"\n description={`We sent reset instructions to ${submittedEmail}.`}\n >\n <Callout role=\"alert\" type=\"success\">\n If a Shipfox account exists for that email, the reset link will arrive shortly.\n </Callout>\n <Button\n className=\"w-full\"\n variant=\"secondary\"\n type=\"button\"\n onClick={() => setSubmittedEmail(undefined)}\n >\n Use another email\n </Button>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Remembered it?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n }\n\n return (\n <AuthShell title=\"Reset your password\" description=\"Enter your email to get a reset link.\">\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"email\"\n validators={{\n onBlur: passwordResetRequestBodySchema.shape.email,\n onSubmit: passwordResetRequestBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n setAuthFormDraft((current) => ({...current, email: field.state.value}));\n }}\n />\n </FormField>\n )}\n </form.Field>\n <Button className=\"w-full\" isLoading={requestPasswordReset.isPending} type=\"submit\">\n {requestPasswordReset.isPending ? 'Sending link...' : 'Send reset link'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Remembered it?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n\nfunction PasswordResetConfirm({token}: {token: string}) {\n const confirmPasswordReset = useConfirmPasswordResetAuth();\n const navigate = useNavigate();\n const [, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {new_password: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const body = passwordResetConfirmBodySchema.parse({\n token,\n new_password: value.new_password,\n });\n await confirmPasswordReset.mutateAsync({token: body.token, newPassword: body.new_password});\n setAuthFormDraft(initialAuthFormDraft);\n toast.success('Your password has been changed. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n const mapped = passwordResetConfirmErrorToFormError(error);\n setFormError(mapped.message);\n }\n },\n });\n\n return (\n <AuthShell title=\"Set a new password\" description=\"Choose a password for your Shipfox account.\">\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"new_password\"\n validators={{\n onBlur: passwordResetConfirmBodySchema.shape.new_password,\n onSubmit: passwordResetConfirmBodySchema.shape.new_password,\n }}\n >\n {(field) => (\n <FormField label=\"New password\" id=\"new-password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"new-password\"\n name=\"new_password\"\n type=\"password\"\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 <Button className=\"w-full\" isLoading={confirmPasswordReset.isPending} type=\"submit\">\n {confirmPasswordReset.isPending ? 'Updating password...' : 'Update password'}\n </Button>\n </form>\n <ButtonLink asChild variant=\"subtle\" className=\"self-center\">\n <Link to=\"/auth/login\">Back to login</Link>\n </ButtonLink>\n </AuthShell>\n );\n}\n"],"names":["passwordResetConfirmBodySchema","passwordResetRequestBodySchema","AuthShell","useRouteSearch","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","toast","Text","useForm","Link","useNavigate","useAtom","useEffect","useRef","useState","useConfirmPasswordResetAuth","useRequestPasswordResetAuth","authFormDraftAtom","initialAuthFormDraft","validatePasswordResetSearch","passwordResetConfirmErrorToFormError","passwordResetRequestErrorToFormError","PasswordResetPage","token","PasswordResetConfirm","PasswordResetRequest","requestPasswordReset","authFormDraft","setAuthFormDraft","submittedEmail","setSubmittedEmail","formError","setFormError","draftRef","current","form","defaultValues","email","onSubmit","value","undefined","parsed","parse","mutateAsync","error","mapped","message","state","values","title","description","role","type","className","variant","onClick","size","asChild","underline","to","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","name","validators","onBlur","shape","field","label","id","autoComplete","onChange","handleChange","target","handleBlur","isLoading","isPending","confirmPasswordReset","navigate","new_password","body","newPassword","success","replace"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"signup-page.d.ts","sourceRoot":"","sources":["../../src/pages/signup-page.tsx"],"names":[],"mappings":"AA0BA,wBAAgB,UAAU,gCA0RzB"}
1
+ {"version":3,"file":"signup-page.d.ts","sourceRoot":"","sources":["../../src/pages/signup-page.tsx"],"names":[],"mappings":"AA2BA,wBAAgB,UAAU,gCA+RzB"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { signupBodySchema } from '@shipfox/api-auth-dto';
3
- import { AuthShell } from '@shipfox/client-shell/runtime';
3
+ import { AuthShell, useRouteSearch } from '@shipfox/client-shell/runtime';
4
4
  import { displayNameFieldError } from '@shipfox/client-ui';
5
5
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
6
6
  import { Callout } from '@shipfox/react-ui/callout';
@@ -9,7 +9,7 @@ import { Icon } from '@shipfox/react-ui/icon';
9
9
  import { toast } from '@shipfox/react-ui/toast';
10
10
  import { Text } from '@shipfox/react-ui/typography';
11
11
  import { useForm } from '@tanstack/react-form';
12
- import { Link, useNavigate, useSearch } from '@tanstack/react-router';
12
+ import { Link, useNavigate } from '@tanstack/react-router';
13
13
  import { useAtom } from 'jotai';
14
14
  import { useEffect, useRef, useState } from 'react';
15
15
  import { EmailCodeVerification } from '#/components/email-code-verification.js';
@@ -17,6 +17,7 @@ import { useRefreshAuth } from '#hooks/api/refresh-auth.js';
17
17
  import { useSignupAuth } from '#hooks/api/signup-auth.js';
18
18
  import { useResendEmailVerificationAuth, useVerifyEmailAuth } from '#hooks/api/verify-email-auth.js';
19
19
  import { authFormDraftAtom, initialAuthFormDraft } from '#state/auth.js';
20
+ import { validateRedirectSearch } from '../routes/inputs.js';
20
21
  import { signupErrorToFormError } from './form-errors.js';
21
22
  import { authErrorMessage } from './form-utils.js';
22
23
  import { extractInvitationToken, pendingInvitation, useInvitationContext } from './invitation-context.js';
@@ -26,9 +27,7 @@ export function SignupPage() {
26
27
  const resendEmailVerification = useResendEmailVerificationAuth();
27
28
  const refreshAuth = useRefreshAuth();
28
29
  const navigate = useNavigate();
29
- const search = useSearch({
30
- strict: false
31
- });
30
+ const search = useRouteSearch(validateRedirectSearch);
32
31
  const invitationToken = extractInvitationToken(search.redirect);
33
32
  const invitationPreview = useInvitationContext(invitationToken);
34
33
  const invitationPending = pendingInvitation(invitationPreview.data);
@@ -51,7 +50,7 @@ export function SignupPage() {
51
50
  onSubmit: async ({ value })=>{
52
51
  setFormError(undefined);
53
52
  try {
54
- const body = signupBodySchema.parse({
53
+ const parsed = signupBodySchema.parse({
55
54
  email: value.email,
56
55
  password: value.password,
57
56
  name: value.name,
@@ -59,7 +58,14 @@ export function SignupPage() {
59
58
  invitation_token: invitationToken
60
59
  } : {}
61
60
  });
62
- const result = await signup.mutateAsync(body);
61
+ const result = await signup.mutateAsync({
62
+ email: parsed.email,
63
+ password: parsed.password,
64
+ name: parsed.name,
65
+ ...parsed.invitation_token ? {
66
+ invitationToken: parsed.invitation_token
67
+ } : {}
68
+ });
63
69
  skipDraftPersistRef.current = true;
64
70
  setAuthFormDraft(initialAuthFormDraft);
65
71
  if (invitationToken && result.membership && invitationPending) {
@@ -151,9 +157,9 @@ export function SignupPage() {
151
157
  try {
152
158
  const result = await resendEmailVerification.mutateAsync({
153
159
  email: emailChallenge.email,
154
- challenge_id: emailChallenge.id
160
+ challengeId: emailChallenge.id
155
161
  });
156
- setNextResendAvailableAt(result.next_resend_available_at);
162
+ setNextResendAvailableAt(result.nextResendAvailableAt);
157
163
  toast.success('If another verification email can be sent, it will arrive shortly.');
158
164
  } catch (error) {
159
165
  setResendError(authErrorMessage(error));
@@ -171,7 +177,7 @@ export function SignupPage() {
171
177
  try {
172
178
  await verifyEmail.mutateAsync({
173
179
  email: emailChallenge.email,
174
- challenge_id: emailChallenge.id,
180
+ challengeId: emailChallenge.id,
175
181
  code
176
182
  });
177
183
  await refreshAuth();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/signup-page.tsx"],"sourcesContent":["import {signupBodySchema} from '@shipfox/api-auth-dto';\nimport {AuthShell} from '@shipfox/client-shell/runtime';\nimport {displayNameFieldError} from '@shipfox/client-ui';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, useNavigate, useSearch} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {EmailCodeVerification} from '#/components/email-code-verification.js';\nimport {useRefreshAuth} from '#hooks/api/refresh-auth.js';\nimport {useSignupAuth} from '#hooks/api/signup-auth.js';\nimport {useResendEmailVerificationAuth, useVerifyEmailAuth} from '#hooks/api/verify-email-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {signupErrorToFormError} from './form-errors.js';\nimport {authErrorMessage} from './form-utils.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function SignupPage() {\n const signup = useSignupAuth();\n const verifyEmail = useVerifyEmailAuth();\n const resendEmailVerification = useResendEmailVerificationAuth();\n const refreshAuth = useRefreshAuth();\n const navigate = useNavigate();\n const search = useSearch({strict: false}) as {redirect?: unknown};\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [emailChallenge, setEmailChallenge] = useState<{email: string; id: string} | undefined>();\n const [nextResendAvailableAt, setNextResendAvailableAt] = useState<string | undefined>();\n const [formError, setFormError] = useState<string | undefined>();\n const [resendError, setResendError] = useState<string | undefined>();\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n // Set just before clearing the draft on success so the unmount cleanup\n // below does not repersist the just-submitted credentials.\n const skipDraftPersistRef = useRef(false);\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password, name: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const body = signupBodySchema.parse({\n email: value.email,\n password: value.password,\n name: value.name,\n ...(invitationToken ? {invitation_token: invitationToken} : {}),\n });\n const result = await signup.mutateAsync(body);\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n\n if (invitationToken && result.membership && invitationPending) {\n try {\n await refreshAuth();\n } catch {\n // Refresh failures don't block the success message — the next API\n // call's 401 handling will re-route the user.\n }\n toast.success(`You joined ${invitationPending.workspaceName}.`);\n await navigate({\n to: '/workspaces/$wid',\n params: {wid: result.membership.workspaceId},\n });\n return;\n }\n\n if (invitationToken && result.acceptError) {\n toast.error(result.acceptError.message);\n await navigate({\n to: '/invitations/accept',\n search: {token: invitationToken},\n });\n return;\n }\n\n if (!result.emailChallenge) {\n throw new Error('Signup did not return an email verification challenge');\n }\n setEmailChallenge({email: result.user.email, id: result.emailChallenge.id});\n setResendError(undefined);\n setNextResendAvailableAt(result.emailChallenge.nextResendAvailableAt);\n } catch (error) {\n const mapped = signupErrorToFormError(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 // When arriving from an invitation link, prefill the email and lock it.\n useEffect(() => {\n if (invitationPending && form.state.values.email !== invitationPending.email) {\n form.setFieldValue('email', invitationPending.email);\n setAuthFormDraft((current) => ({...current, email: invitationPending.email}));\n }\n }, [invitationPending, form, setAuthFormDraft]);\n\n // Sync form values back to the Jotai draft on unmount (only email + password\n // — name is intentionally not persisted across navigation). Skipped after a\n // successful signup because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n async function onResendVerificationEmail() {\n if (!emailChallenge || resendEmailVerification.isPending) return;\n\n setResendError(undefined);\n try {\n const result = await resendEmailVerification.mutateAsync({\n email: emailChallenge.email,\n challenge_id: emailChallenge.id,\n });\n setNextResendAvailableAt(result.next_resend_available_at);\n toast.success('If another verification email can be sent, it will arrive shortly.');\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }\n\n if (emailChallenge) {\n return (\n <EmailCodeVerification\n destination={emailChallenge.email}\n nextResendAvailableAt={nextResendAvailableAt}\n isResending={resendEmailVerification.isPending}\n isVerifying={verifyEmail.isPending}\n error={resendError}\n onVerify={async (code) => {\n setResendError(undefined);\n try {\n await verifyEmail.mutateAsync({\n email: emailChallenge.email,\n challenge_id: emailChallenge.id,\n code,\n });\n await refreshAuth();\n toast.success('Your email is verified. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }}\n onResend={onResendVerificationEmail}\n onUseAnotherEmail={() => {\n setEmailChallenge(undefined);\n setResendError(undefined);\n }}\n >\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already verified?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </EmailCodeVerification>\n );\n }\n\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Create your Shipfox account';\n const headerDescription = invitationPending\n ? `Create an account to accept your invitation.`\n : 'Start with your email and a password.';\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) => displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Name\" id=\"name\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"name\"\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 <form.Field\n name=\"email\"\n validators={{\n onBlur: signupBodySchema.shape.email,\n onSubmit: signupBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n readOnly={isInvitationEmailLocked}\n iconRight={\n isInvitationEmailLocked ? (\n <Icon\n aria-hidden=\"true\"\n className=\"size-16 text-foreground-neutral-disabled\"\n name=\"lockLine\"\n />\n ) : undefined\n }\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"password\"\n validators={{\n onBlur: signupBodySchema.shape.password,\n onSubmit: signupBodySchema.shape.password,\n }}\n >\n {(field) => (\n <FormField label=\"Password\" id=\"password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"new-password\"\n name=\"password\"\n type=\"password\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n />\n </FormField>\n )}\n </form.Field>\n <Button className=\"w-full\" isLoading={signup.isPending} type=\"submit\">\n {signup.isPending ? 'Creating account...' : 'Create account'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already have an account?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link\n to=\"/auth/login\"\n search={invitationRedirect ? {redirect: invitationRedirect} : undefined}\n >\n Log in\n </Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n"],"names":["signupBodySchema","AuthShell","displayNameFieldError","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","Link","useNavigate","useSearch","useAtom","useEffect","useRef","useState","EmailCodeVerification","useRefreshAuth","useSignupAuth","useResendEmailVerificationAuth","useVerifyEmailAuth","authFormDraftAtom","initialAuthFormDraft","signupErrorToFormError","authErrorMessage","extractInvitationToken","pendingInvitation","useInvitationContext","SignupPage","signup","verifyEmail","resendEmailVerification","refreshAuth","navigate","search","strict","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","emailChallenge","setEmailChallenge","nextResendAvailableAt","setNextResendAvailableAt","formError","setFormError","resendError","setResendError","draftRef","current","skipDraftPersistRef","form","defaultValues","email","password","name","onSubmit","value","undefined","body","parse","invitation_token","result","mutateAsync","membership","success","workspaceName","to","params","wid","workspaceId","acceptError","error","message","token","Error","user","id","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","state","values","setFieldValue","onResendVerificationEmail","isPending","challenge_id","next_resend_available_at","destination","isResending","isVerifying","onVerify","code","replace","onResend","onUseAnotherEmail","size","className","asChild","variant","underline","headerTitle","headerDescription","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","persistDraft","title","description","noValidate","event","preventDefault","stopPropagation","handleSubmit","role","type","Field","validators","onBlur","shape","label","autoComplete","onChange","handleChange","target","handleBlur","readOnly","iconRight","aria-hidden","isLoading"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/signup-page.tsx"],"sourcesContent":["import {signupBodySchema} from '@shipfox/api-auth-dto';\nimport {AuthShell, useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {displayNameFieldError} from '@shipfox/client-ui';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {EmailCodeVerification} from '#/components/email-code-verification.js';\nimport {useRefreshAuth} from '#hooks/api/refresh-auth.js';\nimport {useSignupAuth} from '#hooks/api/signup-auth.js';\nimport {useResendEmailVerificationAuth, useVerifyEmailAuth} from '#hooks/api/verify-email-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {validateRedirectSearch} from '../routes/inputs.js';\nimport {signupErrorToFormError} from './form-errors.js';\nimport {authErrorMessage} from './form-utils.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function SignupPage() {\n const signup = useSignupAuth();\n const verifyEmail = useVerifyEmailAuth();\n const resendEmailVerification = useResendEmailVerificationAuth();\n const refreshAuth = useRefreshAuth();\n const navigate = useNavigate();\n const search = useRouteSearch(validateRedirectSearch);\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [emailChallenge, setEmailChallenge] = useState<{email: string; id: string} | undefined>();\n const [nextResendAvailableAt, setNextResendAvailableAt] = useState<string | undefined>();\n const [formError, setFormError] = useState<string | undefined>();\n const [resendError, setResendError] = useState<string | undefined>();\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n // Set just before clearing the draft on success so the unmount cleanup\n // below does not repersist the just-submitted credentials.\n const skipDraftPersistRef = useRef(false);\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password, name: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const parsed = signupBodySchema.parse({\n email: value.email,\n password: value.password,\n name: value.name,\n ...(invitationToken ? {invitation_token: invitationToken} : {}),\n });\n const result = await signup.mutateAsync({\n email: parsed.email,\n password: parsed.password,\n name: parsed.name,\n ...(parsed.invitation_token ? {invitationToken: parsed.invitation_token} : {}),\n });\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n\n if (invitationToken && result.membership && invitationPending) {\n try {\n await refreshAuth();\n } catch {\n // Refresh failures don't block the success message — the next API\n // call's 401 handling will re-route the user.\n }\n toast.success(`You joined ${invitationPending.workspaceName}.`);\n await navigate({\n to: '/workspaces/$wid',\n params: {wid: result.membership.workspaceId},\n });\n return;\n }\n\n if (invitationToken && result.acceptError) {\n toast.error(result.acceptError.message);\n await navigate({\n to: '/invitations/accept',\n search: {token: invitationToken},\n });\n return;\n }\n\n if (!result.emailChallenge) {\n throw new Error('Signup did not return an email verification challenge');\n }\n setEmailChallenge({email: result.user.email, id: result.emailChallenge.id});\n setResendError(undefined);\n setNextResendAvailableAt(result.emailChallenge.nextResendAvailableAt);\n } catch (error) {\n const mapped = signupErrorToFormError(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 // When arriving from an invitation link, prefill the email and lock it.\n useEffect(() => {\n if (invitationPending && form.state.values.email !== invitationPending.email) {\n form.setFieldValue('email', invitationPending.email);\n setAuthFormDraft((current) => ({...current, email: invitationPending.email}));\n }\n }, [invitationPending, form, setAuthFormDraft]);\n\n // Sync form values back to the Jotai draft on unmount (only email + password\n // — name is intentionally not persisted across navigation). Skipped after a\n // successful signup because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n async function onResendVerificationEmail() {\n if (!emailChallenge || resendEmailVerification.isPending) return;\n\n setResendError(undefined);\n try {\n const result = await resendEmailVerification.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n });\n setNextResendAvailableAt(result.nextResendAvailableAt);\n toast.success('If another verification email can be sent, it will arrive shortly.');\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }\n\n if (emailChallenge) {\n return (\n <EmailCodeVerification\n destination={emailChallenge.email}\n nextResendAvailableAt={nextResendAvailableAt}\n isResending={resendEmailVerification.isPending}\n isVerifying={verifyEmail.isPending}\n error={resendError}\n onVerify={async (code) => {\n setResendError(undefined);\n try {\n await verifyEmail.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n code,\n });\n await refreshAuth();\n toast.success('Your email is verified. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }}\n onResend={onResendVerificationEmail}\n onUseAnotherEmail={() => {\n setEmailChallenge(undefined);\n setResendError(undefined);\n }}\n >\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already verified?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </EmailCodeVerification>\n );\n }\n\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Create your Shipfox account';\n const headerDescription = invitationPending\n ? `Create an account to accept your invitation.`\n : 'Start with your email and a password.';\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) => displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Name\" id=\"name\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"name\"\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 <form.Field\n name=\"email\"\n validators={{\n onBlur: signupBodySchema.shape.email,\n onSubmit: signupBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n readOnly={isInvitationEmailLocked}\n iconRight={\n isInvitationEmailLocked ? (\n <Icon\n aria-hidden=\"true\"\n className=\"size-16 text-foreground-neutral-disabled\"\n name=\"lockLine\"\n />\n ) : undefined\n }\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"password\"\n validators={{\n onBlur: signupBodySchema.shape.password,\n onSubmit: signupBodySchema.shape.password,\n }}\n >\n {(field) => (\n <FormField label=\"Password\" id=\"password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"new-password\"\n name=\"password\"\n type=\"password\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n />\n </FormField>\n )}\n </form.Field>\n <Button className=\"w-full\" isLoading={signup.isPending} type=\"submit\">\n {signup.isPending ? 'Creating account...' : 'Create account'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already have an account?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link\n to=\"/auth/login\"\n search={invitationRedirect ? {redirect: invitationRedirect} : undefined}\n >\n Log in\n </Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n"],"names":["signupBodySchema","AuthShell","useRouteSearch","displayNameFieldError","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","Link","useNavigate","useAtom","useEffect","useRef","useState","EmailCodeVerification","useRefreshAuth","useSignupAuth","useResendEmailVerificationAuth","useVerifyEmailAuth","authFormDraftAtom","initialAuthFormDraft","validateRedirectSearch","signupErrorToFormError","authErrorMessage","extractInvitationToken","pendingInvitation","useInvitationContext","SignupPage","signup","verifyEmail","resendEmailVerification","refreshAuth","navigate","search","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","emailChallenge","setEmailChallenge","nextResendAvailableAt","setNextResendAvailableAt","formError","setFormError","resendError","setResendError","draftRef","current","skipDraftPersistRef","form","defaultValues","email","password","name","onSubmit","value","undefined","parsed","parse","invitation_token","result","mutateAsync","membership","success","workspaceName","to","params","wid","workspaceId","acceptError","error","message","token","Error","user","id","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","state","values","setFieldValue","onResendVerificationEmail","isPending","challengeId","destination","isResending","isVerifying","onVerify","code","replace","onResend","onUseAnotherEmail","size","className","asChild","variant","underline","headerTitle","headerDescription","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","persistDraft","title","description","noValidate","event","preventDefault","stopPropagation","handleSubmit","role","type","Field","validators","onBlur","shape","label","autoComplete","onChange","handleChange","target","handleBlur","readOnly","iconRight","aria-hidden","isLoading"],"mappings":"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"}
@@ -79,10 +79,10 @@ export function WorkspaceOnboardingPage() {
79
79
  onSubmit: async ({ value })=>{
80
80
  setFormError(undefined);
81
81
  try {
82
- const body = createWorkspaceBodySchema.parse({
82
+ const command = createWorkspaceBodySchema.parse({
83
83
  name: value.name
84
84
  });
85
- const created = await createWorkspace.mutateAsync(body);
85
+ const created = await createWorkspace.mutateAsync(command);
86
86
  toast.success('Workspace created.');
87
87
  // Pin the new workspace as the last-active one so a page refresh and
88
88
  // future visits to `/` land on it.