@shipfox/client-auth 22.0.1 → 22.0.3

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.
@@ -4,8 +4,8 @@ import type {Decorator, Preview} from '@storybook/react';
4
4
 
5
5
  if (typeof document !== 'undefined' && document.fonts) {
6
6
  void Promise.all([
7
- document.fonts.load("16px 'Inter'"),
8
- document.fonts.load("italic 16px 'Inter'"),
7
+ document.fonts.load("16px 'IBM Plex Sans'"),
8
+ document.fonts.load("italic 16px 'IBM Plex Sans'"),
9
9
  document.fonts.load("16px 'Commit Mono'"),
10
10
  document.fonts.load("bold 16px 'Commit Mono'"),
11
11
  ]);
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 42 files with swc (156.41ms)
2
+ Successfully compiled: 42 files with swc (466.82ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @shipfox/client-auth
2
2
 
3
+ ## 22.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - a7fb52d: Supports server-declared Markdown in signup denial messages while keeping custom policy messages plain by default.
8
+
9
+ Existing environment-backed messages now interpret Markdown syntax. Older clients display the Markdown source as plain text.
10
+
11
+ - Updated dependencies [a7fb52d]
12
+ - Updated dependencies [ddcc546]
13
+ - @shipfox/react-ui@2.2.0
14
+ - @shipfox/client-invitations@22.0.3
15
+ - @shipfox/client-shell@22.0.3
16
+ - @shipfox/client-ui@22.0.3
17
+
18
+ ## 22.0.2
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [87d9bd8]
23
+ - @shipfox/react-ui@2.1.1
24
+ - @shipfox/client-invitations@22.0.2
25
+ - @shipfox/client-shell@22.0.2
26
+ - @shipfox/client-ui@22.0.2
27
+
3
28
  ## 22.0.1
4
29
 
5
30
  ### Patch Changes
@@ -5,6 +5,7 @@ export type FormErrorMapping<TField extends string> = {
5
5
  } | {
6
6
  kind: 'form';
7
7
  message: string;
8
+ format?: 'markdown';
8
9
  };
9
10
  type LoginField = 'email' | 'password';
10
11
  type SignupField = 'email' | 'password' | 'name';
@@ -1 +1 @@
1
- {"version":3,"file":"form-errors.d.ts","sourceRoot":"","sources":["../../src/pages/form-errors.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,CAAC,MAAM,SAAS,MAAM,IAC9C;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAC/C;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAC;AAEpC,KAAK,UAAU,GAAG,OAAO,GAAG,UAAU,CAAC;AACvC,KAAK,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AACjD,KAAK,yBAAyB,GAAG,OAAO,CAAC;AACzC,KAAK,yBAAyB,GAAG,cAAc,CAAC;AAMhD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAKlF;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAKpF;AAED,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,GACb,gBAAgB,CAAC,yBAAyB,CAAC,CAE7C;AAED,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,GACb,gBAAgB,CAAC,yBAAyB,CAAC,CAE7C;AAED,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,OAAO,GACb,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,CAKnC"}
1
+ {"version":3,"file":"form-errors.d.ts","sourceRoot":"","sources":["../../src/pages/form-errors.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,CAAC,MAAM,SAAS,MAAM,IAC9C;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAC/C;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAC,CAAC;AAEzD,KAAK,UAAU,GAAG,OAAO,GAAG,UAAU,CAAC;AACvC,KAAK,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AACjD,KAAK,yBAAyB,GAAG,OAAO,CAAC;AACzC,KAAK,yBAAyB,GAAG,cAAc,CAAC;AAMhD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAKlF;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAapF;AAED,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,GACb,gBAAgB,CAAC,yBAAyB,CAAC,CAE7C;AAED,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,GACb,gBAAgB,CAAC,yBAAyB,CAAC,CAE7C;AAED,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,OAAO,GACb,gBAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,CAKnC"}
@@ -1,5 +1,5 @@
1
1
  import { ApiError } from '@shipfox/client-api';
2
- import { authErrorMessage } from './form-utils.js';
2
+ import { authErrorMessage, authErrorMessageFormat } from './form-utils.js';
3
3
  function apiCode(error) {
4
4
  return error instanceof ApiError ? error.code : undefined;
5
5
  }
@@ -24,6 +24,16 @@ export function signupErrorToFormError(error) {
24
24
  message: authErrorMessage(error)
25
25
  };
26
26
  }
27
+ if (apiCode(error) === 'signup-not-allowed') {
28
+ const format = authErrorMessageFormat(error);
29
+ return {
30
+ kind: 'form',
31
+ message: authErrorMessage(error),
32
+ ...format ? {
33
+ format
34
+ } : {}
35
+ };
36
+ }
27
37
  return {
28
38
  kind: 'form',
29
39
  message: authErrorMessage(error)
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/form-errors.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\nimport {authErrorMessage} from './form-utils.js';\n\nexport type FormErrorMapping<TField extends string> =\n | {kind: 'field'; field: TField; message: string}\n | {kind: 'form'; message: string};\n\ntype LoginField = 'email' | 'password';\ntype SignupField = 'email' | 'password' | 'name';\ntype PasswordResetRequestField = 'email';\ntype PasswordResetConfirmField = 'new_password';\n\nfunction apiCode(error: unknown): string | undefined {\n return error instanceof ApiError ? error.code : undefined;\n}\n\nexport function loginErrorToFormError(error: unknown): FormErrorMapping<LoginField> {\n if (apiCode(error) === 'email-not-verified') {\n return {kind: 'field', field: 'email', message: authErrorMessage(error)};\n }\n return {kind: 'form', message: authErrorMessage(error)};\n}\n\nexport function signupErrorToFormError(error: unknown): FormErrorMapping<SignupField> {\n if (apiCode(error) === 'email-taken') {\n return {kind: 'field', field: 'email', message: authErrorMessage(error)};\n }\n return {kind: 'form', message: authErrorMessage(error)};\n}\n\nexport function passwordResetRequestErrorToFormError(\n error: unknown,\n): FormErrorMapping<PasswordResetRequestField> {\n return {kind: 'form', message: authErrorMessage(error)};\n}\n\nexport function passwordResetConfirmErrorToFormError(\n error: unknown,\n): FormErrorMapping<PasswordResetConfirmField> {\n return {kind: 'form', message: authErrorMessage(error)};\n}\n\nexport function workspaceOnboardingErrorToFormError(\n error: unknown,\n): FormErrorMapping<'name' | 'slug'> {\n if (apiCode(error) === 'slug-conflict') {\n return {kind: 'field', field: 'slug', message: 'That workspace slug is already taken.'};\n }\n return {kind: 'form', message: authErrorMessage(error)};\n}\n"],"names":["ApiError","authErrorMessage","apiCode","error","code","undefined","loginErrorToFormError","kind","field","message","signupErrorToFormError","passwordResetRequestErrorToFormError","passwordResetConfirmErrorToFormError","workspaceOnboardingErrorToFormError"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAC7C,SAAQC,gBAAgB,QAAO,kBAAkB;AAWjD,SAASC,QAAQC,KAAc;IAC7B,OAAOA,iBAAiBH,WAAWG,MAAMC,IAAI,GAAGC;AAClD;AAEA,OAAO,SAASC,sBAAsBH,KAAc;IAClD,IAAID,QAAQC,WAAW,sBAAsB;QAC3C,OAAO;YAACI,MAAM;YAASC,OAAO;YAASC,SAASR,iBAAiBE;QAAM;IACzE;IACA,OAAO;QAACI,MAAM;QAAQE,SAASR,iBAAiBE;IAAM;AACxD;AAEA,OAAO,SAASO,uBAAuBP,KAAc;IACnD,IAAID,QAAQC,WAAW,eAAe;QACpC,OAAO;YAACI,MAAM;YAASC,OAAO;YAASC,SAASR,iBAAiBE;QAAM;IACzE;IACA,OAAO;QAACI,MAAM;QAAQE,SAASR,iBAAiBE;IAAM;AACxD;AAEA,OAAO,SAASQ,qCACdR,KAAc;IAEd,OAAO;QAACI,MAAM;QAAQE,SAASR,iBAAiBE;IAAM;AACxD;AAEA,OAAO,SAASS,qCACdT,KAAc;IAEd,OAAO;QAACI,MAAM;QAAQE,SAASR,iBAAiBE;IAAM;AACxD;AAEA,OAAO,SAASU,oCACdV,KAAc;IAEd,IAAID,QAAQC,WAAW,iBAAiB;QACtC,OAAO;YAACI,MAAM;YAASC,OAAO;YAAQC,SAAS;QAAuC;IACxF;IACA,OAAO;QAACF,MAAM;QAAQE,SAASR,iBAAiBE;IAAM;AACxD"}
1
+ {"version":3,"sources":["../../src/pages/form-errors.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\nimport {authErrorMessage, authErrorMessageFormat} from './form-utils.js';\n\nexport type FormErrorMapping<TField extends string> =\n | {kind: 'field'; field: TField; message: string}\n | {kind: 'form'; message: string; format?: 'markdown'};\n\ntype LoginField = 'email' | 'password';\ntype SignupField = 'email' | 'password' | 'name';\ntype PasswordResetRequestField = 'email';\ntype PasswordResetConfirmField = 'new_password';\n\nfunction apiCode(error: unknown): string | undefined {\n return error instanceof ApiError ? error.code : undefined;\n}\n\nexport function loginErrorToFormError(error: unknown): FormErrorMapping<LoginField> {\n if (apiCode(error) === 'email-not-verified') {\n return {kind: 'field', field: 'email', message: authErrorMessage(error)};\n }\n return {kind: 'form', message: authErrorMessage(error)};\n}\n\nexport function signupErrorToFormError(error: unknown): FormErrorMapping<SignupField> {\n if (apiCode(error) === 'email-taken') {\n return {kind: 'field', field: 'email', message: authErrorMessage(error)};\n }\n if (apiCode(error) === 'signup-not-allowed') {\n const format = authErrorMessageFormat(error);\n return {\n kind: 'form',\n message: authErrorMessage(error),\n ...(format ? {format} : {}),\n };\n }\n return {kind: 'form', message: authErrorMessage(error)};\n}\n\nexport function passwordResetRequestErrorToFormError(\n error: unknown,\n): FormErrorMapping<PasswordResetRequestField> {\n return {kind: 'form', message: authErrorMessage(error)};\n}\n\nexport function passwordResetConfirmErrorToFormError(\n error: unknown,\n): FormErrorMapping<PasswordResetConfirmField> {\n return {kind: 'form', message: authErrorMessage(error)};\n}\n\nexport function workspaceOnboardingErrorToFormError(\n error: unknown,\n): FormErrorMapping<'name' | 'slug'> {\n if (apiCode(error) === 'slug-conflict') {\n return {kind: 'field', field: 'slug', message: 'That workspace slug is already taken.'};\n }\n return {kind: 'form', message: authErrorMessage(error)};\n}\n"],"names":["ApiError","authErrorMessage","authErrorMessageFormat","apiCode","error","code","undefined","loginErrorToFormError","kind","field","message","signupErrorToFormError","format","passwordResetRequestErrorToFormError","passwordResetConfirmErrorToFormError","workspaceOnboardingErrorToFormError"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAC7C,SAAQC,gBAAgB,EAAEC,sBAAsB,QAAO,kBAAkB;AAWzE,SAASC,QAAQC,KAAc;IAC7B,OAAOA,iBAAiBJ,WAAWI,MAAMC,IAAI,GAAGC;AAClD;AAEA,OAAO,SAASC,sBAAsBH,KAAc;IAClD,IAAID,QAAQC,WAAW,sBAAsB;QAC3C,OAAO;YAACI,MAAM;YAASC,OAAO;YAASC,SAAST,iBAAiBG;QAAM;IACzE;IACA,OAAO;QAACI,MAAM;QAAQE,SAAST,iBAAiBG;IAAM;AACxD;AAEA,OAAO,SAASO,uBAAuBP,KAAc;IACnD,IAAID,QAAQC,WAAW,eAAe;QACpC,OAAO;YAACI,MAAM;YAASC,OAAO;YAASC,SAAST,iBAAiBG;QAAM;IACzE;IACA,IAAID,QAAQC,WAAW,sBAAsB;QAC3C,MAAMQ,SAASV,uBAAuBE;QACtC,OAAO;YACLI,MAAM;YACNE,SAAST,iBAAiBG;YAC1B,GAAIQ,SAAS;gBAACA;YAAM,IAAI,CAAC,CAAC;QAC5B;IACF;IACA,OAAO;QAACJ,MAAM;QAAQE,SAAST,iBAAiBG;IAAM;AACxD;AAEA,OAAO,SAASS,qCACdT,KAAc;IAEd,OAAO;QAACI,MAAM;QAAQE,SAAST,iBAAiBG;IAAM;AACxD;AAEA,OAAO,SAASU,qCACdV,KAAc;IAEd,OAAO;QAACI,MAAM;QAAQE,SAAST,iBAAiBG;IAAM;AACxD;AAEA,OAAO,SAASW,oCACdX,KAAc;IAEd,IAAID,QAAQC,WAAW,iBAAiB;QACtC,OAAO;YAACI,MAAM;YAASC,OAAO;YAAQC,SAAS;QAAuC;IACxF;IACA,OAAO;QAACF,MAAM;QAAQE,SAAST,iBAAiBG;IAAM;AACxD"}
@@ -1,2 +1,3 @@
1
+ export declare function authErrorMessageFormat(error: unknown): 'markdown' | undefined;
1
2
  export declare function authErrorMessage(error: unknown): string;
2
3
  //# sourceMappingURL=form-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-utils.d.ts","sourceRoot":"","sources":["../../src/pages/form-utils.ts"],"names":[],"mappings":"AAcA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA4BvD"}
1
+ {"version":3,"file":"form-utils.d.ts","sourceRoot":"","sources":["../../src/pages/form-utils.ts"],"names":[],"mappings":"AAkBA,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAG7E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA4BvD"}
@@ -2,12 +2,19 @@ import { ApiError } from '@shipfox/client-api';
2
2
  function isRecord(value) {
3
3
  return typeof value === 'object' && value !== null;
4
4
  }
5
- function signupNotAllowedMessage(error) {
5
+ function signupNotAllowedDetails(error) {
6
6
  if (error.code !== 'signup-not-allowed') return undefined;
7
7
  if (!isRecord(error.details)) return undefined;
8
8
  const details = error.details.details;
9
- if (!isRecord(details)) return undefined;
10
- return typeof details.message === 'string' ? details.message : undefined;
9
+ return isRecord(details) ? details : undefined;
10
+ }
11
+ function signupNotAllowedMessage(error) {
12
+ const details = signupNotAllowedDetails(error);
13
+ return typeof details?.message === 'string' ? details.message : undefined;
14
+ }
15
+ export function authErrorMessageFormat(error) {
16
+ if (!(error instanceof ApiError)) return undefined;
17
+ return signupNotAllowedDetails(error)?.format === 'markdown' ? 'markdown' : undefined;
11
18
  }
12
19
  export function authErrorMessage(error) {
13
20
  if (!(error instanceof ApiError)) return 'Something went wrong. Try again.';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/form-utils.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction signupNotAllowedMessage(error: ApiError): string | undefined {\n if (error.code !== 'signup-not-allowed') return undefined;\n if (!isRecord(error.details)) return undefined;\n const details = error.details.details;\n if (!isRecord(details)) return undefined;\n return typeof details.message === 'string' ? details.message : undefined;\n}\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 const signupMessage = signupNotAllowedMessage(error);\n if (signupMessage) return signupMessage;\n return error.message || 'Something went wrong. Try again.';\n}\n"],"names":["ApiError","isRecord","value","signupNotAllowedMessage","error","code","undefined","details","message","authErrorMessage","signupMessage"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAE7C,SAASC,SAASC,KAAc;IAC9B,OAAO,OAAOA,UAAU,YAAYA,UAAU;AAChD;AAEA,SAASC,wBAAwBC,KAAe;IAC9C,IAAIA,MAAMC,IAAI,KAAK,sBAAsB,OAAOC;IAChD,IAAI,CAACL,SAASG,MAAMG,OAAO,GAAG,OAAOD;IACrC,MAAMC,UAAUH,MAAMG,OAAO,CAACA,OAAO;IACrC,IAAI,CAACN,SAASM,UAAU,OAAOD;IAC/B,OAAO,OAAOC,QAAQC,OAAO,KAAK,WAAWD,QAAQC,OAAO,GAAGF;AACjE;AAEA,OAAO,SAASG,iBAAiBL,KAAc;IAC7C,IAAI,CAAEA,CAAAA,iBAAiBJ,QAAO,GAAI,OAAO;IAEzC,IAAII,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,MAAMK,gBAAgBP,wBAAwBC;IAC9C,IAAIM,eAAe,OAAOA;IAC1B,OAAON,MAAMI,OAAO,IAAI;AAC1B"}
1
+ {"version":3,"sources":["../../src/pages/form-utils.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction signupNotAllowedDetails(error: ApiError): Record<string, unknown> | undefined {\n if (error.code !== 'signup-not-allowed') return undefined;\n if (!isRecord(error.details)) return undefined;\n const details = error.details.details;\n return isRecord(details) ? details : undefined;\n}\n\nfunction signupNotAllowedMessage(error: ApiError): string | undefined {\n const details = signupNotAllowedDetails(error);\n return typeof details?.message === 'string' ? details.message : undefined;\n}\n\nexport function authErrorMessageFormat(error: unknown): 'markdown' | undefined {\n if (!(error instanceof ApiError)) return undefined;\n return signupNotAllowedDetails(error)?.format === 'markdown' ? 'markdown' : undefined;\n}\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 const signupMessage = signupNotAllowedMessage(error);\n if (signupMessage) return signupMessage;\n return error.message || 'Something went wrong. Try again.';\n}\n"],"names":["ApiError","isRecord","value","signupNotAllowedDetails","error","code","undefined","details","signupNotAllowedMessage","message","authErrorMessageFormat","format","authErrorMessage","signupMessage"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAsB;AAE7C,SAASC,SAASC,KAAc;IAC9B,OAAO,OAAOA,UAAU,YAAYA,UAAU;AAChD;AAEA,SAASC,wBAAwBC,KAAe;IAC9C,IAAIA,MAAMC,IAAI,KAAK,sBAAsB,OAAOC;IAChD,IAAI,CAACL,SAASG,MAAMG,OAAO,GAAG,OAAOD;IACrC,MAAMC,UAAUH,MAAMG,OAAO,CAACA,OAAO;IACrC,OAAON,SAASM,WAAWA,UAAUD;AACvC;AAEA,SAASE,wBAAwBJ,KAAe;IAC9C,MAAMG,UAAUJ,wBAAwBC;IACxC,OAAO,OAAOG,SAASE,YAAY,WAAWF,QAAQE,OAAO,GAAGH;AAClE;AAEA,OAAO,SAASI,uBAAuBN,KAAc;IACnD,IAAI,CAAEA,CAAAA,iBAAiBJ,QAAO,GAAI,OAAOM;IACzC,OAAOH,wBAAwBC,QAAQO,WAAW,aAAa,aAAaL;AAC9E;AAEA,OAAO,SAASM,iBAAiBR,KAAc;IAC7C,IAAI,CAAEA,CAAAA,iBAAiBJ,QAAO,GAAI,OAAO;IAEzC,IAAII,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,MAAMQ,gBAAgBL,wBAAwBJ;IAC9C,IAAIS,eAAe,OAAOA;IAC1B,OAAOT,MAAMK,OAAO,IAAI;AAC1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"signup-page.d.ts","sourceRoot":"","sources":["../../src/pages/signup-page.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,UAAU,gCAyWzB"}
1
+ {"version":3,"file":"signup-page.d.ts","sourceRoot":"","sources":["../../src/pages/signup-page.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,UAAU,gCAiXzB"}
@@ -3,9 +3,10 @@ import { signupBodySchema } from '@shipfox/api-auth-dto';
3
3
  import { AuthShell, rememberLastWorkspaceId, useRouteSearch, userWorkspacesQueryKey } from '@shipfox/client-shell/runtime';
4
4
  import { displayNameFieldError } from '@shipfox/client-ui';
5
5
  import { Button, ButtonLink } from '@shipfox/react-ui/button';
6
- import { Callout } from '@shipfox/react-ui/callout';
6
+ import { Callout, CalloutContent } from '@shipfox/react-ui/callout';
7
7
  import { FormField, FormFieldInput, fieldError } from '@shipfox/react-ui/form-field';
8
8
  import { Icon } from '@shipfox/react-ui/icon';
9
+ import { Markdown } from '@shipfox/react-ui/markdown';
9
10
  import { toast } from '@shipfox/react-ui/toast';
10
11
  import { Text } from '@shipfox/react-ui/typography';
11
12
  import { useForm } from '@tanstack/react-form';
@@ -136,7 +137,7 @@ export function SignupPage() {
136
137
  }
137
138
  }));
138
139
  } else {
139
- setFormError(mapped.message);
140
+ setFormError(mapped);
140
141
  }
141
142
  }
142
143
  }
@@ -308,7 +309,13 @@ export function SignupPage() {
308
309
  formError ? /*#__PURE__*/ _jsx(Callout, {
309
310
  role: "alert",
310
311
  type: "error",
311
- children: formError
312
+ children: /*#__PURE__*/ _jsx(CalloutContent, {
313
+ children: formError.format === 'markdown' ? /*#__PURE__*/ _jsx(Markdown, {
314
+ className: "[&>*:last-child]:mb-0",
315
+ headingLevelOffset: 1,
316
+ children: formError.message
317
+ }) : formError.message
318
+ })
312
319
  }) : null,
313
320
  /*#__PURE__*/ _jsx(form.Field, {
314
321
  name: "name",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/signup-page.tsx"],"sourcesContent":["import {signupBodySchema} from '@shipfox/api-auth-dto';\nimport {\n AuthShell,\n rememberLastWorkspaceId,\n useRouteSearch,\n userWorkspacesQueryKey,\n} 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 {useQueryClient} from '@tanstack/react-query';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {EmailCodeVerification} from '#/components/email-code-verification.js';\nimport {useRefreshAuth} from '#hooks/api/refresh-auth.js';\nimport {useSignupAuth} from '#hooks/api/signup-auth.js';\nimport {useResendEmailVerificationAuth, useVerifyEmailAuth} from '#hooks/api/verify-email-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {validateRedirectSearch} from '../routes/inputs.js';\nimport {signupErrorToFormError} from './form-errors.js';\nimport {authErrorMessage} from './form-utils.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function SignupPage() {\n const signup = useSignupAuth();\n const verifyEmail = useVerifyEmailAuth();\n const resendEmailVerification = useResendEmailVerificationAuth();\n const refreshAuth = useRefreshAuth();\n const queryClient = useQueryClient();\n const navigate = useNavigate();\n const search = useRouteSearch(validateRedirectSearch);\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [emailChallenge, setEmailChallenge] = useState<{email: string; id: string} | undefined>();\n const [nextResendAvailableAt, setNextResendAvailableAt] = useState<string | undefined>();\n const [formError, setFormError] = useState<string | undefined>();\n const [resendError, setResendError] = useState<string | undefined>();\n const [invitationRefreshFailure, setInvitationRefreshFailure] = useState<{\n workspaceId: string;\n workspaceName: string;\n userId?: string | undefined;\n }>();\n const [isRetryingInvitationRefresh, setIsRetryingInvitationRefresh] = useState(false);\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 async function refreshInvitationWorkspace(workspaceId: string) {\n for (let attempt = 0; attempt < 2; attempt += 1) {\n try {\n await refreshAuth();\n const workspaces = queryClient.getQueryData<{\n memberships: Array<{id: string}>;\n }>(userWorkspacesQueryKey);\n const memberships = workspaces?.memberships ?? [];\n if (memberships.some(({id}) => id === workspaceId)) return true;\n } catch {\n // A mount-time refresh can race the post-signup refresh. Retry once so\n // an in-flight request that predates membership creation does not turn\n // a successful invitation into a manual recovery flow.\n }\n }\n return false;\n }\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password, name: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const parsed = signupBodySchema.parse({\n email: value.email,\n password: value.password,\n name: value.name,\n ...(invitationToken ? {invitation_token: invitationToken} : {}),\n });\n const result = await signup.mutateAsync({\n email: parsed.email,\n password: parsed.password,\n name: parsed.name,\n ...(parsed.invitation_token ? {invitationToken: parsed.invitation_token} : {}),\n });\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n\n if (invitationToken && result.membership && invitationPending) {\n const joinedWorkspace = await refreshInvitationWorkspace(result.membership.workspaceId);\n if (!joinedWorkspace) {\n setInvitationRefreshFailure({\n workspaceId: result.membership.workspaceId,\n workspaceName: invitationPending.workspaceName,\n userId: result.user?.id,\n });\n return;\n }\n toast.success(`You joined ${invitationPending.workspaceName}.`);\n if (result.user?.id) {\n rememberLastWorkspaceId(result.user.id, result.membership.workspaceId);\n }\n await navigate({to: '/'});\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 async function retryInvitationAuthRefresh() {\n if (!invitationRefreshFailure || isRetryingInvitationRefresh) return;\n setIsRetryingInvitationRefresh(true);\n const joinedWorkspace = await refreshInvitationWorkspace(invitationRefreshFailure.workspaceId);\n setIsRetryingInvitationRefresh(false);\n if (!joinedWorkspace) return;\n if (invitationRefreshFailure.userId) {\n rememberLastWorkspaceId(\n invitationRefreshFailure.userId,\n invitationRefreshFailure.workspaceId,\n );\n }\n toast.success(`You joined ${invitationRefreshFailure.workspaceName}.`);\n setInvitationRefreshFailure(undefined);\n await navigate({to: '/'});\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. The draft stores only\n // email and password; name is intentionally not persisted across navigation.\n // Skipped after a\n // successful signup because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n async function onResendVerificationEmail() {\n if (!emailChallenge || resendEmailVerification.isPending) return;\n\n setResendError(undefined);\n try {\n const result = await resendEmailVerification.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n });\n setNextResendAvailableAt(result.nextResendAvailableAt);\n toast.success('If another verification email can be sent, it will arrive shortly.');\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }\n\n if (invitationRefreshFailure) {\n return (\n <AuthShell\n title={`Join ${invitationRefreshFailure.workspaceName}`}\n description=\"Your account was created, but we could not finish signing you in.\"\n >\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex flex-col gap-inline\">\n <Text size=\"sm\">\n Try again to finish joining your workspace. You can safely retry this step.\n </Text>\n <Button\n type=\"button\"\n variant=\"secondary\"\n className=\"w-fit\"\n isLoading={isRetryingInvitationRefresh}\n onClick={() => {\n void retryInvitationAuthRefresh();\n }}\n >\n Retry sign-in\n </Button>\n </div>\n </Callout>\n </AuthShell>\n );\n }\n\n if (emailChallenge) {\n return (\n <EmailCodeVerification\n destination={emailChallenge.email}\n nextResendAvailableAt={nextResendAvailableAt}\n isResending={resendEmailVerification.isPending}\n isVerifying={verifyEmail.isPending}\n error={resendError}\n onVerify={async (code) => {\n setResendError(undefined);\n try {\n await verifyEmail.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n code,\n });\n await refreshAuth();\n toast.success('Your email is verified. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }}\n onResend={onResendVerificationEmail}\n onUseAnotherEmail={() => {\n setEmailChallenge(undefined);\n setResendError(undefined);\n }}\n >\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already verified?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </EmailCodeVerification>\n );\n }\n\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Create your Shipfox account';\n const headerDescription = invitationPending\n ? `Create an account to accept your invitation.`\n : 'Start with your email and a password.';\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-group\"\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","rememberLastWorkspaceId","useRouteSearch","userWorkspacesQueryKey","displayNameFieldError","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","useQueryClient","Link","useNavigate","useAtom","useEffect","useRef","useState","EmailCodeVerification","useRefreshAuth","useSignupAuth","useResendEmailVerificationAuth","useVerifyEmailAuth","authFormDraftAtom","initialAuthFormDraft","validateRedirectSearch","signupErrorToFormError","authErrorMessage","extractInvitationToken","pendingInvitation","useInvitationContext","SignupPage","signup","verifyEmail","resendEmailVerification","refreshAuth","queryClient","navigate","search","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","emailChallenge","setEmailChallenge","nextResendAvailableAt","setNextResendAvailableAt","formError","setFormError","resendError","setResendError","invitationRefreshFailure","setInvitationRefreshFailure","isRetryingInvitationRefresh","setIsRetryingInvitationRefresh","draftRef","current","skipDraftPersistRef","refreshInvitationWorkspace","workspaceId","attempt","workspaces","getQueryData","memberships","some","id","form","defaultValues","email","password","name","onSubmit","value","undefined","parsed","parse","invitation_token","result","mutateAsync","membership","joinedWorkspace","workspaceName","userId","user","success","to","acceptError","error","message","token","Error","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","retryInvitationAuthRefresh","state","values","setFieldValue","onResendVerificationEmail","isPending","challengeId","title","description","role","type","div","className","size","variant","isLoading","onClick","destination","isResending","isVerifying","onVerify","code","replace","onResend","onUseAnotherEmail","asChild","underline","headerTitle","headerDescription","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","persistDraft","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","shape","label","autoComplete","onChange","handleChange","target","handleBlur","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 {\n AuthShell,\n rememberLastWorkspaceId,\n useRouteSearch,\n userWorkspacesQueryKey,\n} from '@shipfox/client-shell/runtime';\nimport {displayNameFieldError} from '@shipfox/client-ui';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout, CalloutContent} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Markdown} from '@shipfox/react-ui/markdown';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {EmailCodeVerification} from '#/components/email-code-verification.js';\nimport {useRefreshAuth} from '#hooks/api/refresh-auth.js';\nimport {useSignupAuth} from '#hooks/api/signup-auth.js';\nimport {useResendEmailVerificationAuth, useVerifyEmailAuth} from '#hooks/api/verify-email-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {validateRedirectSearch} from '../routes/inputs.js';\nimport {signupErrorToFormError} from './form-errors.js';\nimport {authErrorMessage} from './form-utils.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function SignupPage() {\n const signup = useSignupAuth();\n const verifyEmail = useVerifyEmailAuth();\n const resendEmailVerification = useResendEmailVerificationAuth();\n const refreshAuth = useRefreshAuth();\n const queryClient = useQueryClient();\n const navigate = useNavigate();\n const search = useRouteSearch(validateRedirectSearch);\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [emailChallenge, setEmailChallenge] = useState<{email: string; id: string} | undefined>();\n const [nextResendAvailableAt, setNextResendAvailableAt] = useState<string | undefined>();\n const [formError, setFormError] = useState<{message: string; format?: 'markdown'} | undefined>();\n const [resendError, setResendError] = useState<string | undefined>();\n const [invitationRefreshFailure, setInvitationRefreshFailure] = useState<{\n workspaceId: string;\n workspaceName: string;\n userId?: string | undefined;\n }>();\n const [isRetryingInvitationRefresh, setIsRetryingInvitationRefresh] = useState(false);\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 async function refreshInvitationWorkspace(workspaceId: string) {\n for (let attempt = 0; attempt < 2; attempt += 1) {\n try {\n await refreshAuth();\n const workspaces = queryClient.getQueryData<{\n memberships: Array<{id: string}>;\n }>(userWorkspacesQueryKey);\n const memberships = workspaces?.memberships ?? [];\n if (memberships.some(({id}) => id === workspaceId)) return true;\n } catch {\n // A mount-time refresh can race the post-signup refresh. Retry once so\n // an in-flight request that predates membership creation does not turn\n // a successful invitation into a manual recovery flow.\n }\n }\n return false;\n }\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password, name: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const parsed = signupBodySchema.parse({\n email: value.email,\n password: value.password,\n name: value.name,\n ...(invitationToken ? {invitation_token: invitationToken} : {}),\n });\n const result = await signup.mutateAsync({\n email: parsed.email,\n password: parsed.password,\n name: parsed.name,\n ...(parsed.invitation_token ? {invitationToken: parsed.invitation_token} : {}),\n });\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n\n if (invitationToken && result.membership && invitationPending) {\n const joinedWorkspace = await refreshInvitationWorkspace(result.membership.workspaceId);\n if (!joinedWorkspace) {\n setInvitationRefreshFailure({\n workspaceId: result.membership.workspaceId,\n workspaceName: invitationPending.workspaceName,\n userId: result.user?.id,\n });\n return;\n }\n toast.success(`You joined ${invitationPending.workspaceName}.`);\n if (result.user?.id) {\n rememberLastWorkspaceId(result.user.id, result.membership.workspaceId);\n }\n await navigate({to: '/'});\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);\n }\n }\n },\n });\n\n async function retryInvitationAuthRefresh() {\n if (!invitationRefreshFailure || isRetryingInvitationRefresh) return;\n setIsRetryingInvitationRefresh(true);\n const joinedWorkspace = await refreshInvitationWorkspace(invitationRefreshFailure.workspaceId);\n setIsRetryingInvitationRefresh(false);\n if (!joinedWorkspace) return;\n if (invitationRefreshFailure.userId) {\n rememberLastWorkspaceId(\n invitationRefreshFailure.userId,\n invitationRefreshFailure.workspaceId,\n );\n }\n toast.success(`You joined ${invitationRefreshFailure.workspaceName}.`);\n setInvitationRefreshFailure(undefined);\n await navigate({to: '/'});\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. The draft stores only\n // email and password; name is intentionally not persisted across navigation.\n // Skipped after a\n // successful signup because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n async function onResendVerificationEmail() {\n if (!emailChallenge || resendEmailVerification.isPending) return;\n\n setResendError(undefined);\n try {\n const result = await resendEmailVerification.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n });\n setNextResendAvailableAt(result.nextResendAvailableAt);\n toast.success('If another verification email can be sent, it will arrive shortly.');\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }\n\n if (invitationRefreshFailure) {\n return (\n <AuthShell\n title={`Join ${invitationRefreshFailure.workspaceName}`}\n description=\"Your account was created, but we could not finish signing you in.\"\n >\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex flex-col gap-inline\">\n <Text size=\"sm\">\n Try again to finish joining your workspace. You can safely retry this step.\n </Text>\n <Button\n type=\"button\"\n variant=\"secondary\"\n className=\"w-fit\"\n isLoading={isRetryingInvitationRefresh}\n onClick={() => {\n void retryInvitationAuthRefresh();\n }}\n >\n Retry sign-in\n </Button>\n </div>\n </Callout>\n </AuthShell>\n );\n }\n\n if (emailChallenge) {\n return (\n <EmailCodeVerification\n destination={emailChallenge.email}\n nextResendAvailableAt={nextResendAvailableAt}\n isResending={resendEmailVerification.isPending}\n isVerifying={verifyEmail.isPending}\n error={resendError}\n onVerify={async (code) => {\n setResendError(undefined);\n try {\n await verifyEmail.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n code,\n });\n await refreshAuth();\n toast.success('Your email is verified. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }}\n onResend={onResendVerificationEmail}\n onUseAnotherEmail={() => {\n setEmailChallenge(undefined);\n setResendError(undefined);\n }}\n >\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already verified?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </EmailCodeVerification>\n );\n }\n\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Create your Shipfox account';\n const headerDescription = invitationPending\n ? `Create an account to accept your invitation.`\n : 'Start with your email and a password.';\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-group\"\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 <CalloutContent>\n {formError.format === 'markdown' ? (\n <Markdown className=\"[&>*:last-child]:mb-0\" headingLevelOffset={1}>\n {formError.message}\n </Markdown>\n ) : (\n formError.message\n )}\n </CalloutContent>\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","rememberLastWorkspaceId","useRouteSearch","userWorkspacesQueryKey","displayNameFieldError","Button","ButtonLink","Callout","CalloutContent","FormField","FormFieldInput","fieldError","Icon","Markdown","toast","Text","useForm","useQueryClient","Link","useNavigate","useAtom","useEffect","useRef","useState","EmailCodeVerification","useRefreshAuth","useSignupAuth","useResendEmailVerificationAuth","useVerifyEmailAuth","authFormDraftAtom","initialAuthFormDraft","validateRedirectSearch","signupErrorToFormError","authErrorMessage","extractInvitationToken","pendingInvitation","useInvitationContext","SignupPage","signup","verifyEmail","resendEmailVerification","refreshAuth","queryClient","navigate","search","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","emailChallenge","setEmailChallenge","nextResendAvailableAt","setNextResendAvailableAt","formError","setFormError","resendError","setResendError","invitationRefreshFailure","setInvitationRefreshFailure","isRetryingInvitationRefresh","setIsRetryingInvitationRefresh","draftRef","current","skipDraftPersistRef","refreshInvitationWorkspace","workspaceId","attempt","workspaces","getQueryData","memberships","some","id","form","defaultValues","email","password","name","onSubmit","value","undefined","parsed","parse","invitation_token","result","mutateAsync","membership","joinedWorkspace","workspaceName","userId","user","success","to","acceptError","error","message","token","Error","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","retryInvitationAuthRefresh","state","values","setFieldValue","onResendVerificationEmail","isPending","challengeId","title","description","role","type","div","className","size","variant","isLoading","onClick","destination","isResending","isVerifying","onVerify","code","replace","onResend","onUseAnotherEmail","asChild","underline","headerTitle","headerDescription","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","persistDraft","noValidate","event","preventDefault","stopPropagation","handleSubmit","format","headingLevelOffset","Field","validators","onBlur","shape","label","autoComplete","onChange","handleChange","target","handleBlur","readOnly","iconRight","aria-hidden"],"mappings":"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"}