@shipfox/client-auth 4.0.0 → 5.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 (84) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/dist/components/email-code-verification.d.ts.map +1 -1
  4. package/dist/components/email-code-verification.js +1 -1
  5. package/dist/components/email-code-verification.js.map +1 -1
  6. package/dist/components/workspace-switcher.stories.d.ts +1 -1
  7. package/dist/continuation.d.ts +1 -1
  8. package/dist/continuation.d.ts.map +1 -1
  9. package/dist/continuation.js +1 -1
  10. package/dist/continuation.js.map +1 -1
  11. package/dist/hooks/api/auth-mapper.d.ts +21 -0
  12. package/dist/hooks/api/auth-mapper.d.ts.map +1 -0
  13. package/dist/hooks/api/auth-mapper.js +25 -0
  14. package/dist/hooks/api/auth-mapper.js.map +1 -0
  15. package/dist/hooks/api/login-auth.d.ts +3 -25
  16. package/dist/hooks/api/login-auth.d.ts.map +1 -1
  17. package/dist/hooks/api/login-auth.js +5 -2
  18. package/dist/hooks/api/login-auth.js.map +1 -1
  19. package/dist/hooks/api/password-reset-auth.d.ts +3 -25
  20. package/dist/hooks/api/password-reset-auth.d.ts.map +1 -1
  21. package/dist/hooks/api/password-reset-auth.js +5 -2
  22. package/dist/hooks/api/password-reset-auth.js.map +1 -1
  23. package/dist/hooks/api/signup-auth.d.ts +1 -25
  24. package/dist/hooks/api/signup-auth.d.ts.map +1 -1
  25. package/dist/hooks/api/signup-auth.js +5 -2
  26. package/dist/hooks/api/signup-auth.js.map +1 -1
  27. package/dist/hooks/api/verify-email-auth.d.ts +1 -12
  28. package/dist/hooks/api/verify-email-auth.d.ts.map +1 -1
  29. package/dist/hooks/api/verify-email-auth.js +6 -3
  30. package/dist/hooks/api/verify-email-auth.js.map +1 -1
  31. package/dist/hooks/api/workspace-auth.d.ts +3 -17
  32. package/dist/hooks/api/workspace-auth.d.ts.map +1 -1
  33. package/dist/hooks/api/workspace-auth.js +5 -2
  34. package/dist/hooks/api/workspace-auth.js.map +1 -1
  35. package/dist/hooks/api/workspace-mapper.d.ts +8 -0
  36. package/dist/hooks/api/workspace-mapper.d.ts.map +1 -0
  37. package/dist/hooks/api/workspace-mapper.js +9 -0
  38. package/dist/hooks/api/workspace-mapper.js.map +1 -0
  39. package/dist/index.d.ts +0 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +0 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/pages/invitation-context.d.ts +9 -14
  44. package/dist/pages/invitation-context.d.ts.map +1 -1
  45. package/dist/pages/invitation-context.js +4 -27
  46. package/dist/pages/invitation-context.js.map +1 -1
  47. package/dist/pages/login-page.js +2 -2
  48. package/dist/pages/login-page.js.map +1 -1
  49. package/dist/pages/logout-page.js +1 -1
  50. package/dist/pages/logout-page.js.map +1 -1
  51. package/dist/pages/password-reset-page.d.ts.map +1 -1
  52. package/dist/pages/password-reset-page.js +1 -4
  53. package/dist/pages/password-reset-page.js.map +1 -1
  54. package/dist/pages/signup-page.js +9 -9
  55. package/dist/pages/signup-page.js.map +1 -1
  56. package/dist/tsconfig.test.tsbuildinfo +1 -1
  57. package/package.json +4 -3
  58. package/src/components/email-code-verification.tsx +1 -1
  59. package/src/continuation.ts +1 -1
  60. package/src/hooks/api/auth-mapper.test.ts +77 -0
  61. package/src/hooks/api/auth-mapper.ts +39 -0
  62. package/src/hooks/api/login-auth.test.ts +1 -1
  63. package/src/hooks/api/login-auth.ts +8 -3
  64. package/src/hooks/api/password-reset-auth.test.ts +1 -1
  65. package/src/hooks/api/password-reset-auth.ts +15 -9
  66. package/src/hooks/api/signup-auth.ts +8 -3
  67. package/src/hooks/api/verify-email-auth.ts +17 -11
  68. package/src/hooks/api/workspace-auth.ts +8 -3
  69. package/src/hooks/api/workspace-mapper.test.ts +21 -0
  70. package/src/hooks/api/workspace-mapper.ts +11 -0
  71. package/src/index.ts +0 -1
  72. package/src/pages/invitation-context.ts +8 -31
  73. package/src/pages/login-page.test.tsx +1 -1
  74. package/src/pages/login-page.tsx +2 -2
  75. package/src/pages/logout-page.tsx +1 -1
  76. package/src/pages/password-reset-page.tsx +1 -4
  77. package/src/pages/signup-page.test.tsx +1 -1
  78. package/src/pages/signup-page.tsx +9 -9
  79. package/tsconfig.build.tsbuildinfo +1 -1
  80. package/dist/components/auth-shell.d.ts +0 -13
  81. package/dist/components/auth-shell.d.ts.map +0 -1
  82. package/dist/components/auth-shell.js +0 -68
  83. package/dist/components/auth-shell.js.map +0 -1
  84. package/src/components/auth-shell.tsx +0 -68
@@ -1,26 +1,21 @@
1
- import type { PreviewInvitationPendingDto, PreviewInvitationResponseDto } from '@shipfox/api-workspaces-dto';
1
+ import { type InvitationPreview, pendingInvitation } from '@shipfox/client-invitations';
2
2
  export declare function extractInvitationToken(redirect: unknown): string | undefined;
3
- export declare function pendingInvitation(data: PreviewInvitationResponseDto | undefined): PreviewInvitationPendingDto | undefined;
4
- /**
5
- * Mirrors the hook in `@shipfox/client-invitations` but lives in `client-auth`
6
- * to avoid a circular dependency. Signup/login pages call this to lock the
7
- * email field when arriving from an invitation link.
8
- */
9
3
  export declare function useInvitationContext(token: string | undefined): import("@tanstack/react-query").UseQueryResult<NoInfer<{
10
4
  status: "pending";
11
- workspace_id: string;
12
- workspace_name: string;
5
+ workspaceId: string;
6
+ workspaceName: string;
13
7
  email: string;
14
- invited_by_display: string | null;
15
- expires_at: string;
8
+ invitedByDisplay?: string;
9
+ expiresAt: string;
16
10
  } | {
17
11
  status: "expired";
18
- workspace_name: string;
19
- expires_at: string;
12
+ workspaceName: string;
13
+ expiresAt: string;
20
14
  } | {
21
15
  status: "already_used";
22
- workspace_name: string;
16
+ workspaceName: string;
23
17
  } | {
24
18
  status: "invalid";
25
19
  }>, Error>;
20
+ export { type InvitationPreview, pendingInvitation };
26
21
  //# sourceMappingURL=invitation-context.d.ts.map
@@ -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;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
+ {"version":3,"file":"invitation-context.d.ts","sourceRoot":"","sources":["../../src/pages/invitation-context.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,iBAAiB,EAElB,MAAM,6BAA6B,CAAC;AAGrC,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE5E;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS;;;;;;;;;;;;;;;;WAE7D;AAED,OAAO,EAAC,KAAK,iBAAiB,EAAE,iBAAiB,EAAC,CAAC"}
@@ -1,34 +1,11 @@
1
- import { apiRequest } from '@shipfox/client-api';
2
- import { useQuery } from '@tanstack/react-query';
1
+ import { pendingInvitation, usePreviewInvitation } from '@shipfox/client-invitations';
3
2
  import { parseRedirectContext } from '#/components/redirect-context.js';
4
3
  export function extractInvitationToken(redirect) {
5
4
  return parseRedirectContext(redirect).invitationToken;
6
5
  }
7
- async function fetchPreview(token) {
8
- const params = new URLSearchParams({
9
- token
10
- });
11
- return await apiRequest(`/invitations/preview?${params.toString()}`);
12
- }
13
- export function pendingInvitation(data) {
14
- return data?.status === 'pending' ? data : undefined;
15
- }
16
- /**
17
- * Mirrors the hook in `@shipfox/client-invitations` but lives in `client-auth`
18
- * to avoid a circular dependency. Signup/login pages call this to lock the
19
- * email field when arriving from an invitation link.
20
- */ export function useInvitationContext(token) {
21
- return useQuery({
22
- queryKey: [
23
- 'invitations',
24
- 'preview',
25
- token ?? ''
26
- ],
27
- queryFn: ()=>fetchPreview(token),
28
- enabled: Boolean(token),
29
- retry: false,
30
- staleTime: 30_000
31
- });
6
+ export function useInvitationContext(token) {
7
+ return usePreviewInvitation(token);
32
8
  }
9
+ export { pendingInvitation };
33
10
 
34
11
  //# sourceMappingURL=invitation-context.js.map
@@ -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';\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
+ {"version":3,"sources":["../../src/pages/invitation-context.ts"],"sourcesContent":["import {\n type InvitationPreview,\n pendingInvitation,\n usePreviewInvitation,\n} from '@shipfox/client-invitations';\nimport {parseRedirectContext} from '#/components/redirect-context.js';\n\nexport function extractInvitationToken(redirect: unknown): string | undefined {\n return parseRedirectContext(redirect).invitationToken;\n}\n\nexport function useInvitationContext(token: string | undefined) {\n return usePreviewInvitation(token);\n}\n\nexport {type InvitationPreview, pendingInvitation};\n"],"names":["pendingInvitation","usePreviewInvitation","parseRedirectContext","extractInvitationToken","redirect","invitationToken","useInvitationContext","token"],"mappings":"AAAA,SAEEA,iBAAiB,EACjBC,oBAAoB,QACf,8BAA8B;AACrC,SAAQC,oBAAoB,QAAO,mCAAmC;AAEtE,OAAO,SAASC,uBAAuBC,QAAiB;IACtD,OAAOF,qBAAqBE,UAAUC,eAAe;AACvD;AAEA,OAAO,SAASC,qBAAqBC,KAAyB;IAC5D,OAAON,qBAAqBM;AAC9B;AAEA,SAAgCP,iBAAiB,GAAE"}
@@ -1,5 +1,6 @@
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
4
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
4
5
  import { Callout } from '@shipfox/react-ui/callout';
5
6
  import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
@@ -9,7 +10,6 @@ import { useForm } from '@tanstack/react-form';
9
10
  import { Link, useSearch } from '@tanstack/react-router';
10
11
  import { useAtom } from 'jotai';
11
12
  import { useEffect, useRef, useState } from 'react';
12
- import { AuthShell } from '#/components/auth-shell.js';
13
13
  import { useLoginAuth } from '#hooks/api/login-auth.js';
14
14
  import { authFormDraftAtom, initialAuthFormDraft } from '#state/auth.js';
15
15
  import { loginErrorToFormError } from './form-errors.js';
@@ -91,7 +91,7 @@ export function LoginPage() {
91
91
  ]);
92
92
  const isInvitationEmailLocked = Boolean(invitationPending);
93
93
  const invitationRedirect = invitationToken ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}` : undefined;
94
- const headerTitle = invitationPending ? `Join ${invitationPending.workspace_name}` : 'Connect to Shipfox';
94
+ const headerTitle = invitationPending ? `Join ${invitationPending.workspaceName}` : 'Connect to Shipfox';
95
95
  const headerDescription = invitationPending ? 'Log in to accept your invitation.' : 'Log in to access Shipfox.';
96
96
  function persistDraft() {
97
97
  const { email, password } = form.state.values;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/login-page.tsx"],"sourcesContent":["import {loginBodySchema} from '@shipfox/api-auth-dto';\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 {AuthShell} from '#/components/auth-shell.js';\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.workspace_name}`\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","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","Text","useForm","Link","useSearch","useAtom","useEffect","useRef","useState","AuthShell","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","workspace_name","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} 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":";AAAA,SAAQA,eAAe,QAAO,wBAAwB;AACtD,SAAQC,SAAS,QAAO,gCAAgC;AACxD,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,IAAI,QAAO,+BAA+B;AAClD,SAAQC,OAAO,QAAO,uBAAuB;AAC7C,SAAQC,IAAI,EAAEC,SAAS,QAAO,yBAAyB;AACvD,SAAQC,OAAO,QAAO,QAAQ;AAC9B,SAAQC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAClD,SAAQC,YAAY,QAAO,2BAA2B;AACtD,SAAQC,iBAAiB,EAAEC,oBAAoB,QAAO,iBAAiB;AACvE,SAAQC,qBAAqB,QAAO,mBAAmB;AACvD,SACEC,sBAAsB,EACtBC,iBAAiB,EACjBC,oBAAoB,QACf,0BAA0B;AAEjC,OAAO,SAASC;IACd,MAAMC,QAAQR;IACd,MAAMS,SAASd,UAAU;QAACe,QAAQ;IAAK;IACvC,MAAMC,kBAAkBP,uBAAuBK,OAAOG,QAAQ;IAC9D,MAAMC,oBAAoBP,qBAAqBK;IAC/C,MAAMG,oBAAoBT,kBAAkBQ,kBAAkBE,IAAI;IAClE,MAAM,CAACC,eAAeC,iBAAiB,GAAGrB,QAAQK;IAClD,MAAM,CAACiB,WAAWC,aAAa,GAAGpB;IAClC,MAAMqB,WAAWtB,OAAOkB;IACxBI,SAASC,OAAO,GAAGL;IACnB,uEAAuE;IACvE,2DAA2D;IAC3D,MAAMM,sBAAsBxB,OAAO;IAEnC,MAAMyB,OAAO9B,QAAQ;QACnB+B,eAAe;YAACC,OAAOT,cAAcS,KAAK;YAAEC,UAAUV,cAAcU,QAAQ;QAAA;QAC5EC,UAAU,OAAO,EAACC,KAAK,EAAC;YACtBT,aAAaU;YACb,IAAI;gBACF,MAAMrB,MAAMsB,WAAW,CAACF;gBACxBN,oBAAoBD,OAAO,GAAG;gBAC9BJ,iBAAiBf;YACnB,EAAE,OAAO6B,OAAO;gBACd,MAAMC,SAAS7B,sBAAsB4B;gBACrC,IAAIC,OAAOC,IAAI,KAAK,SAAS;oBAC3BV,KAAKW,YAAY,CAACF,OAAOG,KAAK,EAAE,CAACC,OAAU,CAAA;4BACzC,GAAGA,IAAI;4BACPC,UAAU;gCAAC,GAAGD,KAAKC,QAAQ;gCAAEC,UAAUN,OAAOO,OAAO;4BAAA;wBACvD,CAAA;gBACF,OAAO;oBACLpB,aAAaa,OAAOO,OAAO;gBAC7B;YACF;QACF;IACF;IAEA,yEAAyE;IACzE,2EAA2E;IAC3E1C,UAAU;QACR,IAAIiB,qBAAqBS,KAAKiB,KAAK,CAACC,MAAM,CAAChB,KAAK,KAAKX,kBAAkBW,KAAK,EAAE;YAC5EF,KAAKmB,aAAa,CAAC,SAAS5B,kBAAkBW,KAAK;YACnDR,iBAAiB,CAACI,UAAa,CAAA;oBAAC,GAAGA,OAAO;oBAAEI,OAAOX,kBAAkBW,KAAK;gBAAA,CAAA;QAC5E;IACF,GAAG;QAACX;QAAmBS;QAAMN;KAAiB;IAE9C,sEAAsE;IACtE,yEAAyE;IACzE,4EAA4E;IAC5EpB,UAAU;QACR,OAAO;YACL,IAAIyB,oBAAoBD,OAAO,EAAE;YACjC,MAAM,EAACI,KAAK,EAAEC,QAAQ,EAAC,GAAGH,KAAKiB,KAAK,CAACC,MAAM;YAC3C,IAAIhB,UAAUL,SAASC,OAAO,CAACI,KAAK,IAAIC,aAAaN,SAASC,OAAO,CAACK,QAAQ,EAAE;gBAC9ET,iBAAiB;oBAACQ;oBAAOC;gBAAQ;YACnC;QACF;IACF,GAAG;QAACH;QAAMN;KAAiB;IAE3B,MAAM0B,0BAA0BC,QAAQ9B;IACxC,MAAM+B,qBAAqBlC,kBACvB,CAAC,0BAA0B,EAAEmC,mBAAmBnC,kBAAkB,GAClEkB;IACJ,MAAMkB,cAAcjC,oBAChB,CAAC,KAAK,EAAEA,kBAAkBkC,aAAa,EAAE,GACzC;IACJ,MAAMC,oBAAoBnC,oBACtB,sCACA;IAEJ,SAASoC;QACP,MAAM,EAACzB,KAAK,EAAEC,QAAQ,EAAC,GAAGH,KAAKiB,KAAK,CAACC,MAAM;QAC3CxB,iBAAiB;YAACQ;YAAOC;QAAQ;IACnC;IAEA,qBACE,MAAC1C;QAAUmE,OAAOJ;QAAaK,aAAaH;;0BAC1C,MAAC1B;gBACC8B,WAAU;gBACVC,UAAU;gBACV3B,UAAU,CAAC4B;oBACTA,MAAMC,cAAc;oBACpBD,MAAME,eAAe;oBACrB,KAAKlC,KAAKmC,YAAY;gBACxB;;oBAECxC,0BACC,KAAC/B;wBAAQwE,MAAK;wBAAQC,MAAK;kCACxB1C;yBAED;kCACJ,KAACK,KAAKsC,KAAK;wBACTC,MAAK;wBACLC,YAAY;4BAACC,QAAQjF,gBAAgBkF,KAAK,CAACxC,KAAK;4BAAEE,UAAU5C,gBAAgBkF,KAAK,CAACxC,KAAK;wBAAA;kCAEtF,CAACU,sBACA,KAAC/C;gCAAU8E,OAAM;gCAAQC,IAAG;gCAAQpC,OAAOzC,WAAW6C;0CACpD,cAAA,KAAC9C;oCACC+E,cAAa;oCACbN,MAAK;oCACLF,MAAK;oCACLhC,OAAOO,MAAMK,KAAK,CAACZ,KAAK;oCACxByC,UAAU,CAACd,QAAUpB,MAAMmC,YAAY,CAACf,MAAMgB,MAAM,CAAC3C,KAAK;oCAC1DoC,QAAQ;wCACN7B,MAAMqC,UAAU;wCAChBtB;oCACF;oCACAuB,UAAU9B;oCACV+B,WACE/B,wCACE,KAACpD;wCACCoF,eAAY;wCACZtB,WAAU;wCACVS,MAAK;yCAELjC;;;;kCAMd,KAACN,KAAKsC,KAAK;wBACTC,MAAK;wBACLC,YAAY;4BACVC,QAAQjF,gBAAgBkF,KAAK,CAACvC,QAAQ;4BACtCC,UAAU5C,gBAAgBkF,KAAK,CAACvC,QAAQ;wBAC1C;kCAEC,CAACS,sBACA,KAAC/C;gCAAU8E,OAAM;gCAAWC,IAAG;gCAAWpC,OAAOzC,WAAW6C;0CAC1D,cAAA,KAAC9C;oCACC+E,cAAa;oCACbN,MAAK;oCACLF,MAAK;oCACLhC,OAAOO,MAAMK,KAAK,CAACZ,KAAK;oCACxByC,UAAU,CAACd,QAAUpB,MAAMmC,YAAY,CAACf,MAAMgB,MAAM,CAAC3C,KAAK;oCAC1DoC,QAAQ;wCACN7B,MAAMqC,UAAU;wCAChBtB;oCACF;;;;kCAKR,KAAChE;wBAAW0F,OAAO;wBAACC,SAAQ;wBAASxB,WAAU;kCAC7C,cAAA,KAAC3D;4BAAKoF,IAAG;sCAAc;;;kCAEzB,KAAC7F;wBAAOoE,WAAU;wBAAS0B,WAAWvE,MAAMwE,SAAS;wBAAEpB,MAAK;kCACzDpD,MAAMwE,SAAS,GAAG,kBAAkB;;;;0BAGzC,MAACxF;gBAAKyF,MAAK;gBAAK5B,WAAU;;oBAA6C;oBACrD;kCAChB,KAACnE;wBAAW0F,OAAO;wBAACC,SAAQ;wBAAcK,SAAS;kCACjD,cAAA,KAACxF;4BACCoF,IAAG;4BACHrE,QAAQoC,qBAAqB;gCAACjC,UAAUiC;4BAAkB,IAAIhB;sCAC/D;;;;;;;AAOX"}
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AuthShell } from '@shipfox/client-shell/runtime';
2
3
  import { Text } from '@shipfox/react-ui/typography';
3
4
  import { useNavigate, useRouter, useSearch } from '@tanstack/react-router';
4
5
  import { useEffect } from 'react';
5
- import { AuthShell } from '#/components/auth-shell.js';
6
6
  import { sanitizeRedirectPath } from '#/components/redirect-target.js';
7
7
  import { useLogoutAuth } from '#hooks/api/logout-auth.js';
8
8
  export function LogoutPage() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/logout-page.tsx"],"sourcesContent":["import {Text} from '@shipfox/react-ui/typography';\nimport {useNavigate, useRouter, useSearch} from '@tanstack/react-router';\nimport {useEffect} from 'react';\nimport {AuthShell} from '#/components/auth-shell.js';\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":["Text","useNavigate","useRouter","useSearch","useEffect","AuthShell","sanitizeRedirectPath","useLogoutAuth","LogoutPage","logout","navigate","router","search","strict","target","redirect","mutateAsync","finally","undefined","history","replace","to","title","description","size","className"],"mappings":";AAAA,SAAQA,IAAI,QAAO,+BAA+B;AAClD,SAAQC,WAAW,EAAEC,SAAS,EAAEC,SAAS,QAAO,yBAAyB;AACzE,SAAQC,SAAS,QAAO,QAAQ;AAChC,SAAQC,SAAS,QAAO,6BAA6B;AACrD,SAAQC,oBAAoB,QAAO,kCAAkC;AACrE,SAAQC,aAAa,QAAO,4BAA4B;AAExD,OAAO,SAASC;IACd,MAAMC,SAASF;IACf,MAAMG,WAAWT;IACjB,MAAMU,SAAST;IACf,MAAMU,SAAST,UAAU;QAACU,QAAQ;IAAK;IACvC,MAAMC,SAASR,qBAAqBM,OAAOG,QAAQ;IAEnDX,UAAU;QACRK,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,KAACT;QAAUiB,OAAM;QAAcC,aAAY;kBACzC,cAAA,KAACvB;YAAKwB,MAAK;YAAKC,WAAU;sBAA6C;;;AAK7E"}
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 +1 @@
1
- {"version":3,"file":"password-reset-page.d.ts","sourceRoot":"","sources":["../../src/pages/password-reset-page.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,iBAAiB,gCAShC"}
1
+ {"version":3,"file":"password-reset-page.d.ts","sourceRoot":"","sources":["../../src/pages/password-reset-page.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,iBAAiB,gCAShC"}
@@ -1,5 +1,6 @@
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
4
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
4
5
  import { Callout } from '@shipfox/react-ui/callout';
5
6
  import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
@@ -9,9 +10,7 @@ import { useForm } from '@tanstack/react-form';
9
10
  import { Link, useNavigate, useSearch } from '@tanstack/react-router';
10
11
  import { useAtom } from 'jotai';
11
12
  import { useEffect, useRef, useState } from 'react';
12
- import { AuthShell } from '#/components/auth-shell.js';
13
13
  import { useConfirmPasswordResetAuth, useRequestPasswordResetAuth } from '#hooks/api/password-reset-auth.js';
14
- import { useRefreshAuth } from '#hooks/api/refresh-auth.js';
15
14
  import { authFormDraftAtom, initialAuthFormDraft } from '#state/auth.js';
16
15
  import { passwordResetConfirmErrorToFormError, passwordResetRequestErrorToFormError } from './form-errors.js';
17
16
  export function PasswordResetPage() {
@@ -174,7 +173,6 @@ function PasswordResetRequest() {
174
173
  }
175
174
  function PasswordResetConfirm({ token }) {
176
175
  const confirmPasswordReset = useConfirmPasswordResetAuth();
177
- const refreshAuth = useRefreshAuth();
178
176
  const navigate = useNavigate();
179
177
  const [, setAuthFormDraft] = useAtom(authFormDraftAtom);
180
178
  const [formError, setFormError] = useState();
@@ -190,7 +188,6 @@ function PasswordResetConfirm({ token }) {
190
188
  new_password: value.new_password
191
189
  });
192
190
  await confirmPasswordReset.mutateAsync(body);
193
- await refreshAuth();
194
191
  setAuthFormDraft(initialAuthFormDraft);
195
192
  toast.success('Your password has been changed. You are now logged in.');
196
193
  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 {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 {AuthShell} from '#/components/auth-shell.js';\nimport {\n useConfirmPasswordResetAuth,\n useRequestPasswordResetAuth,\n} from '#hooks/api/password-reset-auth.js';\nimport {useRefreshAuth} from '#hooks/api/refresh-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 refreshAuth = useRefreshAuth();\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 await refreshAuth();\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","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","toast","Text","useForm","Link","useNavigate","useSearch","useAtom","useEffect","useRef","useState","AuthShell","useConfirmPasswordResetAuth","useRequestPasswordResetAuth","useRefreshAuth","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","refreshAuth","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} 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,5 +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
4
  import { displayNameFieldError } from '@shipfox/client-ui';
4
5
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
5
6
  import { Callout } from '@shipfox/react-ui/callout';
@@ -11,7 +12,6 @@ import { useForm } from '@tanstack/react-form';
11
12
  import { Link, useNavigate, useSearch } from '@tanstack/react-router';
12
13
  import { useAtom } from 'jotai';
13
14
  import { useEffect, useRef, useState } from 'react';
14
- import { AuthShell } from '#/components/auth-shell.js';
15
15
  import { EmailCodeVerification } from '#/components/email-code-verification.js';
16
16
  import { useRefreshAuth } from '#hooks/api/refresh-auth.js';
17
17
  import { useSignupAuth } from '#hooks/api/signup-auth.js';
@@ -69,17 +69,17 @@ export function SignupPage() {
69
69
  // Refresh failures don't block the success message — the next API
70
70
  // call's 401 handling will re-route the user.
71
71
  }
72
- toast.success(`You joined ${invitationPending.workspace_name}.`);
72
+ toast.success(`You joined ${invitationPending.workspaceName}.`);
73
73
  await navigate({
74
74
  to: '/workspaces/$wid',
75
75
  params: {
76
- wid: result.membership.workspace_id
76
+ wid: result.membership.workspaceId
77
77
  }
78
78
  });
79
79
  return;
80
80
  }
81
- if (invitationToken && result.accept_error) {
82
- toast.error(result.accept_error.message);
81
+ if (invitationToken && result.acceptError) {
82
+ toast.error(result.acceptError.message);
83
83
  await navigate({
84
84
  to: '/invitations/accept',
85
85
  search: {
@@ -88,15 +88,15 @@ export function SignupPage() {
88
88
  });
89
89
  return;
90
90
  }
91
- if (!result.email_challenge) {
91
+ if (!result.emailChallenge) {
92
92
  throw new Error('Signup did not return an email verification challenge');
93
93
  }
94
94
  setEmailChallenge({
95
95
  email: result.user.email,
96
- id: result.email_challenge.id
96
+ id: result.emailChallenge.id
97
97
  });
98
98
  setResendError(undefined);
99
- setNextResendAvailableAt(result.email_challenge.next_resend_available_at);
99
+ setNextResendAvailableAt(result.emailChallenge.nextResendAvailableAt);
100
100
  } catch (error) {
101
101
  const mapped = signupErrorToFormError(error);
102
102
  if (mapped.kind === 'field') {
@@ -208,7 +208,7 @@ export function SignupPage() {
208
208
  })
209
209
  });
210
210
  }
211
- const headerTitle = invitationPending ? `Join ${invitationPending.workspace_name}` : 'Create your Shipfox account';
211
+ const headerTitle = invitationPending ? `Join ${invitationPending.workspaceName}` : 'Create your Shipfox account';
212
212
  const headerDescription = invitationPending ? `Create an account to accept your invitation.` : 'Start with your email and a password.';
213
213
  const isInvitationEmailLocked = Boolean(invitationPending);
214
214
  const invitationRedirect = invitationToken ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}` : undefined;
@@ -1 +1 @@
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
+ {"version":3,"sources":["../../src/pages/signup-page.tsx"],"sourcesContent":["import {signupBodySchema} from '@shipfox/api-auth-dto';\nimport {AuthShell} from '@shipfox/client-shell/runtime';\nimport {displayNameFieldError} from '@shipfox/client-ui';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, useNavigate, useSearch} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {EmailCodeVerification} from '#/components/email-code-verification.js';\nimport {useRefreshAuth} from '#hooks/api/refresh-auth.js';\nimport {useSignupAuth} from '#hooks/api/signup-auth.js';\nimport {useResendEmailVerificationAuth, useVerifyEmailAuth} from '#hooks/api/verify-email-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {signupErrorToFormError} from './form-errors.js';\nimport {authErrorMessage} from './form-utils.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function SignupPage() {\n const signup = useSignupAuth();\n const verifyEmail = useVerifyEmailAuth();\n const resendEmailVerification = useResendEmailVerificationAuth();\n const refreshAuth = useRefreshAuth();\n const navigate = useNavigate();\n const search = useSearch({strict: false}) as {redirect?: unknown};\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [emailChallenge, setEmailChallenge] = useState<{email: string; id: string} | undefined>();\n const [nextResendAvailableAt, setNextResendAvailableAt] = useState<string | undefined>();\n const [formError, setFormError] = useState<string | undefined>();\n const [resendError, setResendError] = useState<string | undefined>();\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n // Set just before clearing the draft on success so the unmount cleanup\n // below does not repersist the just-submitted credentials.\n const skipDraftPersistRef = useRef(false);\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password, name: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const body = signupBodySchema.parse({\n email: value.email,\n password: value.password,\n name: value.name,\n ...(invitationToken ? {invitation_token: invitationToken} : {}),\n });\n const result = await signup.mutateAsync(body);\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n\n if (invitationToken && result.membership && invitationPending) {\n try {\n await refreshAuth();\n } catch {\n // Refresh failures don't block the success message — the next API\n // call's 401 handling will re-route the user.\n }\n toast.success(`You joined ${invitationPending.workspaceName}.`);\n await navigate({\n to: '/workspaces/$wid',\n params: {wid: result.membership.workspaceId},\n });\n return;\n }\n\n if (invitationToken && result.acceptError) {\n toast.error(result.acceptError.message);\n await navigate({\n to: '/invitations/accept',\n search: {token: invitationToken},\n });\n return;\n }\n\n if (!result.emailChallenge) {\n throw new Error('Signup did not return an email verification challenge');\n }\n setEmailChallenge({email: result.user.email, id: result.emailChallenge.id});\n setResendError(undefined);\n setNextResendAvailableAt(result.emailChallenge.nextResendAvailableAt);\n } catch (error) {\n const mapped = signupErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n // When arriving from an invitation link, prefill the email and lock it.\n useEffect(() => {\n if (invitationPending && form.state.values.email !== invitationPending.email) {\n form.setFieldValue('email', invitationPending.email);\n setAuthFormDraft((current) => ({...current, email: invitationPending.email}));\n }\n }, [invitationPending, form, setAuthFormDraft]);\n\n // Sync form values back to the Jotai draft on unmount (only email + password\n // — name is intentionally not persisted across navigation). Skipped after a\n // successful signup because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n async function onResendVerificationEmail() {\n if (!emailChallenge || resendEmailVerification.isPending) return;\n\n setResendError(undefined);\n try {\n const result = await resendEmailVerification.mutateAsync({\n email: emailChallenge.email,\n challenge_id: emailChallenge.id,\n });\n setNextResendAvailableAt(result.next_resend_available_at);\n toast.success('If another verification email can be sent, it will arrive shortly.');\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }\n\n if (emailChallenge) {\n return (\n <EmailCodeVerification\n destination={emailChallenge.email}\n nextResendAvailableAt={nextResendAvailableAt}\n isResending={resendEmailVerification.isPending}\n isVerifying={verifyEmail.isPending}\n error={resendError}\n onVerify={async (code) => {\n setResendError(undefined);\n try {\n await verifyEmail.mutateAsync({\n email: emailChallenge.email,\n challenge_id: emailChallenge.id,\n code,\n });\n await refreshAuth();\n toast.success('Your email is verified. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }}\n onResend={onResendVerificationEmail}\n onUseAnotherEmail={() => {\n setEmailChallenge(undefined);\n setResendError(undefined);\n }}\n >\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already verified?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </EmailCodeVerification>\n );\n }\n\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Create your Shipfox account';\n const headerDescription = invitationPending\n ? `Create an account to accept your invitation.`\n : 'Start with your email and a password.';\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) => displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Name\" id=\"name\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"name\"\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"email\"\n validators={{\n onBlur: signupBodySchema.shape.email,\n onSubmit: signupBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n readOnly={isInvitationEmailLocked}\n iconRight={\n isInvitationEmailLocked ? (\n <Icon\n aria-hidden=\"true\"\n className=\"size-16 text-foreground-neutral-disabled\"\n name=\"lockLine\"\n />\n ) : undefined\n }\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"password\"\n validators={{\n onBlur: signupBodySchema.shape.password,\n onSubmit: signupBodySchema.shape.password,\n }}\n >\n {(field) => (\n <FormField label=\"Password\" id=\"password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"new-password\"\n name=\"password\"\n type=\"password\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n />\n </FormField>\n )}\n </form.Field>\n <Button className=\"w-full\" isLoading={signup.isPending} type=\"submit\">\n {signup.isPending ? 'Creating account...' : 'Create account'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already have an account?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link\n to=\"/auth/login\"\n search={invitationRedirect ? {redirect: invitationRedirect} : undefined}\n >\n Log in\n </Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n"],"names":["signupBodySchema","AuthShell","displayNameFieldError","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","Link","useNavigate","useSearch","useAtom","useEffect","useRef","useState","EmailCodeVerification","useRefreshAuth","useSignupAuth","useResendEmailVerificationAuth","useVerifyEmailAuth","authFormDraftAtom","initialAuthFormDraft","signupErrorToFormError","authErrorMessage","extractInvitationToken","pendingInvitation","useInvitationContext","SignupPage","signup","verifyEmail","resendEmailVerification","refreshAuth","navigate","search","strict","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","emailChallenge","setEmailChallenge","nextResendAvailableAt","setNextResendAvailableAt","formError","setFormError","resendError","setResendError","draftRef","current","skipDraftPersistRef","form","defaultValues","email","password","name","onSubmit","value","undefined","body","parse","invitation_token","result","mutateAsync","membership","success","workspaceName","to","params","wid","workspaceId","acceptError","error","message","token","Error","user","id","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","state","values","setFieldValue","onResendVerificationEmail","isPending","challenge_id","next_resend_available_at","destination","isResending","isVerifying","onVerify","code","replace","onResend","onUseAnotherEmail","size","className","asChild","variant","underline","headerTitle","headerDescription","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","persistDraft","title","description","noValidate","event","preventDefault","stopPropagation","handleSubmit","role","type","Field","validators","onBlur","shape","label","autoComplete","onChange","handleChange","target","handleBlur","readOnly","iconRight","aria-hidden","isLoading"],"mappings":"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"}