@shipfox/client-auth 5.0.0 → 6.0.2

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 (120) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +57 -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/components/workspace-switcher.d.ts.map +1 -1
  11. package/dist/components/workspace-switcher.js +3 -2
  12. package/dist/components/workspace-switcher.js.map +1 -1
  13. package/dist/core/auth.d.ts +57 -0
  14. package/dist/core/auth.d.ts.map +1 -0
  15. package/dist/core/auth.js +3 -0
  16. package/dist/core/auth.js.map +1 -0
  17. package/dist/hooks/api/auth-mapper.d.ts +2 -17
  18. package/dist/hooks/api/auth-mapper.d.ts.map +1 -1
  19. package/dist/hooks/api/auth-mapper.js.map +1 -1
  20. package/dist/hooks/api/login-auth.d.ts +3 -6
  21. package/dist/hooks/api/login-auth.d.ts.map +1 -1
  22. package/dist/hooks/api/login-auth.js +3 -3
  23. package/dist/hooks/api/login-auth.js.map +1 -1
  24. package/dist/hooks/api/logout-auth.js +3 -3
  25. package/dist/hooks/api/logout-auth.js.map +1 -1
  26. package/dist/hooks/api/password-reset-auth.d.ts +5 -10
  27. package/dist/hooks/api/password-reset-auth.d.ts.map +1 -1
  28. package/dist/hooks/api/password-reset-auth.js +10 -7
  29. package/dist/hooks/api/password-reset-auth.js.map +1 -1
  30. package/dist/hooks/api/refresh-auth.d.ts +1 -1
  31. package/dist/hooks/api/refresh-auth.d.ts.map +1 -1
  32. package/dist/hooks/api/refresh-auth.js +1 -1
  33. package/dist/hooks/api/refresh-auth.js.map +1 -1
  34. package/dist/hooks/api/signup-auth.d.ts +2 -6
  35. package/dist/hooks/api/signup-auth.d.ts.map +1 -1
  36. package/dist/hooks/api/signup-auth.js +9 -2
  37. package/dist/hooks/api/signup-auth.js.map +1 -1
  38. package/dist/hooks/api/verify-email-auth.d.ts +4 -10
  39. package/dist/hooks/api/verify-email-auth.d.ts.map +1 -1
  40. package/dist/hooks/api/verify-email-auth.js +16 -6
  41. package/dist/hooks/api/verify-email-auth.js.map +1 -1
  42. package/dist/hooks/api/workspace-auth.d.ts +5 -7
  43. package/dist/hooks/api/workspace-auth.d.ts.map +1 -1
  44. package/dist/hooks/api/workspace-auth.js +6 -4
  45. package/dist/hooks/api/workspace-auth.js.map +1 -1
  46. package/dist/hooks/api/workspace-mapper.d.ts +1 -5
  47. package/dist/hooks/api/workspace-mapper.d.ts.map +1 -1
  48. package/dist/hooks/api/workspace-mapper.js.map +1 -1
  49. package/dist/pages/login-page.d.ts.map +1 -1
  50. package/dist/pages/login-page.js +4 -5
  51. package/dist/pages/login-page.js.map +1 -1
  52. package/dist/pages/logout-page.d.ts.map +1 -1
  53. package/dist/pages/logout-page.js +8 -18
  54. package/dist/pages/logout-page.js.map +1 -1
  55. package/dist/pages/password-reset-page.d.ts.map +1 -1
  56. package/dist/pages/password-reset-page.js +8 -7
  57. package/dist/pages/password-reset-page.js.map +1 -1
  58. package/dist/pages/signup-page.d.ts.map +1 -1
  59. package/dist/pages/signup-page.js +16 -10
  60. package/dist/pages/signup-page.js.map +1 -1
  61. package/dist/pages/workspace-onboarding-page.d.ts.map +1 -1
  62. package/dist/pages/workspace-onboarding-page.js +6 -3
  63. package/dist/pages/workspace-onboarding-page.js.map +1 -1
  64. package/dist/routes/index.d.ts.map +1 -1
  65. package/dist/routes/index.js +4 -3
  66. package/dist/routes/index.js.map +1 -1
  67. package/dist/routes/inputs.d.ts +9 -0
  68. package/dist/routes/inputs.d.ts.map +1 -0
  69. package/dist/routes/inputs.js +17 -0
  70. package/dist/routes/inputs.js.map +1 -0
  71. package/dist/routes/login.d.ts +2 -0
  72. package/dist/routes/login.d.ts.map +1 -1
  73. package/dist/routes/login.js +2 -0
  74. package/dist/routes/login.js.map +1 -1
  75. package/dist/routes/logout.d.ts +2 -0
  76. package/dist/routes/logout.d.ts.map +1 -1
  77. package/dist/routes/logout.js +2 -0
  78. package/dist/routes/logout.js.map +1 -1
  79. package/dist/routes/reset.d.ts +2 -0
  80. package/dist/routes/reset.d.ts.map +1 -1
  81. package/dist/routes/reset.js +2 -0
  82. package/dist/routes/reset.js.map +1 -1
  83. package/dist/routes/signup.d.ts +2 -0
  84. package/dist/routes/signup.d.ts.map +1 -1
  85. package/dist/routes/signup.js +2 -0
  86. package/dist/routes/signup.js.map +1 -1
  87. package/dist/tsconfig.test.tsbuildinfo +1 -1
  88. package/package.json +11 -12
  89. package/src/components/auth-guard.tsx +4 -2
  90. package/src/components/auth-provider.test.tsx +132 -19
  91. package/src/components/redirect-target.test.ts +32 -1
  92. package/src/components/redirect-target.ts +41 -4
  93. package/src/components/workspace-switcher.tsx +3 -2
  94. package/src/core/auth.ts +55 -0
  95. package/src/hooks/api/auth-mapper.ts +2 -12
  96. package/src/hooks/api/login-auth.ts +5 -4
  97. package/src/hooks/api/logout-auth.ts +3 -3
  98. package/src/hooks/api/password-reset-auth.test.ts +2 -2
  99. package/src/hooks/api/password-reset-auth.ts +11 -11
  100. package/src/hooks/api/refresh-auth.ts +1 -0
  101. package/src/hooks/api/signup-auth.ts +9 -3
  102. package/src/hooks/api/verify-email-auth.ts +20 -10
  103. package/src/hooks/api/workspace-auth.ts +10 -5
  104. package/src/hooks/api/workspace-mapper.ts +1 -6
  105. package/src/pages/login-page.tsx +4 -3
  106. package/src/pages/logout-page.test.tsx +75 -0
  107. package/src/pages/logout-page.tsx +9 -13
  108. package/src/pages/password-reset-page.tsx +5 -5
  109. package/src/pages/signup-page.tsx +14 -8
  110. package/src/pages/workspace-onboarding-page.tsx +6 -3
  111. package/src/routes/index.tsx +5 -3
  112. package/src/routes/inputs.test.ts +17 -0
  113. package/src/routes/inputs.ts +21 -0
  114. package/src/routes/login.tsx +2 -0
  115. package/src/routes/logout.tsx +2 -1
  116. package/src/routes/reset.tsx +2 -0
  117. package/src/routes/signup.tsx +2 -0
  118. package/src/state/last-workspace.test.ts +21 -28
  119. package/tsconfig.build.tsbuildinfo +1 -1
  120. package/vercel.json +5 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/signup-auth.ts"],"sourcesContent":["import {type SignupBodyDto, signupResponseSchema} from '@shipfox/api-auth-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {useMutation} from '@tanstack/react-query';\nimport {toSignupResult} from './auth-mapper.js';\n\nasync function signupAuth(body: SignupBodyDto) {\n const response = await checkedApiRequest(signupResponseSchema, '/auth/signup', {\n method: 'POST',\n body,\n });\n return toSignupResult(response);\n}\n\nexport function useSignupAuth() {\n return useMutation({mutationFn: signupAuth});\n}\n"],"names":["signupResponseSchema","checkedApiRequest","useMutation","toSignupResult","signupAuth","body","response","method","useSignupAuth","mutationFn"],"mappings":"AAAA,SAA4BA,oBAAoB,QAAO,wBAAwB;AAC/E,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,cAAc,QAAO,mBAAmB;AAEhD,eAAeC,WAAWC,IAAmB;IAC3C,MAAMC,WAAW,MAAML,kBAAkBD,sBAAsB,gBAAgB;QAC7EO,QAAQ;QACRF;IACF;IACA,OAAOF,eAAeG;AACxB;AAEA,OAAO,SAASE;IACd,OAAON,YAAY;QAACO,YAAYL;IAAU;AAC5C"}
1
+ {"version":3,"sources":["../../../src/hooks/api/signup-auth.ts"],"sourcesContent":["import {signupResponseSchema} from '@shipfox/api-auth-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {useMutation} from '@tanstack/react-query';\nimport type {SignupCommand} from '#core/auth.js';\nimport {toSignupResult} from './auth-mapper.js';\n\nasync function signupAuth(command: SignupCommand) {\n const response = await checkedApiRequest(signupResponseSchema, '/auth/signup', {\n method: 'POST',\n body: {\n email: command.email,\n password: command.password,\n name: command.name,\n ...(command.invitationToken ? {invitation_token: command.invitationToken} : {}),\n },\n });\n return toSignupResult(response);\n}\n\nexport function useSignupAuth() {\n return useMutation({mutationFn: signupAuth});\n}\n"],"names":["signupResponseSchema","checkedApiRequest","useMutation","toSignupResult","signupAuth","command","response","method","body","email","password","name","invitationToken","invitation_token","useSignupAuth","mutationFn"],"mappings":"AAAA,SAAQA,oBAAoB,QAAO,wBAAwB;AAC3D,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAQC,WAAW,QAAO,wBAAwB;AAElD,SAAQC,cAAc,QAAO,mBAAmB;AAEhD,eAAeC,WAAWC,OAAsB;IAC9C,MAAMC,WAAW,MAAML,kBAAkBD,sBAAsB,gBAAgB;QAC7EO,QAAQ;QACRC,MAAM;YACJC,OAAOJ,QAAQI,KAAK;YACpBC,UAAUL,QAAQK,QAAQ;YAC1BC,MAAMN,QAAQM,IAAI;YAClB,GAAIN,QAAQO,eAAe,GAAG;gBAACC,kBAAkBR,QAAQO,eAAe;YAAA,IAAI,CAAC,CAAC;QAChF;IACF;IACA,OAAOT,eAAeG;AACxB;AAEA,OAAO,SAASQ;IACd,OAAOZ,YAAY;QAACa,YAAYX;IAAU;AAC5C"}
@@ -1,12 +1,6 @@
1
+ import type { ResendEmailVerificationCommand, VerifyEmailCommand } from '#core/auth.js';
1
2
  export declare function useResendEmailVerificationAuth(): import("@tanstack/react-query").UseMutationResult<{
2
- next_resend_available_at: string;
3
- }, Error, {
4
- email: string;
5
- challenge_id: string;
6
- }, unknown>;
7
- export declare function useVerifyEmailAuth(): import("@tanstack/react-query").UseMutationResult<import("@shipfox/client-shell/runtime").AuthenticatedSession, Error, {
8
- email: string;
9
- challenge_id: string;
10
- code: string;
11
- }, unknown>;
3
+ nextResendAvailableAt: string;
4
+ }, Error, ResendEmailVerificationCommand, unknown>;
5
+ export declare function useVerifyEmailAuth(): import("@tanstack/react-query").UseMutationResult<import("@shipfox/client-shell/runtime").AuthenticatedSession, Error, VerifyEmailCommand, unknown>;
12
6
  //# sourceMappingURL=verify-email-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"verify-email-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/verify-email-auth.ts"],"names":[],"mappings":"AA8BA,wBAAgB,8BAA8B;;;;;YAE7C;AAED,wBAAgB,kBAAkB;;;;YAOjC"}
1
+ {"version":3,"file":"verify-email-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/verify-email-auth.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,8BAA8B,EAC9B,kBAAkB,EACnB,MAAM,eAAe,CAAC;AA8BvB,wBAAgB,8BAA8B;;mDAE7C;AAED,wBAAgB,kBAAkB,wJAOjC"}
@@ -3,18 +3,28 @@ import { checkedApiRequest } from '@shipfox/client-api';
3
3
  import { useMutation } from '@tanstack/react-query';
4
4
  import { useAuthTransition } from '#state/auth.js';
5
5
  import { toAuthenticatedSession } from './auth-mapper.js';
6
- async function verifyEmailAuth(body) {
6
+ async function verifyEmailAuth(command) {
7
7
  const response = await checkedApiRequest(verifyEmailConfirmResponseSchema, '/auth/verify-email/confirm', {
8
8
  method: 'POST',
9
- body
9
+ body: {
10
+ email: command.email,
11
+ challenge_id: command.challengeId,
12
+ code: command.code
13
+ }
10
14
  });
11
15
  return toAuthenticatedSession(response);
12
16
  }
13
- async function resendEmailVerificationAuth(body) {
14
- return await checkedApiRequest(verifyEmailResendResponseSchema, '/auth/verify-email/resend', {
17
+ async function resendEmailVerificationAuth(command) {
18
+ const response = await checkedApiRequest(verifyEmailResendResponseSchema, '/auth/verify-email/resend', {
15
19
  method: 'POST',
16
- body
20
+ body: {
21
+ email: command.email,
22
+ challenge_id: command.challengeId
23
+ }
17
24
  });
25
+ return {
26
+ nextResendAvailableAt: response.next_resend_available_at
27
+ };
18
28
  }
19
29
  export function useResendEmailVerificationAuth() {
20
30
  return useMutation({
@@ -25,7 +35,7 @@ export function useVerifyEmailAuth() {
25
35
  const { enterAuthenticated } = useAuthTransition();
26
36
  return useMutation({
27
37
  mutationFn: verifyEmailAuth,
28
- onSuccess: enterAuthenticated
38
+ onSuccess: (session)=>enterAuthenticated(session)
29
39
  });
30
40
  }
31
41
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/verify-email-auth.ts"],"sourcesContent":["import {\n type VerifyEmailConfirmBodyDto,\n type VerifyEmailResendBodyDto,\n verifyEmailConfirmResponseSchema,\n verifyEmailResendResponseSchema,\n} from '@shipfox/api-auth-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {useMutation} from '@tanstack/react-query';\nimport {useAuthTransition} from '#state/auth.js';\nimport {toAuthenticatedSession} from './auth-mapper.js';\n\nasync function verifyEmailAuth(body: VerifyEmailConfirmBodyDto) {\n const response = await checkedApiRequest(\n verifyEmailConfirmResponseSchema,\n '/auth/verify-email/confirm',\n {\n method: 'POST',\n body,\n },\n );\n return toAuthenticatedSession(response);\n}\n\nasync function resendEmailVerificationAuth(body: VerifyEmailResendBodyDto) {\n return await checkedApiRequest(verifyEmailResendResponseSchema, '/auth/verify-email/resend', {\n method: 'POST',\n body,\n });\n}\n\nexport function useResendEmailVerificationAuth() {\n return useMutation({mutationFn: resendEmailVerificationAuth});\n}\n\nexport function useVerifyEmailAuth() {\n const {enterAuthenticated} = useAuthTransition();\n\n return useMutation({\n mutationFn: verifyEmailAuth,\n onSuccess: enterAuthenticated,\n });\n}\n"],"names":["verifyEmailConfirmResponseSchema","verifyEmailResendResponseSchema","checkedApiRequest","useMutation","useAuthTransition","toAuthenticatedSession","verifyEmailAuth","body","response","method","resendEmailVerificationAuth","useResendEmailVerificationAuth","mutationFn","useVerifyEmailAuth","enterAuthenticated","onSuccess"],"mappings":"AAAA,SAGEA,gCAAgC,EAChCC,+BAA+B,QAC1B,wBAAwB;AAC/B,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,iBAAiB,QAAO,iBAAiB;AACjD,SAAQC,sBAAsB,QAAO,mBAAmB;AAExD,eAAeC,gBAAgBC,IAA+B;IAC5D,MAAMC,WAAW,MAAMN,kBACrBF,kCACA,8BACA;QACES,QAAQ;QACRF;IACF;IAEF,OAAOF,uBAAuBG;AAChC;AAEA,eAAeE,4BAA4BH,IAA8B;IACvE,OAAO,MAAML,kBAAkBD,iCAAiC,6BAA6B;QAC3FQ,QAAQ;QACRF;IACF;AACF;AAEA,OAAO,SAASI;IACd,OAAOR,YAAY;QAACS,YAAYF;IAA2B;AAC7D;AAEA,OAAO,SAASG;IACd,MAAM,EAACC,kBAAkB,EAAC,GAAGV;IAE7B,OAAOD,YAAY;QACjBS,YAAYN;QACZS,WAAWD;IACb;AACF"}
1
+ {"version":3,"sources":["../../../src/hooks/api/verify-email-auth.ts"],"sourcesContent":["import {\n verifyEmailConfirmResponseSchema,\n verifyEmailResendResponseSchema,\n} from '@shipfox/api-auth-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {useMutation} from '@tanstack/react-query';\nimport type {\n EmailVerificationResendResult,\n ResendEmailVerificationCommand,\n VerifyEmailCommand,\n} from '#core/auth.js';\nimport {useAuthTransition} from '#state/auth.js';\nimport {toAuthenticatedSession} from './auth-mapper.js';\n\nasync function verifyEmailAuth(command: VerifyEmailCommand) {\n const response = await checkedApiRequest(\n verifyEmailConfirmResponseSchema,\n '/auth/verify-email/confirm',\n {\n method: 'POST',\n body: {email: command.email, challenge_id: command.challengeId, code: command.code},\n },\n );\n return toAuthenticatedSession(response);\n}\n\nasync function resendEmailVerificationAuth(command: ResendEmailVerificationCommand) {\n const response = await checkedApiRequest(\n verifyEmailResendResponseSchema,\n '/auth/verify-email/resend',\n {\n method: 'POST',\n body: {email: command.email, challenge_id: command.challengeId},\n },\n );\n return {\n nextResendAvailableAt: response.next_resend_available_at,\n } satisfies EmailVerificationResendResult;\n}\n\nexport function useResendEmailVerificationAuth() {\n return useMutation({mutationFn: resendEmailVerificationAuth});\n}\n\nexport function useVerifyEmailAuth() {\n const {enterAuthenticated} = useAuthTransition();\n\n return useMutation({\n mutationFn: verifyEmailAuth,\n onSuccess: (session) => enterAuthenticated(session),\n });\n}\n"],"names":["verifyEmailConfirmResponseSchema","verifyEmailResendResponseSchema","checkedApiRequest","useMutation","useAuthTransition","toAuthenticatedSession","verifyEmailAuth","command","response","method","body","email","challenge_id","challengeId","code","resendEmailVerificationAuth","nextResendAvailableAt","next_resend_available_at","useResendEmailVerificationAuth","mutationFn","useVerifyEmailAuth","enterAuthenticated","onSuccess","session"],"mappings":"AAAA,SACEA,gCAAgC,EAChCC,+BAA+B,QAC1B,wBAAwB;AAC/B,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAQC,WAAW,QAAO,wBAAwB;AAMlD,SAAQC,iBAAiB,QAAO,iBAAiB;AACjD,SAAQC,sBAAsB,QAAO,mBAAmB;AAExD,eAAeC,gBAAgBC,OAA2B;IACxD,MAAMC,WAAW,MAAMN,kBACrBF,kCACA,8BACA;QACES,QAAQ;QACRC,MAAM;YAACC,OAAOJ,QAAQI,KAAK;YAAEC,cAAcL,QAAQM,WAAW;YAAEC,MAAMP,QAAQO,IAAI;QAAA;IACpF;IAEF,OAAOT,uBAAuBG;AAChC;AAEA,eAAeO,4BAA4BR,OAAuC;IAChF,MAAMC,WAAW,MAAMN,kBACrBD,iCACA,6BACA;QACEQ,QAAQ;QACRC,MAAM;YAACC,OAAOJ,QAAQI,KAAK;YAAEC,cAAcL,QAAQM,WAAW;QAAA;IAChE;IAEF,OAAO;QACLG,uBAAuBR,SAASS,wBAAwB;IAC1D;AACF;AAEA,OAAO,SAASC;IACd,OAAOf,YAAY;QAACgB,YAAYJ;IAA2B;AAC7D;AAEA,OAAO,SAASK;IACd,MAAM,EAACC,kBAAkB,EAAC,GAAGjB;IAE7B,OAAOD,YAAY;QACjBgB,YAAYb;QACZgB,WAAW,CAACC,UAAYF,mBAAmBE;IAC7C;AACF"}
@@ -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();