@shipfox/client-auth 2.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +40 -0
  3. package/dist/components/auth-shell.d.ts +6 -4
  4. package/dist/components/auth-shell.d.ts.map +1 -1
  5. package/dist/components/auth-shell.js +6 -1
  6. package/dist/components/auth-shell.js.map +1 -1
  7. package/dist/components/email-code-verification.d.ts +20 -0
  8. package/dist/components/email-code-verification.d.ts.map +1 -0
  9. package/dist/components/email-code-verification.js +136 -0
  10. package/dist/components/email-code-verification.js.map +1 -0
  11. package/dist/components/redirect-context.d.ts +11 -0
  12. package/dist/components/redirect-context.d.ts.map +1 -0
  13. package/dist/components/redirect-context.js +22 -0
  14. package/dist/components/redirect-context.js.map +1 -0
  15. package/dist/components/redirect-target.d.ts.map +1 -1
  16. package/dist/components/redirect-target.js +13 -12
  17. package/dist/components/redirect-target.js.map +1 -1
  18. package/dist/continuation.d.ts +4 -0
  19. package/dist/continuation.d.ts.map +1 -0
  20. package/dist/continuation.js +5 -0
  21. package/dist/continuation.js.map +1 -0
  22. package/dist/hooks/api/login-auth.d.ts.map +1 -1
  23. package/dist/hooks/api/login-auth.js +4 -28
  24. package/dist/hooks/api/login-auth.js.map +1 -1
  25. package/dist/hooks/api/logout-auth.d.ts.map +1 -1
  26. package/dist/hooks/api/logout-auth.js +4 -14
  27. package/dist/hooks/api/logout-auth.js.map +1 -1
  28. package/dist/hooks/api/password-reset-auth.d.ts.map +1 -1
  29. package/dist/hooks/api/password-reset-auth.js +4 -23
  30. package/dist/hooks/api/password-reset-auth.js.map +1 -1
  31. package/dist/hooks/api/verify-email-auth.d.ts.map +1 -1
  32. package/dist/hooks/api/verify-email-auth.js +4 -23
  33. package/dist/hooks/api/verify-email-auth.js.map +1 -1
  34. package/dist/pages/form-utils.js +1 -1
  35. package/dist/pages/form-utils.js.map +1 -1
  36. package/dist/pages/invitation-context.d.ts +0 -5
  37. package/dist/pages/invitation-context.d.ts.map +1 -1
  38. package/dist/pages/invitation-context.js +3 -19
  39. package/dist/pages/invitation-context.js.map +1 -1
  40. package/dist/pages/signup-page.d.ts.map +1 -1
  41. package/dist/pages/signup-page.js +50 -144
  42. package/dist/pages/signup-page.js.map +1 -1
  43. package/dist/state/auth.d.ts +1 -1
  44. package/dist/state/auth.d.ts.map +1 -1
  45. package/dist/state/auth.js +1 -1
  46. package/dist/state/auth.js.map +1 -1
  47. package/dist/tsconfig.test.tsbuildinfo +1 -1
  48. package/package.json +10 -40
  49. package/src/components/auth-provider.test.tsx +173 -3
  50. package/src/components/auth-shell.tsx +20 -5
  51. package/src/components/email-code-verification.test.tsx +159 -0
  52. package/src/components/email-code-verification.tsx +177 -0
  53. package/src/components/redirect-context.test.ts +51 -0
  54. package/src/components/redirect-context.ts +26 -0
  55. package/src/components/redirect-target.test.ts +8 -0
  56. package/src/components/redirect-target.ts +13 -12
  57. package/src/continuation.test.ts +15 -0
  58. package/src/continuation.ts +6 -0
  59. package/src/hooks/api/login-auth.ts +4 -28
  60. package/src/hooks/api/logout-auth.ts +4 -10
  61. package/src/hooks/api/password-reset-auth.ts +4 -23
  62. package/src/hooks/api/verify-email-auth.ts +4 -23
  63. package/src/pages/form-errors.test.ts +1 -1
  64. package/src/pages/form-utils.test.ts +1 -4
  65. package/src/pages/form-utils.ts +1 -1
  66. package/src/pages/invitation-context.ts +2 -20
  67. package/src/pages/signup-page.tsx +33 -129
  68. package/src/state/auth.ts +1 -0
  69. package/src/state/last-workspace.test.ts +16 -0
  70. package/tsconfig.build.tsbuildinfo +1 -1
  71. package/dist/pages/email-verification-resend-model.d.ts +0 -6
  72. package/dist/pages/email-verification-resend-model.d.ts.map +0 -1
  73. package/dist/pages/email-verification-resend-model.js +0 -11
  74. package/dist/pages/email-verification-resend-model.js.map +0 -1
  75. package/src/pages/email-verification-resend-model.test.ts +0 -42
  76. package/src/pages/email-verification-resend-model.ts +0 -15
@@ -1 +1 @@
1
- {"version":3,"file":"verify-email-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/verify-email-auth.ts"],"names":[],"mappings":"AA2BA,wBAAgB,8BAA8B;;;;;YAE7C;AAED,wBAAgB,kBAAkB;;;;;;;;;;;;;;;YAuBjC"}
1
+ {"version":3,"file":"verify-email-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/verify-email-auth.ts"],"names":[],"mappings":"AAwBA,wBAAgB,8BAA8B;;;;;YAE7C;AAED,wBAAgB,kBAAkB;;;;;;;;;;;;;;;YAOjC"}
@@ -1,9 +1,6 @@
1
1
  import { apiRequest } from '@shipfox/client-api';
2
- import { useMutation, useQueryClient } from '@tanstack/react-query';
3
- import { useSetAtom } from 'jotai';
4
- import { authStateAtom, toAuthenticatedState } from '#state/auth.js';
5
- import { authRefreshQueryKey } from './refresh-auth.js';
6
- import { listUserWorkspaces, userWorkspacesQueryKey } from './workspace-auth.js';
2
+ import { useMutation } from '@tanstack/react-query';
3
+ import { useAuthTransition } from '#state/auth.js';
7
4
  async function verifyEmailAuth(body) {
8
5
  return await apiRequest('/auth/verify-email/confirm', {
9
6
  method: 'POST',
@@ -22,26 +19,10 @@ export function useResendEmailVerificationAuth() {
22
19
  });
23
20
  }
24
21
  export function useVerifyEmailAuth() {
25
- const queryClient = useQueryClient();
26
- const setState = useSetAtom(authStateAtom);
22
+ const { enterAuthenticated } = useAuthTransition();
27
23
  return useMutation({
28
24
  mutationFn: verifyEmailAuth,
29
- onSuccess: async (result)=>{
30
- setState(toAuthenticatedState(result));
31
- queryClient.setQueryData(authRefreshQueryKey, result);
32
- try {
33
- const workspaces = await queryClient.fetchQuery({
34
- queryKey: userWorkspacesQueryKey,
35
- queryFn: ()=>listUserWorkspaces(result.token),
36
- retry: false,
37
- staleTime: 0
38
- });
39
- setState(toAuthenticatedState(result, workspaces.memberships));
40
- queryClient.setQueryData(userWorkspacesQueryKey, workspaces);
41
- } catch {
42
- // The user is authenticated even if workspace hydration fails.
43
- }
44
- }
25
+ onSuccess: enterAuthenticated
45
26
  });
46
27
  }
47
28
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/verify-email-auth.ts"],"sourcesContent":["import type {\n VerifyEmailConfirmBodyDto,\n VerifyEmailConfirmResponseDto,\n VerifyEmailResendBodyDto,\n VerifyEmailResendResponseDto,\n} from '@shipfox/api-auth-dto';\nimport {apiRequest} from '@shipfox/client-api';\nimport {useMutation, useQueryClient} from '@tanstack/react-query';\nimport {useSetAtom} from 'jotai';\nimport {authStateAtom, toAuthenticatedState} from '#state/auth.js';\nimport {authRefreshQueryKey} from './refresh-auth.js';\nimport {listUserWorkspaces, userWorkspacesQueryKey} from './workspace-auth.js';\n\nasync function verifyEmailAuth(body: VerifyEmailConfirmBodyDto) {\n return await apiRequest<VerifyEmailConfirmResponseDto>('/auth/verify-email/confirm', {\n method: 'POST',\n body,\n });\n}\n\nasync function resendEmailVerificationAuth(body: VerifyEmailResendBodyDto) {\n return await apiRequest<VerifyEmailResendResponseDto>('/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 queryClient = useQueryClient();\n const setState = useSetAtom(authStateAtom);\n\n return useMutation({\n mutationFn: verifyEmailAuth,\n onSuccess: async (result) => {\n setState(toAuthenticatedState(result));\n queryClient.setQueryData(authRefreshQueryKey, result);\n try {\n const workspaces = await queryClient.fetchQuery({\n queryKey: userWorkspacesQueryKey,\n queryFn: () => listUserWorkspaces(result.token),\n retry: false,\n staleTime: 0,\n });\n setState(toAuthenticatedState(result, workspaces.memberships));\n queryClient.setQueryData(userWorkspacesQueryKey, workspaces);\n } catch {\n // The user is authenticated even if workspace hydration fails.\n }\n },\n });\n}\n"],"names":["apiRequest","useMutation","useQueryClient","useSetAtom","authStateAtom","toAuthenticatedState","authRefreshQueryKey","listUserWorkspaces","userWorkspacesQueryKey","verifyEmailAuth","body","method","resendEmailVerificationAuth","useResendEmailVerificationAuth","mutationFn","useVerifyEmailAuth","queryClient","setState","onSuccess","result","setQueryData","workspaces","fetchQuery","queryKey","queryFn","token","retry","staleTime","memberships"],"mappings":"AAMA,SAAQA,UAAU,QAAO,sBAAsB;AAC/C,SAAQC,WAAW,EAAEC,cAAc,QAAO,wBAAwB;AAClE,SAAQC,UAAU,QAAO,QAAQ;AACjC,SAAQC,aAAa,EAAEC,oBAAoB,QAAO,iBAAiB;AACnE,SAAQC,mBAAmB,QAAO,oBAAoB;AACtD,SAAQC,kBAAkB,EAAEC,sBAAsB,QAAO,sBAAsB;AAE/E,eAAeC,gBAAgBC,IAA+B;IAC5D,OAAO,MAAMV,WAA0C,8BAA8B;QACnFW,QAAQ;QACRD;IACF;AACF;AAEA,eAAeE,4BAA4BF,IAA8B;IACvE,OAAO,MAAMV,WAAyC,6BAA6B;QACjFW,QAAQ;QACRD;IACF;AACF;AAEA,OAAO,SAASG;IACd,OAAOZ,YAAY;QAACa,YAAYF;IAA2B;AAC7D;AAEA,OAAO,SAASG;IACd,MAAMC,cAAcd;IACpB,MAAMe,WAAWd,WAAWC;IAE5B,OAAOH,YAAY;QACjBa,YAAYL;QACZS,WAAW,OAAOC;YAChBF,SAASZ,qBAAqBc;YAC9BH,YAAYI,YAAY,CAACd,qBAAqBa;YAC9C,IAAI;gBACF,MAAME,aAAa,MAAML,YAAYM,UAAU,CAAC;oBAC9CC,UAAUf;oBACVgB,SAAS,IAAMjB,mBAAmBY,OAAOM,KAAK;oBAC9CC,OAAO;oBACPC,WAAW;gBACb;gBACAV,SAASZ,qBAAqBc,QAAQE,WAAWO,WAAW;gBAC5DZ,YAAYI,YAAY,CAACZ,wBAAwBa;YACnD,EAAE,OAAM;YACN,+DAA+D;YACjE;QACF;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/hooks/api/verify-email-auth.ts"],"sourcesContent":["import type {\n VerifyEmailConfirmBodyDto,\n VerifyEmailConfirmResponseDto,\n VerifyEmailResendBodyDto,\n VerifyEmailResendResponseDto,\n} from '@shipfox/api-auth-dto';\nimport {apiRequest} from '@shipfox/client-api';\nimport {useMutation} from '@tanstack/react-query';\nimport {useAuthTransition} from '#state/auth.js';\n\nasync function verifyEmailAuth(body: VerifyEmailConfirmBodyDto) {\n return await apiRequest<VerifyEmailConfirmResponseDto>('/auth/verify-email/confirm', {\n method: 'POST',\n body,\n });\n}\n\nasync function resendEmailVerificationAuth(body: VerifyEmailResendBodyDto) {\n return await apiRequest<VerifyEmailResendResponseDto>('/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":["apiRequest","useMutation","useAuthTransition","verifyEmailAuth","body","method","resendEmailVerificationAuth","useResendEmailVerificationAuth","mutationFn","useVerifyEmailAuth","enterAuthenticated","onSuccess"],"mappings":"AAMA,SAAQA,UAAU,QAAO,sBAAsB;AAC/C,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,iBAAiB,QAAO,iBAAiB;AAEjD,eAAeC,gBAAgBC,IAA+B;IAC5D,OAAO,MAAMJ,WAA0C,8BAA8B;QACnFK,QAAQ;QACRD;IACF;AACF;AAEA,eAAeE,4BAA4BF,IAA8B;IACvE,OAAO,MAAMJ,WAAyC,6BAA6B;QACjFK,QAAQ;QACRD;IACF;AACF;AAEA,OAAO,SAASG;IACd,OAAON,YAAY;QAACO,YAAYF;IAA2B;AAC7D;AAEA,OAAO,SAASG;IACd,MAAM,EAACC,kBAAkB,EAAC,GAAGR;IAE7B,OAAOD,YAAY;QACjBO,YAAYL;QACZQ,WAAWD;IACb;AACF"}
@@ -17,7 +17,7 @@ export function authErrorMessage(error) {
17
17
  return 'Too many attempts. Wait a bit and try again.';
18
18
  }
19
19
  if (error.code === 'auth-rate-limit-unavailable') {
20
- return 'Sign-in protection is temporarily unavailable. Try again soon.';
20
+ return 'Sign-in is temporarily unavailable. Try again soon.';
21
21
  }
22
22
  if (error.code === 'network-error') {
23
23
  return 'We could not reach the API. Check your connection and try again.';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/form-utils.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nexport function authErrorMessage(error: unknown): string {\n if (!(error instanceof ApiError)) return 'Something went wrong. Try again.';\n\n if (error.code === 'invalid-credentials') {\n return 'Email or password is incorrect.';\n }\n if (error.code === 'email-not-verified') {\n return 'Verify your email before signing in.';\n }\n if (error.code === 'email-taken') {\n return 'An account already exists for this email.';\n }\n if (error.code === 'token-invalid') {\n return 'This link is invalid or expired.';\n }\n if (error.code === 'rate-limited') {\n return 'Too many attempts. Wait a bit and try again.';\n }\n if (error.code === 'auth-rate-limit-unavailable') {\n return 'Sign-in protection is temporarily unavailable. Try again soon.';\n }\n if (error.code === 'network-error') {\n return 'We could not reach the API. Check your connection and try again.';\n }\n\n return error.message || 'Something went wrong. Try again.';\n}\n"],"names":["ApiError","authErrorMessage","error","code","message"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAE7C,OAAO,SAASC,iBAAiBC,KAAc;IAC7C,IAAI,CAAEA,CAAAA,iBAAiBF,QAAO,GAAI,OAAO;IAEzC,IAAIE,MAAMC,IAAI,KAAK,uBAAuB;QACxC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,sBAAsB;QACvC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,eAAe;QAChC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,iBAAiB;QAClC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,gBAAgB;QACjC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,+BAA+B;QAChD,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,iBAAiB;QAClC,OAAO;IACT;IAEA,OAAOD,MAAME,OAAO,IAAI;AAC1B"}
1
+ {"version":3,"sources":["../../src/pages/form-utils.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nexport function authErrorMessage(error: unknown): string {\n if (!(error instanceof ApiError)) return 'Something went wrong. Try again.';\n\n if (error.code === 'invalid-credentials') {\n return 'Email or password is incorrect.';\n }\n if (error.code === 'email-not-verified') {\n return 'Verify your email before signing in.';\n }\n if (error.code === 'email-taken') {\n return 'An account already exists for this email.';\n }\n if (error.code === 'token-invalid') {\n return 'This link is invalid or expired.';\n }\n if (error.code === 'rate-limited') {\n return 'Too many attempts. Wait a bit and try again.';\n }\n if (error.code === 'auth-rate-limit-unavailable') {\n return 'Sign-in is temporarily unavailable. Try again soon.';\n }\n if (error.code === 'network-error') {\n return 'We could not reach the API. Check your connection and try again.';\n }\n\n return error.message || 'Something went wrong. Try again.';\n}\n"],"names":["ApiError","authErrorMessage","error","code","message"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAE7C,OAAO,SAASC,iBAAiBC,KAAc;IAC7C,IAAI,CAAEA,CAAAA,iBAAiBF,QAAO,GAAI,OAAO;IAEzC,IAAIE,MAAMC,IAAI,KAAK,uBAAuB;QACxC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,sBAAsB;QACvC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,eAAe;QAChC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,iBAAiB;QAClC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,gBAAgB;QACjC,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,+BAA+B;QAChD,OAAO;IACT;IACA,IAAID,MAAMC,IAAI,KAAK,iBAAiB;QAClC,OAAO;IACT;IAEA,OAAOD,MAAME,OAAO,IAAI;AAC1B"}
@@ -1,9 +1,4 @@
1
1
  import type { PreviewInvitationPendingDto, PreviewInvitationResponseDto } from '@shipfox/api-workspaces-dto';
2
- /**
3
- * Extract an invitation token from a `redirect=` URL if it points at the
4
- * canonical pre-auth invitation page. Returns undefined when the redirect is
5
- * absent, malformed, or unrelated to invitations.
6
- */
7
2
  export declare function extractInvitationToken(redirect: unknown): string | undefined;
8
3
  export declare function pendingInvitation(data: PreviewInvitationResponseDto | undefined): PreviewInvitationPendingDto | undefined;
9
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"invitation-context.d.ts","sourceRoot":"","sources":["../../src/pages/invitation-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,4BAA4B,EAC7B,MAAM,6BAA6B,CAAC;AAMrC;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAc5E;AASD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,4BAA4B,GAAG,SAAS,GAC7C,2BAA2B,GAAG,SAAS,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS;;;;;;;;;;;;;;;;WAQ7D"}
1
+ {"version":3,"file":"invitation-context.d.ts","sourceRoot":"","sources":["../../src/pages/invitation-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,4BAA4B,EAC7B,MAAM,6BAA6B,CAAC;AAKrC,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE5E;AASD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,4BAA4B,GAAG,SAAS,GAC7C,2BAA2B,GAAG,SAAS,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS;;;;;;;;;;;;;;;;WAQ7D"}
@@ -1,24 +1,8 @@
1
1
  import { apiRequest } from '@shipfox/client-api';
2
2
  import { useQuery } from '@tanstack/react-query';
3
- const INVITATION_ACCEPT_PATH = '/invitations/accept';
4
- /**
5
- * Extract an invitation token from a `redirect=` URL if it points at the
6
- * canonical pre-auth invitation page. Returns undefined when the redirect is
7
- * absent, malformed, or unrelated to invitations.
8
- */ export function extractInvitationToken(redirect) {
9
- if (typeof redirect !== 'string') return undefined;
10
- let decoded;
11
- try {
12
- decoded = decodeURIComponent(redirect);
13
- } catch {
14
- return undefined;
15
- }
16
- if (!decoded.startsWith('/')) return undefined;
17
- const [path, queryString = ''] = decoded.split('?', 2);
18
- if (path !== INVITATION_ACCEPT_PATH) return undefined;
19
- const params = new URLSearchParams(queryString);
20
- const token = params.get('token');
21
- return token && token.length > 0 ? token : undefined;
3
+ import { parseRedirectContext } from '#/components/redirect-context.js';
4
+ export function extractInvitationToken(redirect) {
5
+ return parseRedirectContext(redirect).invitationToken;
22
6
  }
23
7
  async function fetchPreview(token) {
24
8
  const params = new URLSearchParams({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/invitation-context.ts"],"sourcesContent":["import type {\n PreviewInvitationPendingDto,\n PreviewInvitationResponseDto,\n} from '@shipfox/api-workspaces-dto';\nimport {apiRequest} from '@shipfox/client-api';\nimport {useQuery} from '@tanstack/react-query';\n\nconst INVITATION_ACCEPT_PATH = '/invitations/accept';\n\n/**\n * Extract an invitation token from a `redirect=` URL if it points at the\n * canonical pre-auth invitation page. Returns undefined when the redirect is\n * absent, malformed, or unrelated to invitations.\n */\nexport function extractInvitationToken(redirect: unknown): string | undefined {\n if (typeof redirect !== 'string') return undefined;\n let decoded: string;\n try {\n decoded = decodeURIComponent(redirect);\n } catch {\n return undefined;\n }\n if (!decoded.startsWith('/')) return undefined;\n const [path, queryString = ''] = decoded.split('?', 2);\n if (path !== INVITATION_ACCEPT_PATH) return undefined;\n const params = new URLSearchParams(queryString);\n const token = params.get('token');\n return token && token.length > 0 ? token : undefined;\n}\n\nasync function fetchPreview(token: string): Promise<PreviewInvitationResponseDto> {\n const params = new URLSearchParams({token});\n return await apiRequest<PreviewInvitationResponseDto>(\n `/invitations/preview?${params.toString()}`,\n );\n}\n\nexport function pendingInvitation(\n data: PreviewInvitationResponseDto | undefined,\n): PreviewInvitationPendingDto | undefined {\n return data?.status === 'pending' ? data : undefined;\n}\n\n/**\n * Mirrors the hook in `@shipfox/client-invitations` but lives in `client-auth`\n * to avoid a circular dependency. Signup/login pages call this to lock the\n * email field when arriving from an invitation link.\n */\nexport function useInvitationContext(token: string | undefined) {\n return useQuery({\n queryKey: ['invitations', 'preview', token ?? ''] as const,\n queryFn: () => fetchPreview(token as string),\n enabled: Boolean(token),\n retry: false,\n staleTime: 30_000,\n });\n}\n"],"names":["apiRequest","useQuery","INVITATION_ACCEPT_PATH","extractInvitationToken","redirect","undefined","decoded","decodeURIComponent","startsWith","path","queryString","split","params","URLSearchParams","token","get","length","fetchPreview","toString","pendingInvitation","data","status","useInvitationContext","queryKey","queryFn","enabled","Boolean","retry","staleTime"],"mappings":"AAIA,SAAQA,UAAU,QAAO,sBAAsB;AAC/C,SAAQC,QAAQ,QAAO,wBAAwB;AAE/C,MAAMC,yBAAyB;AAE/B;;;;CAIC,GACD,OAAO,SAASC,uBAAuBC,QAAiB;IACtD,IAAI,OAAOA,aAAa,UAAU,OAAOC;IACzC,IAAIC;IACJ,IAAI;QACFA,UAAUC,mBAAmBH;IAC/B,EAAE,OAAM;QACN,OAAOC;IACT;IACA,IAAI,CAACC,QAAQE,UAAU,CAAC,MAAM,OAAOH;IACrC,MAAM,CAACI,MAAMC,cAAc,EAAE,CAAC,GAAGJ,QAAQK,KAAK,CAAC,KAAK;IACpD,IAAIF,SAASP,wBAAwB,OAAOG;IAC5C,MAAMO,SAAS,IAAIC,gBAAgBH;IACnC,MAAMI,QAAQF,OAAOG,GAAG,CAAC;IACzB,OAAOD,SAASA,MAAME,MAAM,GAAG,IAAIF,QAAQT;AAC7C;AAEA,eAAeY,aAAaH,KAAa;IACvC,MAAMF,SAAS,IAAIC,gBAAgB;QAACC;IAAK;IACzC,OAAO,MAAMd,WACX,CAAC,qBAAqB,EAAEY,OAAOM,QAAQ,IAAI;AAE/C;AAEA,OAAO,SAASC,kBACdC,IAA8C;IAE9C,OAAOA,MAAMC,WAAW,YAAYD,OAAOf;AAC7C;AAEA;;;;CAIC,GACD,OAAO,SAASiB,qBAAqBR,KAAyB;IAC5D,OAAOb,SAAS;QACdsB,UAAU;YAAC;YAAe;YAAWT,SAAS;SAAG;QACjDU,SAAS,IAAMP,aAAaH;QAC5BW,SAASC,QAAQZ;QACjBa,OAAO;QACPC,WAAW;IACb;AACF"}
1
+ {"version":3,"sources":["../../src/pages/invitation-context.ts"],"sourcesContent":["import type {\n PreviewInvitationPendingDto,\n PreviewInvitationResponseDto,\n} from '@shipfox/api-workspaces-dto';\nimport {apiRequest} from '@shipfox/client-api';\nimport {useQuery} from '@tanstack/react-query';\nimport {parseRedirectContext} from '#/components/redirect-context.js';\n\nexport function extractInvitationToken(redirect: unknown): string | undefined {\n return parseRedirectContext(redirect).invitationToken;\n}\n\nasync function fetchPreview(token: string): Promise<PreviewInvitationResponseDto> {\n const params = new URLSearchParams({token});\n return await apiRequest<PreviewInvitationResponseDto>(\n `/invitations/preview?${params.toString()}`,\n );\n}\n\nexport function pendingInvitation(\n data: PreviewInvitationResponseDto | undefined,\n): PreviewInvitationPendingDto | undefined {\n return data?.status === 'pending' ? data : undefined;\n}\n\n/**\n * Mirrors the hook in `@shipfox/client-invitations` but lives in `client-auth`\n * to avoid a circular dependency. Signup/login pages call this to lock the\n * email field when arriving from an invitation link.\n */\nexport function useInvitationContext(token: string | undefined) {\n return useQuery({\n queryKey: ['invitations', 'preview', token ?? ''] as const,\n queryFn: () => fetchPreview(token as string),\n enabled: Boolean(token),\n retry: false,\n staleTime: 30_000,\n });\n}\n"],"names":["apiRequest","useQuery","parseRedirectContext","extractInvitationToken","redirect","invitationToken","fetchPreview","token","params","URLSearchParams","toString","pendingInvitation","data","status","undefined","useInvitationContext","queryKey","queryFn","enabled","Boolean","retry","staleTime"],"mappings":"AAIA,SAAQA,UAAU,QAAO,sBAAsB;AAC/C,SAAQC,QAAQ,QAAO,wBAAwB;AAC/C,SAAQC,oBAAoB,QAAO,mCAAmC;AAEtE,OAAO,SAASC,uBAAuBC,QAAiB;IACtD,OAAOF,qBAAqBE,UAAUC,eAAe;AACvD;AAEA,eAAeC,aAAaC,KAAa;IACvC,MAAMC,SAAS,IAAIC,gBAAgB;QAACF;IAAK;IACzC,OAAO,MAAMP,WACX,CAAC,qBAAqB,EAAEQ,OAAOE,QAAQ,IAAI;AAE/C;AAEA,OAAO,SAASC,kBACdC,IAA8C;IAE9C,OAAOA,MAAMC,WAAW,YAAYD,OAAOE;AAC7C;AAEA;;;;CAIC,GACD,OAAO,SAASC,qBAAqBR,KAAyB;IAC5D,OAAON,SAAS;QACde,UAAU;YAAC;YAAe;YAAWT,SAAS;SAAG;QACjDU,SAAS,IAAMX,aAAaC;QAC5BW,SAASC,QAAQZ;QACjBa,OAAO;QACPC,WAAW;IACb;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"signup-page.d.ts","sourceRoot":"","sources":["../../src/pages/signup-page.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,UAAU,gCAuXzB"}
1
+ {"version":3,"file":"signup-page.d.ts","sourceRoot":"","sources":["../../src/pages/signup-page.tsx"],"names":[],"mappings":"AA0BA,wBAAgB,UAAU,gCA0RzB"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { signupBodySchema, verifyEmailConfirmBodySchema } from '@shipfox/api-auth-dto';
2
+ import { signupBodySchema } from '@shipfox/api-auth-dto';
3
3
  import { displayNameFieldError } from '@shipfox/client-ui';
4
4
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
5
5
  import { Callout } from '@shipfox/react-ui/callout';
@@ -12,11 +12,11 @@ import { Link, useNavigate, useSearch } from '@tanstack/react-router';
12
12
  import { useAtom } from 'jotai';
13
13
  import { useEffect, useRef, useState } from 'react';
14
14
  import { AuthShell } from '#/components/auth-shell.js';
15
+ import { EmailCodeVerification } from '#/components/email-code-verification.js';
15
16
  import { useRefreshAuth } from '#hooks/api/refresh-auth.js';
16
17
  import { useSignupAuth } from '#hooks/api/signup-auth.js';
17
18
  import { useResendEmailVerificationAuth, useVerifyEmailAuth } from '#hooks/api/verify-email-auth.js';
18
19
  import { authFormDraftAtom, initialAuthFormDraft } from '#state/auth.js';
19
- import { getResendRemainingSeconds, parseNextResendAvailableAt } from './email-verification-resend-model.js';
20
20
  import { signupErrorToFormError } from './form-errors.js';
21
21
  import { authErrorMessage } from './form-utils.js';
22
22
  import { extractInvitationToken, pendingInvitation, useInvitationContext } from './invitation-context.js';
@@ -34,7 +34,6 @@ export function SignupPage() {
34
34
  const invitationPending = pendingInvitation(invitationPreview.data);
35
35
  const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);
36
36
  const [emailChallenge, setEmailChallenge] = useState();
37
- const [now, setNow] = useState(()=>Date.now());
38
37
  const [nextResendAvailableAt, setNextResendAvailableAt] = useState();
39
38
  const [formError, setFormError] = useState();
40
39
  const [resendError, setResendError] = useState();
@@ -43,11 +42,6 @@ export function SignupPage() {
43
42
  // Set just before clearing the draft on success so the unmount cleanup
44
43
  // below does not repersist the just-submitted credentials.
45
44
  const skipDraftPersistRef = useRef(false);
46
- const resendRemainingSeconds = getResendRemainingSeconds({
47
- nextResendAvailableAt,
48
- now
49
- });
50
- const isResendCoolingDown = resendRemainingSeconds > 0;
51
45
  const form = useForm({
52
46
  defaultValues: {
53
47
  email: authFormDraft.email,
@@ -101,10 +95,8 @@ export function SignupPage() {
101
95
  email: result.user.email,
102
96
  id: result.email_challenge.id
103
97
  });
104
- verificationForm.reset();
105
98
  setResendError(undefined);
106
- setNow(Date.now());
107
- setNextResendAvailableAt(parseNextResendAvailableAt(result.email_challenge.next_resend_available_at));
99
+ setNextResendAvailableAt(result.email_challenge.next_resend_available_at);
108
100
  } catch (error) {
109
101
  const mapped = signupErrorToFormError(error);
110
102
  if (mapped.kind === 'field') {
@@ -121,51 +113,6 @@ export function SignupPage() {
121
113
  }
122
114
  }
123
115
  });
124
- const verificationForm = useForm({
125
- defaultValues: {
126
- code: ''
127
- },
128
- onSubmit: async ({ value })=>{
129
- if (!emailChallenge || verifyEmail.isPending) return;
130
- setResendError(undefined);
131
- try {
132
- await verifyEmail.mutateAsync({
133
- email: emailChallenge.email,
134
- challenge_id: emailChallenge.id,
135
- code: value.code
136
- });
137
- await refreshAuth();
138
- toast.success('Your email is verified. You are now logged in.');
139
- await navigate({
140
- to: '/',
141
- replace: true
142
- });
143
- } catch (error) {
144
- setResendError(authErrorMessage(error));
145
- }
146
- }
147
- });
148
- useEffect(()=>{
149
- if (!emailChallenge || !nextResendAvailableAt) {
150
- return;
151
- }
152
- const current = Date.now();
153
- setNow(current);
154
- if (nextResendAvailableAt <= current) {
155
- return;
156
- }
157
- const handle = window.setInterval(()=>{
158
- const tickNow = Date.now();
159
- setNow(tickNow);
160
- if (nextResendAvailableAt <= tickNow) {
161
- window.clearInterval(handle);
162
- }
163
- }, 1000);
164
- return ()=>window.clearInterval(handle);
165
- }, [
166
- emailChallenge,
167
- nextResendAvailableAt
168
- ]);
169
116
  // When arriving from an invitation link, prefill the email and lock it.
170
117
  useEffect(()=>{
171
118
  if (invitationPending && form.state.values.email !== invitationPending.email) {
@@ -199,107 +146,66 @@ export function SignupPage() {
199
146
  setAuthFormDraft
200
147
  ]);
201
148
  async function onResendVerificationEmail() {
202
- if (!emailChallenge || isResendCoolingDown || resendEmailVerification.isPending) return;
149
+ if (!emailChallenge || resendEmailVerification.isPending) return;
203
150
  setResendError(undefined);
204
151
  try {
205
152
  const result = await resendEmailVerification.mutateAsync({
206
153
  email: emailChallenge.email,
207
154
  challenge_id: emailChallenge.id
208
155
  });
209
- const nextAvailableAt = parseNextResendAvailableAt(result.next_resend_available_at);
210
- setNow(Date.now());
211
- if (nextAvailableAt !== undefined) {
212
- setNextResendAvailableAt(nextAvailableAt);
213
- }
156
+ setNextResendAvailableAt(result.next_resend_available_at);
214
157
  toast.success('If another verification email can be sent, it will arrive shortly.');
215
158
  } catch (error) {
216
159
  setResendError(authErrorMessage(error));
217
160
  }
218
161
  }
219
162
  if (emailChallenge) {
220
- return /*#__PURE__*/ _jsxs(AuthShell, {
221
- title: "Check your email",
222
- description: `We sent an eight-digit verification code to ${emailChallenge.email}.`,
223
- children: [
224
- resendError ? /*#__PURE__*/ _jsx(Callout, {
225
- role: "alert",
226
- type: "error",
227
- children: resendError
228
- }) : null,
229
- /*#__PURE__*/ _jsxs("form", {
230
- className: "flex flex-col gap-8",
231
- noValidate: true,
232
- onSubmit: (event)=>{
233
- event.preventDefault();
234
- void verificationForm.handleSubmit();
235
- },
236
- children: [
237
- /*#__PURE__*/ _jsx(verificationForm.Field, {
238
- name: "code",
239
- validators: {
240
- onBlur: verifyEmailConfirmBodySchema.shape.code,
241
- onSubmit: verifyEmailConfirmBodySchema.shape.code
242
- },
243
- children: (field)=>/*#__PURE__*/ _jsx(FormField, {
244
- label: "Verification code",
245
- id: "verification-code",
246
- error: fieldError(field),
247
- children: /*#__PURE__*/ _jsx(FormFieldInput, {
248
- autoComplete: "one-time-code",
249
- inputMode: "numeric",
250
- maxLength: 8,
251
- pattern: "[0-9]{8}",
252
- value: field.state.value,
253
- onChange: (event)=>field.handleChange(event.target.value.replace(/\D/gu, '')),
254
- onBlur: field.handleBlur
255
- })
256
- })
257
- }),
258
- /*#__PURE__*/ _jsx(Button, {
259
- className: "w-full",
260
- type: "submit",
261
- isLoading: verifyEmail.isPending,
262
- children: "Verify email"
263
- }),
264
- /*#__PURE__*/ _jsx(Button, {
265
- "aria-disabled": isResendCoolingDown ? true : undefined,
266
- className: "w-full aria-disabled:cursor-not-allowed aria-disabled:opacity-70",
267
- variant: "secondary",
268
- type: "button",
269
- isLoading: resendEmailVerification.isPending,
270
- onClick: onResendVerificationEmail,
271
- children: resendEmailVerification.isPending ? 'Sending email...' : isResendCoolingDown ? `Resend in ${resendRemainingSeconds}s` : 'Resend verification email'
272
- })
273
- ]
274
- }),
275
- /*#__PURE__*/ _jsx(Button, {
276
- className: "w-full",
277
- variant: "transparent",
278
- type: "button",
279
- onClick: ()=>{
280
- setEmailChallenge(undefined);
281
- setResendError(undefined);
282
- },
283
- children: "Use another email"
284
- }),
285
- /*#__PURE__*/ _jsxs(Text, {
286
- size: "sm",
287
- className: "text-center text-foreground-neutral-subtle",
288
- children: [
289
- "Already verified?",
290
- ' ',
291
- /*#__PURE__*/ _jsx(ButtonLink, {
292
- asChild: true,
293
- variant: "interactive",
294
- underline: true,
295
- children: /*#__PURE__*/ _jsx(Link, {
296
- to: "/auth/login",
297
- children: "Log in"
298
- })
163
+ return /*#__PURE__*/ _jsx(EmailCodeVerification, {
164
+ destination: emailChallenge.email,
165
+ nextResendAvailableAt: nextResendAvailableAt,
166
+ isResending: resendEmailVerification.isPending,
167
+ isVerifying: verifyEmail.isPending,
168
+ error: resendError,
169
+ onVerify: async (code)=>{
170
+ setResendError(undefined);
171
+ try {
172
+ await verifyEmail.mutateAsync({
173
+ email: emailChallenge.email,
174
+ challenge_id: emailChallenge.id,
175
+ code
176
+ });
177
+ await refreshAuth();
178
+ toast.success('Your email is verified. You are now logged in.');
179
+ await navigate({
180
+ to: '/',
181
+ replace: true
182
+ });
183
+ } catch (error) {
184
+ setResendError(authErrorMessage(error));
185
+ }
186
+ },
187
+ onResend: onResendVerificationEmail,
188
+ onUseAnotherEmail: ()=>{
189
+ setEmailChallenge(undefined);
190
+ setResendError(undefined);
191
+ },
192
+ children: /*#__PURE__*/ _jsxs(Text, {
193
+ size: "sm",
194
+ className: "text-center text-foreground-neutral-subtle",
195
+ children: [
196
+ "Already verified?",
197
+ ' ',
198
+ /*#__PURE__*/ _jsx(ButtonLink, {
199
+ asChild: true,
200
+ variant: "interactive",
201
+ underline: true,
202
+ children: /*#__PURE__*/ _jsx(Link, {
203
+ to: "/auth/login",
204
+ children: "Log in"
299
205
  })
300
- ]
301
- })
302
- ]
206
+ })
207
+ ]
208
+ })
303
209
  });
304
210
  }
305
211
  const headerTitle = invitationPending ? `Join ${invitationPending.workspace_name}` : 'Create your Shipfox account';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/signup-page.tsx"],"sourcesContent":["import {signupBodySchema, verifyEmailConfirmBodySchema} from '@shipfox/api-auth-dto';\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 {AuthShell} from '#/components/auth-shell.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 {\n getResendRemainingSeconds,\n parseNextResendAvailableAt,\n} from './email-verification-resend-model.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 [now, setNow] = useState(() => Date.now());\n const [nextResendAvailableAt, setNextResendAvailableAt] = useState<number | 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 const resendRemainingSeconds = getResendRemainingSeconds({nextResendAvailableAt, now});\n const isResendCoolingDown = resendRemainingSeconds > 0;\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.workspace_name}.`);\n await navigate({\n to: '/workspaces/$wid',\n params: {wid: result.membership.workspace_id},\n });\n return;\n }\n\n if (invitationToken && result.accept_error) {\n toast.error(result.accept_error.message);\n await navigate({\n to: '/invitations/accept',\n search: {token: invitationToken},\n });\n return;\n }\n\n if (!result.email_challenge) {\n throw new Error('Signup did not return an email verification challenge');\n }\n setEmailChallenge({email: result.user.email, id: result.email_challenge.id});\n verificationForm.reset();\n setResendError(undefined);\n setNow(Date.now());\n setNextResendAvailableAt(\n parseNextResendAvailableAt(result.email_challenge.next_resend_available_at),\n );\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 const verificationForm = useForm({\n defaultValues: {code: ''},\n onSubmit: async ({value}) => {\n if (!emailChallenge || verifyEmail.isPending) return;\n\n setResendError(undefined);\n try {\n await verifyEmail.mutateAsync({\n email: emailChallenge.email,\n challenge_id: emailChallenge.id,\n code: value.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 });\n\n useEffect(() => {\n if (!emailChallenge || !nextResendAvailableAt) {\n return;\n }\n\n const current = Date.now();\n setNow(current);\n if (nextResendAvailableAt <= current) {\n return;\n }\n\n const handle = window.setInterval(() => {\n const tickNow = Date.now();\n setNow(tickNow);\n if (nextResendAvailableAt <= tickNow) {\n window.clearInterval(handle);\n }\n }, 1000);\n return () => window.clearInterval(handle);\n }, [emailChallenge, nextResendAvailableAt]);\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 || isResendCoolingDown || 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 const nextAvailableAt = parseNextResendAvailableAt(result.next_resend_available_at);\n setNow(Date.now());\n if (nextAvailableAt !== undefined) {\n setNextResendAvailableAt(nextAvailableAt);\n }\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 <AuthShell\n title=\"Check your email\"\n description={`We sent an eight-digit verification code to ${emailChallenge.email}.`}\n >\n {resendError ? (\n <Callout role=\"alert\" type=\"error\">\n {resendError}\n </Callout>\n ) : null}\n <form\n className=\"flex flex-col gap-8\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n void verificationForm.handleSubmit();\n }}\n >\n <verificationForm.Field\n name=\"code\"\n validators={{\n onBlur: verifyEmailConfirmBodySchema.shape.code,\n onSubmit: verifyEmailConfirmBodySchema.shape.code,\n }}\n >\n {(field) => (\n <FormField label=\"Verification code\" id=\"verification-code\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"one-time-code\"\n inputMode=\"numeric\"\n maxLength={8}\n pattern=\"[0-9]{8}\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value.replace(/\\D/gu, ''))}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </verificationForm.Field>\n <Button className=\"w-full\" type=\"submit\" isLoading={verifyEmail.isPending}>\n Verify email\n </Button>\n <Button\n aria-disabled={isResendCoolingDown ? true : undefined}\n className=\"w-full aria-disabled:cursor-not-allowed aria-disabled:opacity-70\"\n variant=\"secondary\"\n type=\"button\"\n isLoading={resendEmailVerification.isPending}\n onClick={onResendVerificationEmail}\n >\n {resendEmailVerification.isPending\n ? 'Sending email...'\n : isResendCoolingDown\n ? `Resend in ${resendRemainingSeconds}s`\n : 'Resend verification email'}\n </Button>\n </form>\n <Button\n className=\"w-full\"\n variant=\"transparent\"\n type=\"button\"\n onClick={() => {\n setEmailChallenge(undefined);\n setResendError(undefined);\n }}\n >\n Use another email\n </Button>\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 </AuthShell>\n );\n }\n\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspace_name}`\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","verifyEmailConfirmBodySchema","displayNameFieldError","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","Link","useNavigate","useSearch","useAtom","useEffect","useRef","useState","AuthShell","useRefreshAuth","useSignupAuth","useResendEmailVerificationAuth","useVerifyEmailAuth","authFormDraftAtom","initialAuthFormDraft","getResendRemainingSeconds","parseNextResendAvailableAt","signupErrorToFormError","authErrorMessage","extractInvitationToken","pendingInvitation","useInvitationContext","SignupPage","signup","verifyEmail","resendEmailVerification","refreshAuth","navigate","search","strict","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","emailChallenge","setEmailChallenge","now","setNow","Date","nextResendAvailableAt","setNextResendAvailableAt","formError","setFormError","resendError","setResendError","draftRef","current","skipDraftPersistRef","resendRemainingSeconds","isResendCoolingDown","form","defaultValues","email","password","name","onSubmit","value","undefined","body","parse","invitation_token","result","mutateAsync","membership","success","workspace_name","to","params","wid","workspace_id","accept_error","error","message","token","email_challenge","Error","user","id","verificationForm","reset","next_resend_available_at","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","code","isPending","challenge_id","replace","handle","window","setInterval","tickNow","clearInterval","state","values","setFieldValue","onResendVerificationEmail","nextAvailableAt","title","description","role","type","className","noValidate","event","preventDefault","handleSubmit","Field","validators","onBlur","shape","label","autoComplete","inputMode","maxLength","pattern","onChange","handleChange","target","handleBlur","isLoading","aria-disabled","variant","onClick","size","asChild","underline","headerTitle","headerDescription","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","persistDraft","stopPropagation","readOnly","iconRight","aria-hidden"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/signup-page.tsx"],"sourcesContent":["import {signupBodySchema} from '@shipfox/api-auth-dto';\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 {AuthShell} from '#/components/auth-shell.js';\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.workspace_name}.`);\n await navigate({\n to: '/workspaces/$wid',\n params: {wid: result.membership.workspace_id},\n });\n return;\n }\n\n if (invitationToken && result.accept_error) {\n toast.error(result.accept_error.message);\n await navigate({\n to: '/invitations/accept',\n search: {token: invitationToken},\n });\n return;\n }\n\n if (!result.email_challenge) {\n throw new Error('Signup did not return an email verification challenge');\n }\n setEmailChallenge({email: result.user.email, id: result.email_challenge.id});\n setResendError(undefined);\n setNextResendAvailableAt(result.email_challenge.next_resend_available_at);\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.workspace_name}`\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","displayNameFieldError","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","Link","useNavigate","useSearch","useAtom","useEffect","useRef","useState","AuthShell","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","workspace_name","to","params","wid","workspace_id","accept_error","error","message","token","email_challenge","Error","user","id","next_resend_available_at","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","state","values","setFieldValue","onResendVerificationEmail","isPending","challenge_id","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":";AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,qBAAqB,QAAO,qBAAqB;AACzD,SAAQC,MAAM,EAAEC,UAAU,QAAO,2BAA2B;AAC5D,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SAAQC,SAAS,EAAEC,cAAc,EAAEC,UAAU,QAAO,+BAA+B;AACnF,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,KAAK,QAAO,0BAA0B;AAC9C,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,OAAO,QAAO,uBAAuB;AAC7C,SAAQC,IAAI,EAAEC,WAAW,EAAEC,SAAS,QAAO,yBAAyB;AACpE,SAAQC,OAAO,QAAO,QAAQ;AAC9B,SAAQC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAClD,SAAQC,SAAS,QAAO,6BAA6B;AACrD,SAAQC,qBAAqB,QAAO,0CAA0C;AAC9E,SAAQC,cAAc,QAAO,6BAA6B;AAC1D,SAAQC,aAAa,QAAO,4BAA4B;AACxD,SAAQC,8BAA8B,EAAEC,kBAAkB,QAAO,kCAAkC;AACnG,SAAQC,iBAAiB,EAAEC,oBAAoB,QAAO,iBAAiB;AACvE,SAAQC,sBAAsB,QAAO,mBAAmB;AACxD,SAAQC,gBAAgB,QAAO,kBAAkB;AACjD,SACEC,sBAAsB,EACtBC,iBAAiB,EACjBC,oBAAoB,QACf,0BAA0B;AAEjC,OAAO,SAASC;IACd,MAAMC,SAASX;IACf,MAAMY,cAAcV;IACpB,MAAMW,0BAA0BZ;IAChC,MAAMa,cAAcf;IACpB,MAAMgB,WAAWxB;IACjB,MAAMyB,SAASxB,UAAU;QAACyB,QAAQ;IAAK;IACvC,MAAMC,kBAAkBX,uBAAuBS,OAAOG,QAAQ;IAC9D,MAAMC,oBAAoBX,qBAAqBS;IAC/C,MAAMG,oBAAoBb,kBAAkBY,kBAAkBE,IAAI;IAClE,MAAM,CAACC,eAAeC,iBAAiB,GAAG/B,QAAQU;IAClD,MAAM,CAACsB,gBAAgBC,kBAAkB,GAAG9B;IAC5C,MAAM,CAAC+B,uBAAuBC,yBAAyB,GAAGhC;IAC1D,MAAM,CAACiC,WAAWC,aAAa,GAAGlC;IAClC,MAAM,CAACmC,aAAaC,eAAe,GAAGpC;IACtC,MAAMqC,WAAWtC,OAAO4B;IACxBU,SAASC,OAAO,GAAGX;IACnB,uEAAuE;IACvE,2DAA2D;IAC3D,MAAMY,sBAAsBxC,OAAO;IAEnC,MAAMyC,OAAO/C,QAAQ;QACnBgD,eAAe;YAACC,OAAOf,cAAce,KAAK;YAAEC,UAAUhB,cAAcgB,QAAQ;YAAEC,MAAM;QAAE;QACtFC,UAAU,OAAO,EAACC,KAAK,EAAC;YACtBZ,aAAaa;YACb,IAAI;gBACF,MAAMC,OAAOlE,iBAAiBmE,KAAK,CAAC;oBAClCP,OAAOI,MAAMJ,KAAK;oBAClBC,UAAUG,MAAMH,QAAQ;oBACxBC,MAAME,MAAMF,IAAI;oBAChB,GAAItB,kBAAkB;wBAAC4B,kBAAkB5B;oBAAe,IAAI,CAAC,CAAC;gBAChE;gBACA,MAAM6B,SAAS,MAAMpC,OAAOqC,WAAW,CAACJ;gBACxCT,oBAAoBD,OAAO,GAAG;gBAC9BV,iBAAiBpB;gBAEjB,IAAIc,mBAAmB6B,OAAOE,UAAU,IAAI5B,mBAAmB;oBAC7D,IAAI;wBACF,MAAMP;oBACR,EAAE,OAAM;oBACN,kEAAkE;oBAClE,8CAA8C;oBAChD;oBACA3B,MAAM+D,OAAO,CAAC,CAAC,WAAW,EAAE7B,kBAAkB8B,cAAc,CAAC,CAAC,CAAC;oBAC/D,MAAMpC,SAAS;wBACbqC,IAAI;wBACJC,QAAQ;4BAACC,KAAKP,OAAOE,UAAU,CAACM,YAAY;wBAAA;oBAC9C;oBACA;gBACF;gBAEA,IAAIrC,mBAAmB6B,OAAOS,YAAY,EAAE;oBAC1CrE,MAAMsE,KAAK,CAACV,OAAOS,YAAY,CAACE,OAAO;oBACvC,MAAM3C,SAAS;wBACbqC,IAAI;wBACJpC,QAAQ;4BAAC2C,OAAOzC;wBAAe;oBACjC;oBACA;gBACF;gBAEA,IAAI,CAAC6B,OAAOa,eAAe,EAAE;oBAC3B,MAAM,IAAIC,MAAM;gBAClB;gBACAnC,kBAAkB;oBAACY,OAAOS,OAAOe,IAAI,CAACxB,KAAK;oBAAEyB,IAAIhB,OAAOa,eAAe,CAACG,EAAE;gBAAA;gBAC1E/B,eAAeW;gBACff,yBAAyBmB,OAAOa,eAAe,CAACI,wBAAwB;YAC1E,EAAE,OAAOP,OAAO;gBACd,MAAMQ,SAAS5D,uBAAuBoD;gBACtC,IAAIQ,OAAOC,IAAI,KAAK,SAAS;oBAC3B9B,KAAK+B,YAAY,CAACF,OAAOG,KAAK,EAAE,CAACC,OAAU,CAAA;4BACzC,GAAGA,IAAI;4BACPC,UAAU;gCAAC,GAAGD,KAAKC,QAAQ;gCAAEC,UAAUN,OAAOP,OAAO;4BAAA;wBACvD,CAAA;gBACF,OAAO;oBACL5B,aAAamC,OAAOP,OAAO;gBAC7B;YACF;QACF;IACF;IAEA,wEAAwE;IACxEhE,UAAU;QACR,IAAI2B,qBAAqBe,KAAKoC,KAAK,CAACC,MAAM,CAACnC,KAAK,KAAKjB,kBAAkBiB,KAAK,EAAE;YAC5EF,KAAKsC,aAAa,CAAC,SAASrD,kBAAkBiB,KAAK;YACnDd,iBAAiB,CAACU,UAAa,CAAA;oBAAC,GAAGA,OAAO;oBAAEI,OAAOjB,kBAAkBiB,KAAK;gBAAA,CAAA;QAC5E;IACF,GAAG;QAACjB;QAAmBe;QAAMZ;KAAiB;IAE9C,6EAA6E;IAC7E,4EAA4E;IAC5E,qEAAqE;IACrE9B,UAAU;QACR,OAAO;YACL,IAAIyC,oBAAoBD,OAAO,EAAE;YACjC,MAAM,EAACI,KAAK,EAAEC,QAAQ,EAAC,GAAGH,KAAKoC,KAAK,CAACC,MAAM;YAC3C,IAAInC,UAAUL,SAASC,OAAO,CAACI,KAAK,IAAIC,aAAaN,SAASC,OAAO,CAACK,QAAQ,EAAE;gBAC9Ef,iBAAiB;oBAACc;oBAAOC;gBAAQ;YACnC;QACF;IACF,GAAG;QAACH;QAAMZ;KAAiB;IAE3B,eAAemD;QACb,IAAI,CAAClD,kBAAkBZ,wBAAwB+D,SAAS,EAAE;QAE1D5C,eAAeW;QACf,IAAI;YACF,MAAMI,SAAS,MAAMlC,wBAAwBmC,WAAW,CAAC;gBACvDV,OAAOb,eAAea,KAAK;gBAC3BuC,cAAcpD,eAAesC,EAAE;YACjC;YACAnC,yBAAyBmB,OAAOiB,wBAAwB;YACxD7E,MAAM+D,OAAO,CAAC;QAChB,EAAE,OAAOO,OAAO;YACdzB,eAAe1B,iBAAiBmD;QAClC;IACF;IAEA,IAAIhC,gBAAgB;QAClB,qBACE,KAAC3B;YACCgF,aAAarD,eAAea,KAAK;YACjCX,uBAAuBA;YACvBoD,aAAalE,wBAAwB+D,SAAS;YAC9CI,aAAapE,YAAYgE,SAAS;YAClCnB,OAAO1B;YACPkD,UAAU,OAAOC;gBACflD,eAAeW;gBACf,IAAI;oBACF,MAAM/B,YAAYoC,WAAW,CAAC;wBAC5BV,OAAOb,eAAea,KAAK;wBAC3BuC,cAAcpD,eAAesC,EAAE;wBAC/BmB;oBACF;oBACA,MAAMpE;oBACN3B,MAAM+D,OAAO,CAAC;oBACd,MAAMnC,SAAS;wBAACqC,IAAI;wBAAK+B,SAAS;oBAAI;gBACxC,EAAE,OAAO1B,OAAO;oBACdzB,eAAe1B,iBAAiBmD;gBAClC;YACF;YACA2B,UAAUT;YACVU,mBAAmB;gBACjB3D,kBAAkBiB;gBAClBX,eAAeW;YACjB;sBAEA,cAAA,MAACvD;gBAAKkG,MAAK;gBAAKC,WAAU;;oBAA6C;oBACnD;kCAClB,KAAC1G;wBAAW2G,OAAO;wBAACC,SAAQ;wBAAcC,SAAS;kCACjD,cAAA,KAACpG;4BAAK8D,IAAG;sCAAc;;;;;;IAKjC;IAEA,MAAMuC,cAActE,oBAChB,CAAC,KAAK,EAAEA,kBAAkB8B,cAAc,EAAE,GAC1C;IACJ,MAAMyC,oBAAoBvE,oBACtB,CAAC,4CAA4C,CAAC,GAC9C;IACJ,MAAMwE,0BAA0BC,QAAQzE;IACxC,MAAM0E,qBAAqB7E,kBACvB,CAAC,0BAA0B,EAAE8E,mBAAmB9E,kBAAkB,GAClEyB;IAEJ,SAASsD;QACP,MAAM,EAAC3D,KAAK,EAAEC,QAAQ,EAAC,GAAGH,KAAKoC,KAAK,CAACC,MAAM;QAC3CjD,iBAAiB;YAACc;YAAOC;QAAQ;IACnC;IAEA,qBACE,MAAC1C;QAAUqG,OAAOP;QAAaQ,aAAaP;;0BAC1C,MAACxD;gBACCmD,WAAU;gBACVa,UAAU;gBACV3D,UAAU,CAAC4D;oBACTA,MAAMC,cAAc;oBACpBD,MAAME,eAAe;oBACrB,KAAKnE,KAAKoE,YAAY;gBACxB;;oBAEC3E,0BACC,KAAC/C;wBAAQ2H,MAAK;wBAAQC,MAAK;kCACxB7E;yBAED;kCACJ,KAACO,KAAKuE,KAAK;wBACTnE,MAAK;wBACLoE,YAAY;4BACVC,QAAQ,CAAC,EAACnE,KAAK,EAAC,GAAK/D,sBAAsB+D,OAAO,QAAQhE,iBAAiBoI,KAAK,CAACtE,IAAI;4BACrFC,UAAU,CAAC,EAACC,KAAK,EAAC,GAChB/D,sBAAsB+D,OAAO,QAAQhE,iBAAiBoI,KAAK,CAACtE,IAAI;wBACpE;kCAEC,CAAC4B,sBACA,KAACrF;gCAAUgI,OAAM;gCAAOhD,IAAG;gCAAON,OAAOxE,WAAWmF;0CAClD,cAAA,KAACpF;oCACCgI,cAAa;oCACbxE,MAAK;oCACLkE,MAAK;oCACLhE,OAAO0B,MAAMI,KAAK,CAAC9B,KAAK;oCACxBuE,UAAU,CAACZ,QAAUjC,MAAM8C,YAAY,CAACb,MAAMc,MAAM,CAACzE,KAAK;oCAC1DmE,QAAQzC,MAAMgD,UAAU;;;;kCAKhC,KAAChF,KAAKuE,KAAK;wBACTnE,MAAK;wBACLoE,YAAY;4BACVC,QAAQnI,iBAAiBoI,KAAK,CAACxE,KAAK;4BACpCG,UAAU/D,iBAAiBoI,KAAK,CAACxE,KAAK;wBACxC;kCAEC,CAAC8B,sBACA,KAACrF;gCAAUgI,OAAM;gCAAQhD,IAAG;gCAAQN,OAAOxE,WAAWmF;0CACpD,cAAA,KAACpF;oCACCgI,cAAa;oCACbxE,MAAK;oCACLkE,MAAK;oCACLhE,OAAO0B,MAAMI,KAAK,CAAC9B,KAAK;oCACxBuE,UAAU,CAACZ,QAAUjC,MAAM8C,YAAY,CAACb,MAAMc,MAAM,CAACzE,KAAK;oCAC1DmE,QAAQ;wCACNzC,MAAMgD,UAAU;wCAChBnB;oCACF;oCACAoB,UAAUxB;oCACVyB,WACEzB,wCACE,KAAC3G;wCACCqI,eAAY;wCACZhC,WAAU;wCACV/C,MAAK;yCAELG;;;;kCAMd,KAACP,KAAKuE,KAAK;wBACTnE,MAAK;wBACLoE,YAAY;4BACVC,QAAQnI,iBAAiBoI,KAAK,CAACvE,QAAQ;4BACvCE,UAAU/D,iBAAiBoI,KAAK,CAACvE,QAAQ;wBAC3C;kCAEC,CAAC6B,sBACA,KAACrF;gCAAUgI,OAAM;gCAAWhD,IAAG;gCAAWN,OAAOxE,WAAWmF;0CAC1D,cAAA,KAACpF;oCACCgI,cAAa;oCACbxE,MAAK;oCACLkE,MAAK;oCACLhE,OAAO0B,MAAMI,KAAK,CAAC9B,KAAK;oCACxBuE,UAAU,CAACZ,QAAUjC,MAAM8C,YAAY,CAACb,MAAMc,MAAM,CAACzE,KAAK;oCAC1DmE,QAAQ;wCACNzC,MAAMgD,UAAU;wCAChBnB;oCACF;;;;kCAKR,KAACrH;wBAAO2G,WAAU;wBAASiC,WAAW7G,OAAOiE,SAAS;wBAAE8B,MAAK;kCAC1D/F,OAAOiE,SAAS,GAAG,wBAAwB;;;;0BAGhD,MAACxF;gBAAKkG,MAAK;gBAAKC,WAAU;;oBAA6C;oBAC5C;kCACzB,KAAC1G;wBAAW2G,OAAO;wBAACC,SAAQ;wBAAcC,SAAS;kCACjD,cAAA,KAACpG;4BACC8D,IAAG;4BACHpC,QAAQ+E,qBAAqB;gCAAC5E,UAAU4E;4BAAkB,IAAIpD;sCAC/D;;;;;;;AAOX"}
@@ -1,4 +1,4 @@
1
- export { type AuthState, type AuthStatus, authStateAtom, initialAuthState, toAuthenticatedState, type Workspace, } from '@shipfox/client-shell/runtime';
1
+ export { type AuthState, type AuthStatus, authStateAtom, initialAuthState, toAuthenticatedState, useAuthTransition, type Workspace, } from '@shipfox/client-shell/runtime';
2
2
  export interface AuthFormDraft {
3
3
  email: string;
4
4
  password: string;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/state/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,SAAS,GACf,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,oBAAoB,EAAE,aAAyC,CAAC;AAC7E,eAAO,MAAM,iBAAiB;;CAA4C,CAAC"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/state/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,SAAS,GACf,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,oBAAoB,EAAE,aAAyC,CAAC;AAC7E,eAAO,MAAM,iBAAiB;;CAA4C,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { atom } from 'jotai';
2
- export { authStateAtom, initialAuthState, toAuthenticatedState } from '@shipfox/client-shell/runtime';
2
+ export { authStateAtom, initialAuthState, toAuthenticatedState, useAuthTransition } from '@shipfox/client-shell/runtime';
3
3
  export const initialAuthFormDraft = {
4
4
  email: '',
5
5
  password: ''
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/state/auth.ts"],"sourcesContent":["import {atom} from 'jotai';\n\nexport {\n type AuthState,\n type AuthStatus,\n authStateAtom,\n initialAuthState,\n toAuthenticatedState,\n type Workspace,\n} from '@shipfox/client-shell/runtime';\n\nexport interface AuthFormDraft {\n email: string;\n password: string;\n}\n\nexport const initialAuthFormDraft: AuthFormDraft = {email: '', password: ''};\nexport const authFormDraftAtom = atom<AuthFormDraft>(initialAuthFormDraft);\n"],"names":["atom","authStateAtom","initialAuthState","toAuthenticatedState","initialAuthFormDraft","email","password","authFormDraftAtom"],"mappings":"AAAA,SAAQA,IAAI,QAAO,QAAQ;AAE3B,SAGEC,aAAa,EACbC,gBAAgB,EAChBC,oBAAoB,QAEf,gCAAgC;AAOvC,OAAO,MAAMC,uBAAsC;IAACC,OAAO;IAAIC,UAAU;AAAE,EAAE;AAC7E,OAAO,MAAMC,oBAAoBP,KAAoBI,sBAAsB"}
1
+ {"version":3,"sources":["../../src/state/auth.ts"],"sourcesContent":["import {atom} from 'jotai';\n\nexport {\n type AuthState,\n type AuthStatus,\n authStateAtom,\n initialAuthState,\n toAuthenticatedState,\n useAuthTransition,\n type Workspace,\n} from '@shipfox/client-shell/runtime';\n\nexport interface AuthFormDraft {\n email: string;\n password: string;\n}\n\nexport const initialAuthFormDraft: AuthFormDraft = {email: '', password: ''};\nexport const authFormDraftAtom = atom<AuthFormDraft>(initialAuthFormDraft);\n"],"names":["atom","authStateAtom","initialAuthState","toAuthenticatedState","useAuthTransition","initialAuthFormDraft","email","password","authFormDraftAtom"],"mappings":"AAAA,SAAQA,IAAI,QAAO,QAAQ;AAE3B,SAGEC,aAAa,EACbC,gBAAgB,EAChBC,oBAAoB,EACpBC,iBAAiB,QAEZ,gCAAgC;AAOvC,OAAO,MAAMC,uBAAsC;IAACC,OAAO;IAAIC,UAAU;AAAE,EAAE;AAC7E,OAAO,MAAMC,oBAAoBR,KAAoBK,sBAAsB"}