@shipfox/client-auth 3.0.1 → 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 (116) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +32 -0
  3. package/dist/components/email-code-verification.d.ts +20 -0
  4. package/dist/components/email-code-verification.d.ts.map +1 -0
  5. package/dist/components/email-code-verification.js +136 -0
  6. package/dist/components/email-code-verification.js.map +1 -0
  7. package/dist/components/redirect-context.d.ts +11 -0
  8. package/dist/components/redirect-context.d.ts.map +1 -0
  9. package/dist/components/redirect-context.js +22 -0
  10. package/dist/components/redirect-context.js.map +1 -0
  11. package/dist/components/redirect-target.d.ts.map +1 -1
  12. package/dist/components/redirect-target.js +13 -12
  13. package/dist/components/redirect-target.js.map +1 -1
  14. package/dist/components/workspace-switcher.stories.d.ts +1 -1
  15. package/dist/continuation.d.ts +4 -0
  16. package/dist/continuation.d.ts.map +1 -0
  17. package/dist/continuation.js +5 -0
  18. package/dist/continuation.js.map +1 -0
  19. package/dist/hooks/api/auth-mapper.d.ts +21 -0
  20. package/dist/hooks/api/auth-mapper.d.ts.map +1 -0
  21. package/dist/hooks/api/auth-mapper.js +25 -0
  22. package/dist/hooks/api/auth-mapper.js.map +1 -0
  23. package/dist/hooks/api/login-auth.d.ts +3 -25
  24. package/dist/hooks/api/login-auth.d.ts.map +1 -1
  25. package/dist/hooks/api/login-auth.js +9 -30
  26. package/dist/hooks/api/login-auth.js.map +1 -1
  27. package/dist/hooks/api/logout-auth.d.ts.map +1 -1
  28. package/dist/hooks/api/logout-auth.js +4 -14
  29. package/dist/hooks/api/logout-auth.js.map +1 -1
  30. package/dist/hooks/api/password-reset-auth.d.ts +3 -25
  31. package/dist/hooks/api/password-reset-auth.d.ts.map +1 -1
  32. package/dist/hooks/api/password-reset-auth.js +9 -25
  33. package/dist/hooks/api/password-reset-auth.js.map +1 -1
  34. package/dist/hooks/api/signup-auth.d.ts +1 -25
  35. package/dist/hooks/api/signup-auth.d.ts.map +1 -1
  36. package/dist/hooks/api/signup-auth.js +5 -2
  37. package/dist/hooks/api/signup-auth.js.map +1 -1
  38. package/dist/hooks/api/verify-email-auth.d.ts +1 -12
  39. package/dist/hooks/api/verify-email-auth.d.ts.map +1 -1
  40. package/dist/hooks/api/verify-email-auth.js +10 -26
  41. package/dist/hooks/api/verify-email-auth.js.map +1 -1
  42. package/dist/hooks/api/workspace-auth.d.ts +3 -17
  43. package/dist/hooks/api/workspace-auth.d.ts.map +1 -1
  44. package/dist/hooks/api/workspace-auth.js +5 -2
  45. package/dist/hooks/api/workspace-auth.js.map +1 -1
  46. package/dist/hooks/api/workspace-mapper.d.ts +8 -0
  47. package/dist/hooks/api/workspace-mapper.d.ts.map +1 -0
  48. package/dist/hooks/api/workspace-mapper.js +9 -0
  49. package/dist/hooks/api/workspace-mapper.js.map +1 -0
  50. package/dist/index.d.ts +0 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +0 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/pages/invitation-context.d.ts +9 -19
  55. package/dist/pages/invitation-context.d.ts.map +1 -1
  56. package/dist/pages/invitation-context.js +7 -46
  57. package/dist/pages/invitation-context.js.map +1 -1
  58. package/dist/pages/login-page.js +2 -2
  59. package/dist/pages/login-page.js.map +1 -1
  60. package/dist/pages/logout-page.js +1 -1
  61. package/dist/pages/logout-page.js.map +1 -1
  62. package/dist/pages/password-reset-page.d.ts.map +1 -1
  63. package/dist/pages/password-reset-page.js +1 -4
  64. package/dist/pages/password-reset-page.js.map +1 -1
  65. package/dist/pages/signup-page.d.ts.map +1 -1
  66. package/dist/pages/signup-page.js +58 -152
  67. package/dist/pages/signup-page.js.map +1 -1
  68. package/dist/state/auth.d.ts +1 -1
  69. package/dist/state/auth.d.ts.map +1 -1
  70. package/dist/state/auth.js +1 -1
  71. package/dist/state/auth.js.map +1 -1
  72. package/dist/tsconfig.test.tsbuildinfo +1 -1
  73. package/package.json +10 -39
  74. package/src/components/auth-provider.test.tsx +173 -3
  75. package/src/components/email-code-verification.test.tsx +159 -0
  76. package/src/components/email-code-verification.tsx +177 -0
  77. package/src/components/redirect-context.test.ts +51 -0
  78. package/src/components/redirect-context.ts +26 -0
  79. package/src/components/redirect-target.test.ts +8 -0
  80. package/src/components/redirect-target.ts +13 -12
  81. package/src/continuation.test.ts +15 -0
  82. package/src/continuation.ts +6 -0
  83. package/src/hooks/api/auth-mapper.test.ts +77 -0
  84. package/src/hooks/api/auth-mapper.ts +39 -0
  85. package/src/hooks/api/login-auth.test.ts +1 -1
  86. package/src/hooks/api/login-auth.ts +12 -31
  87. package/src/hooks/api/logout-auth.ts +4 -10
  88. package/src/hooks/api/password-reset-auth.test.ts +1 -1
  89. package/src/hooks/api/password-reset-auth.ts +19 -32
  90. package/src/hooks/api/signup-auth.ts +8 -3
  91. package/src/hooks/api/verify-email-auth.ts +21 -34
  92. package/src/hooks/api/workspace-auth.ts +8 -3
  93. package/src/hooks/api/workspace-mapper.test.ts +21 -0
  94. package/src/hooks/api/workspace-mapper.ts +11 -0
  95. package/src/index.ts +0 -1
  96. package/src/pages/invitation-context.ts +10 -51
  97. package/src/pages/login-page.test.tsx +1 -1
  98. package/src/pages/login-page.tsx +2 -2
  99. package/src/pages/logout-page.tsx +1 -1
  100. package/src/pages/password-reset-page.tsx +1 -4
  101. package/src/pages/signup-page.test.tsx +1 -1
  102. package/src/pages/signup-page.tsx +41 -137
  103. package/src/state/auth.ts +1 -0
  104. package/src/state/last-workspace.test.ts +16 -0
  105. package/tsconfig.build.tsbuildinfo +1 -1
  106. package/dist/components/auth-shell.d.ts +0 -11
  107. package/dist/components/auth-shell.d.ts.map +0 -1
  108. package/dist/components/auth-shell.js +0 -63
  109. package/dist/components/auth-shell.js.map +0 -1
  110. package/dist/pages/email-verification-resend-model.d.ts +0 -6
  111. package/dist/pages/email-verification-resend-model.d.ts.map +0 -1
  112. package/dist/pages/email-verification-resend-model.js +0 -11
  113. package/dist/pages/email-verification-resend-model.js.map +0 -1
  114. package/src/components/auth-shell.tsx +0 -53
  115. package/src/pages/email-verification-resend-model.test.ts +0 -42
  116. package/src/pages/email-verification-resend-model.ts +0 -15
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 36 files with swc (473.63ms)
2
+ Successfully compiled: 39 files with swc (471.46ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @shipfox/client-auth
2
2
 
3
+ ## 5.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - ffd727b: Converges auth session and invitation state onto shared camelCase domain types validated at the API boundary, replacing the raw snake_case DTOs previously returned by login, signup, password reset, email verification, workspace creation, and invitation preview. `AuthState.user`, `useRefreshAuth()`, and `usePreviewInvitation()` now resolve to `UserIdentity`/`AuthenticatedSession`/`InvitationPreview` shapes (for example `accessToken` instead of `token`, `workspaceName` instead of `workspace_name`). Also moves the shared `AuthShell` component and session mapping helpers into `@shipfox/client-shell`, breaking the former `client-auth` ↔ `client-invitations` circular dependency.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [ffd727b]
12
+ - Updated dependencies [79df9d1]
13
+ - @shipfox/client-shell@5.0.0
14
+ - @shipfox/client-invitations@5.0.0
15
+
16
+ ## 4.0.0
17
+
18
+ ### Minor Changes
19
+
20
+ - 20e4feb: Adds provider-neutral authentication continuation primitives for reusable shells, email-code verification, and redirect context.
21
+
22
+ ### Patch Changes
23
+
24
+ - 11b10f7: Prevents private React Query data from persisting across logout and authenticated user changes.
25
+ - Updated dependencies [2e5b718]
26
+ - Updated dependencies [769d919]
27
+ - Updated dependencies [6b4a575]
28
+ - Updated dependencies [11b10f7]
29
+ - Updated dependencies [781a45b]
30
+ - @shipfox/client-ui@4.0.0
31
+ - @shipfox/client-shell@4.0.0
32
+ - @shipfox/api-auth-dto@7.1.0
33
+ - @shipfox/client-api@4.0.0
34
+
3
35
  ## 3.0.1
4
36
 
5
37
  ### Patch Changes
@@ -0,0 +1,20 @@
1
+ import type { HeaderProps } from '@shipfox/react-ui/typography';
2
+ import type { ReactNode, Ref } from 'react';
3
+ export interface EmailCodeVerificationProps {
4
+ destination: string;
5
+ expiresAt?: string | undefined;
6
+ nextResendAvailableAt?: string | undefined;
7
+ isResending?: boolean;
8
+ isVerifying?: boolean;
9
+ error?: string | undefined;
10
+ title?: string;
11
+ description?: string;
12
+ children?: ReactNode;
13
+ headingProps?: Omit<HeaderProps, 'children' | 'id'> | undefined;
14
+ headingRef?: Ref<HTMLHeadingElement> | undefined;
15
+ onResend: () => void | Promise<void>;
16
+ onUseAnotherEmail: () => void;
17
+ onVerify: (code: string) => void | Promise<void>;
18
+ }
19
+ export declare function EmailCodeVerification({ destination, expiresAt, nextResendAvailableAt, isResending, isVerifying, error, title, description, children, headingProps, headingRef, onResend, onUseAnotherEmail, onVerify, }: EmailCodeVerificationProps): import("react").JSX.Element;
20
+ //# sourceMappingURL=email-code-verification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-code-verification.d.ts","sourceRoot":"","sources":["../../src/components/email-code-verification.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAY,SAAS,EAAE,GAAG,EAAC,MAAM,OAAO,CAAC;AAKrD,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,YAAY,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;IAChE,UAAU,CAAC,EAAE,GAAG,CAAC,kBAAkB,CAAC,GAAG,SAAS,CAAC;IACjD,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAQD,wBAAgB,qBAAqB,CAAC,EACpC,WAAW,EACX,SAAS,EACT,qBAAqB,EACrB,WAAmB,EACnB,WAAmB,EACnB,KAAK,EACL,KAA0B,EAC1B,WAA2E,EAC3E,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,QAAQ,GACT,EAAE,0BAA0B,+BAgI5B"}
@@ -0,0 +1,136 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AuthShell } from '@shipfox/client-shell/runtime';
3
+ import { Button } from '@shipfox/react-ui/button';
4
+ import { Callout } from '@shipfox/react-ui/callout';
5
+ import { FormField, FormFieldInput } from '@shipfox/react-ui/form-field';
6
+ import { useCallback, useEffect, useRef, useState } from 'react';
7
+ const EIGHT_DIGIT_CODE_RE = /^\d{8}$/u;
8
+ function timestamp(value) {
9
+ if (!value) return undefined;
10
+ const parsed = Date.parse(value);
11
+ return Number.isNaN(parsed) ? undefined : parsed;
12
+ }
13
+ export function EmailCodeVerification({ destination, expiresAt, nextResendAvailableAt, isResending = false, isVerifying = false, error, title = 'Check your email', description = `We sent an eight-digit verification code to ${destination}.`, children, headingProps, headingRef, onResend, onUseAnotherEmail, onVerify }) {
14
+ const [code, setCode] = useState('');
15
+ const [now, setNow] = useState(()=>Date.now());
16
+ const [codeError, setCodeError] = useState();
17
+ const localHeadingRef = useRef(null);
18
+ const resendAvailableAt = timestamp(nextResendAvailableAt);
19
+ const expiration = timestamp(expiresAt);
20
+ const resendRemainingSeconds = resendAvailableAt ? Math.max(0, Math.ceil((resendAvailableAt - now) / 1_000)) : 0;
21
+ const isResendCoolingDown = resendRemainingSeconds > 0;
22
+ const isExpired = expiration !== undefined && expiration <= now;
23
+ const isPending = isResending || isVerifying;
24
+ useEffect(()=>{
25
+ const isPending = (tickNow)=>resendAvailableAt !== undefined && resendAvailableAt > tickNow || expiration !== undefined && expiration > tickNow;
26
+ const tickNow = Date.now();
27
+ setNow(tickNow);
28
+ if (!isPending(tickNow)) return;
29
+ const interval = window.setInterval(()=>{
30
+ const nextTick = Date.now();
31
+ setNow(nextTick);
32
+ if (!isPending(nextTick)) {
33
+ window.clearInterval(interval);
34
+ }
35
+ }, 1_000);
36
+ return ()=>window.clearInterval(interval);
37
+ }, [
38
+ expiration,
39
+ resendAvailableAt
40
+ ]);
41
+ useEffect(()=>{
42
+ localHeadingRef.current?.focus();
43
+ }, []);
44
+ const setHeadingRef = useCallback((heading)=>{
45
+ localHeadingRef.current = heading;
46
+ if (typeof headingRef === 'function') {
47
+ headingRef(heading);
48
+ } else if (headingRef) {
49
+ headingRef.current = heading;
50
+ }
51
+ }, [
52
+ headingRef
53
+ ]);
54
+ function submit(event) {
55
+ event.preventDefault();
56
+ if (isExpired || isVerifying) return;
57
+ if (!validateCode()) return;
58
+ void onVerify(code);
59
+ }
60
+ function validateCode() {
61
+ const error = EIGHT_DIGIT_CODE_RE.test(code) ? undefined : 'Enter the eight-digit verification code.';
62
+ setCodeError(error);
63
+ return error === undefined;
64
+ }
65
+ return /*#__PURE__*/ _jsxs(AuthShell, {
66
+ title: title,
67
+ description: description,
68
+ headingProps: headingProps,
69
+ headingRef: setHeadingRef,
70
+ children: [
71
+ error ? /*#__PURE__*/ _jsx(Callout, {
72
+ role: "alert",
73
+ type: "error",
74
+ children: error
75
+ }) : null,
76
+ isExpired ? /*#__PURE__*/ _jsx(Callout, {
77
+ role: "alert",
78
+ type: "error",
79
+ children: "This verification code has expired. Use another email to try again."
80
+ }) : null,
81
+ /*#__PURE__*/ _jsxs("form", {
82
+ className: "flex flex-col gap-8",
83
+ noValidate: true,
84
+ onSubmit: submit,
85
+ children: [
86
+ /*#__PURE__*/ _jsx(FormField, {
87
+ label: "Verification code",
88
+ id: "verification-code",
89
+ error: codeError,
90
+ children: /*#__PURE__*/ _jsx(FormFieldInput, {
91
+ autoComplete: "one-time-code",
92
+ inputMode: "numeric",
93
+ maxLength: 8,
94
+ pattern: "[0-9]{8}",
95
+ value: code,
96
+ onChange: (event)=>{
97
+ setCode(event.target.value.replace(/\D/gu, '').slice(0, 8));
98
+ setCodeError(undefined);
99
+ },
100
+ onBlur: validateCode
101
+ })
102
+ }),
103
+ /*#__PURE__*/ _jsx(Button, {
104
+ className: "w-full",
105
+ type: "submit",
106
+ disabled: isExpired,
107
+ isLoading: isVerifying,
108
+ children: "Verify email"
109
+ }),
110
+ /*#__PURE__*/ _jsx(Button, {
111
+ "aria-disabled": isResendCoolingDown ? true : undefined,
112
+ className: "w-full aria-disabled:cursor-not-allowed aria-disabled:opacity-70",
113
+ variant: "secondary",
114
+ type: "button",
115
+ isLoading: isResending,
116
+ onClick: ()=>{
117
+ if (!isResendCoolingDown && !isResending) void onResend();
118
+ },
119
+ children: isResending ? 'Sending email...' : isResendCoolingDown ? `Resend in ${resendRemainingSeconds}s` : 'Resend verification email'
120
+ })
121
+ ]
122
+ }),
123
+ /*#__PURE__*/ _jsx(Button, {
124
+ className: "w-full",
125
+ variant: "transparent",
126
+ type: "button",
127
+ disabled: isPending,
128
+ onClick: onUseAnotherEmail,
129
+ children: "Use another email"
130
+ }),
131
+ children
132
+ ]
133
+ });
134
+ }
135
+
136
+ //# sourceMappingURL=email-code-verification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/email-code-verification.tsx"],"sourcesContent":["import {AuthShell} from '@shipfox/client-shell/runtime';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput} from '@shipfox/react-ui/form-field';\nimport type {HeaderProps} from '@shipfox/react-ui/typography';\nimport type {FormEvent, ReactNode, Ref} from 'react';\nimport {useCallback, useEffect, useRef, useState} from 'react';\n\nconst EIGHT_DIGIT_CODE_RE = /^\\d{8}$/u;\n\nexport interface EmailCodeVerificationProps {\n destination: string;\n expiresAt?: string | undefined;\n nextResendAvailableAt?: string | undefined;\n isResending?: boolean;\n isVerifying?: boolean;\n error?: string | undefined;\n title?: string;\n description?: string;\n children?: ReactNode;\n headingProps?: Omit<HeaderProps, 'children' | 'id'> | undefined;\n headingRef?: Ref<HTMLHeadingElement> | undefined;\n onResend: () => void | Promise<void>;\n onUseAnotherEmail: () => void;\n onVerify: (code: string) => void | Promise<void>;\n}\n\nfunction timestamp(value: string | undefined): number | undefined {\n if (!value) return undefined;\n const parsed = Date.parse(value);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nexport function EmailCodeVerification({\n destination,\n expiresAt,\n nextResendAvailableAt,\n isResending = false,\n isVerifying = false,\n error,\n title = 'Check your email',\n description = `We sent an eight-digit verification code to ${destination}.`,\n children,\n headingProps,\n headingRef,\n onResend,\n onUseAnotherEmail,\n onVerify,\n}: EmailCodeVerificationProps) {\n const [code, setCode] = useState('');\n const [now, setNow] = useState(() => Date.now());\n const [codeError, setCodeError] = useState<string>();\n const localHeadingRef = useRef<HTMLHeadingElement>(null);\n const resendAvailableAt = timestamp(nextResendAvailableAt);\n const expiration = timestamp(expiresAt);\n const resendRemainingSeconds = resendAvailableAt\n ? Math.max(0, Math.ceil((resendAvailableAt - now) / 1_000))\n : 0;\n const isResendCoolingDown = resendRemainingSeconds > 0;\n const isExpired = expiration !== undefined && expiration <= now;\n const isPending = isResending || isVerifying;\n\n useEffect(() => {\n const isPending = (tickNow: number) =>\n (resendAvailableAt !== undefined && resendAvailableAt > tickNow) ||\n (expiration !== undefined && expiration > tickNow);\n\n const tickNow = Date.now();\n setNow(tickNow);\n if (!isPending(tickNow)) return;\n\n const interval = window.setInterval(() => {\n const nextTick = Date.now();\n setNow(nextTick);\n if (!isPending(nextTick)) {\n window.clearInterval(interval);\n }\n }, 1_000);\n return () => window.clearInterval(interval);\n }, [expiration, resendAvailableAt]);\n\n useEffect(() => {\n localHeadingRef.current?.focus();\n }, []);\n\n const setHeadingRef = useCallback(\n (heading: HTMLHeadingElement | null) => {\n localHeadingRef.current = heading;\n if (typeof headingRef === 'function') {\n headingRef(heading);\n } else if (headingRef) {\n headingRef.current = heading;\n }\n },\n [headingRef],\n );\n\n function submit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (isExpired || isVerifying) return;\n if (!validateCode()) return;\n void onVerify(code);\n }\n\n function validateCode() {\n const error = EIGHT_DIGIT_CODE_RE.test(code)\n ? undefined\n : 'Enter the eight-digit verification code.';\n setCodeError(error);\n return error === undefined;\n }\n\n return (\n <AuthShell\n title={title}\n description={description}\n headingProps={headingProps}\n headingRef={setHeadingRef}\n >\n {error ? (\n <Callout role=\"alert\" type=\"error\">\n {error}\n </Callout>\n ) : null}\n {isExpired ? (\n <Callout role=\"alert\" type=\"error\">\n This verification code has expired. Use another email to try again.\n </Callout>\n ) : null}\n <form className=\"flex flex-col gap-8\" noValidate onSubmit={submit}>\n <FormField label=\"Verification code\" id=\"verification-code\" error={codeError}>\n <FormFieldInput\n autoComplete=\"one-time-code\"\n inputMode=\"numeric\"\n maxLength={8}\n pattern=\"[0-9]{8}\"\n value={code}\n onChange={(event) => {\n setCode(event.target.value.replace(/\\D/gu, '').slice(0, 8));\n setCodeError(undefined);\n }}\n onBlur={validateCode}\n />\n </FormField>\n <Button className=\"w-full\" type=\"submit\" disabled={isExpired} isLoading={isVerifying}>\n Verify email\n </Button>\n <Button\n aria-disabled={isResendCoolingDown ? true : undefined}\n className=\"w-full aria-disabled:cursor-not-allowed aria-disabled:opacity-70\"\n variant=\"secondary\"\n type=\"button\"\n isLoading={isResending}\n onClick={() => {\n if (!isResendCoolingDown && !isResending) void onResend();\n }}\n >\n {isResending\n ? 'Sending email...'\n : isResendCoolingDown\n ? `Resend in ${resendRemainingSeconds}s`\n : 'Resend verification email'}\n </Button>\n </form>\n <Button\n className=\"w-full\"\n variant=\"transparent\"\n type=\"button\"\n disabled={isPending}\n onClick={onUseAnotherEmail}\n >\n Use another email\n </Button>\n {children}\n </AuthShell>\n );\n}\n"],"names":["AuthShell","Button","Callout","FormField","FormFieldInput","useCallback","useEffect","useRef","useState","EIGHT_DIGIT_CODE_RE","timestamp","value","undefined","parsed","Date","parse","Number","isNaN","EmailCodeVerification","destination","expiresAt","nextResendAvailableAt","isResending","isVerifying","error","title","description","children","headingProps","headingRef","onResend","onUseAnotherEmail","onVerify","code","setCode","now","setNow","codeError","setCodeError","localHeadingRef","resendAvailableAt","expiration","resendRemainingSeconds","Math","max","ceil","isResendCoolingDown","isExpired","isPending","tickNow","interval","window","setInterval","nextTick","clearInterval","current","focus","setHeadingRef","heading","submit","event","preventDefault","validateCode","test","role","type","form","className","noValidate","onSubmit","label","id","autoComplete","inputMode","maxLength","pattern","onChange","target","replace","slice","onBlur","disabled","isLoading","aria-disabled","variant","onClick"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface RedirectContext {
2
+ invitationToken?: string;
3
+ returnTo?: string;
4
+ }
5
+ /**
6
+ * Separates a safe post-authentication destination from an invitation token.
7
+ * The token never remains in `returnTo`, so callers can keep it in their
8
+ * short-lived invitation flow instead of forwarding it through generic redirects.
9
+ */
10
+ export declare function parseRedirectContext(value: unknown): RedirectContext;
11
+ //# sourceMappingURL=redirect-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-context.d.ts","sourceRoot":"","sources":["../../src/components/redirect-context.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAWpE"}
@@ -0,0 +1,22 @@
1
+ import { sanitizeRedirectPath } from './redirect-target.js';
2
+ const INVITATION_ACCEPT_PATH = '/invitations/accept';
3
+ /**
4
+ * Separates a safe post-authentication destination from an invitation token.
5
+ * The token never remains in `returnTo`, so callers can keep it in their
6
+ * short-lived invitation flow instead of forwarding it through generic redirects.
7
+ */ export function parseRedirectContext(value) {
8
+ const redirect = sanitizeRedirectPath(value);
9
+ if (!redirect) return {};
10
+ const decoded = decodeURIComponent(redirect);
11
+ const [path, queryString = ''] = decoded.split('?', 2);
12
+ if (path !== INVITATION_ACCEPT_PATH) return {
13
+ returnTo: redirect
14
+ };
15
+ const params = new URLSearchParams(queryString);
16
+ const invitationToken = params.get('token');
17
+ return invitationToken ? {
18
+ invitationToken
19
+ } : {};
20
+ }
21
+
22
+ //# sourceMappingURL=redirect-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/redirect-context.ts"],"sourcesContent":["import {sanitizeRedirectPath} from './redirect-target.js';\n\nconst INVITATION_ACCEPT_PATH = '/invitations/accept';\n\nexport interface RedirectContext {\n invitationToken?: string;\n returnTo?: string;\n}\n\n/**\n * Separates a safe post-authentication destination from an invitation token.\n * The token never remains in `returnTo`, so callers can keep it in their\n * short-lived invitation flow instead of forwarding it through generic redirects.\n */\nexport function parseRedirectContext(value: unknown): RedirectContext {\n const redirect = sanitizeRedirectPath(value);\n if (!redirect) return {};\n\n const decoded = decodeURIComponent(redirect);\n const [path, queryString = ''] = decoded.split('?', 2);\n if (path !== INVITATION_ACCEPT_PATH) return {returnTo: redirect};\n\n const params = new URLSearchParams(queryString);\n const invitationToken = params.get('token');\n return invitationToken ? {invitationToken} : {};\n}\n"],"names":["sanitizeRedirectPath","INVITATION_ACCEPT_PATH","parseRedirectContext","value","redirect","decoded","decodeURIComponent","path","queryString","split","returnTo","params","URLSearchParams","invitationToken","get"],"mappings":"AAAA,SAAQA,oBAAoB,QAAO,uBAAuB;AAE1D,MAAMC,yBAAyB;AAO/B;;;;CAIC,GACD,OAAO,SAASC,qBAAqBC,KAAc;IACjD,MAAMC,WAAWJ,qBAAqBG;IACtC,IAAI,CAACC,UAAU,OAAO,CAAC;IAEvB,MAAMC,UAAUC,mBAAmBF;IACnC,MAAM,CAACG,MAAMC,cAAc,EAAE,CAAC,GAAGH,QAAQI,KAAK,CAAC,KAAK;IACpD,IAAIF,SAASN,wBAAwB,OAAO;QAACS,UAAUN;IAAQ;IAE/D,MAAMO,SAAS,IAAIC,gBAAgBJ;IACnC,MAAMK,kBAAkBF,OAAOG,GAAG,CAAC;IACnC,OAAOD,kBAAkB;QAACA;IAAe,IAAI,CAAC;AAChD"}
@@ -1 +1 @@
1
- {"version":3,"file":"redirect-target.d.ts","sourceRoot":"","sources":["../../src/components/redirect-target.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAevE"}
1
+ {"version":3,"file":"redirect-target.d.ts","sourceRoot":"","sources":["../../src/components/redirect-target.ts"],"names":[],"mappings":"AAIA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAiBvE"}
@@ -1,22 +1,23 @@
1
- const SEARCH_OR_HASH_RE = /[?#]/u;
2
- // Returns the input string only if it is a same-origin internal path safe to
3
- // navigate to after authentication. We decode before checking so percent-encoded
4
- // variants like `/%61uth/login` cannot bypass the `/auth/*` rejection.
1
+ const REDIRECT_ORIGIN = 'https://shipfox-redirect.invalid';
2
+ // Resolves and canonicalizes an internal path before returning it, so browser URL
3
+ // parsing cannot turn a seemingly safe path into an external or auth route.
5
4
  export function sanitizeRedirectPath(value) {
6
- if (typeof value !== 'string') return undefined;
5
+ if (typeof value !== 'string' || !value.startsWith('/')) return undefined;
7
6
  let decoded;
8
7
  try {
9
8
  decoded = decodeURIComponent(value);
10
9
  } catch {
11
10
  return undefined;
12
11
  }
13
- if (!decoded.startsWith('/')) return undefined;
14
- if (decoded.startsWith('//')) return undefined;
15
- // Strip search/hash before the /auth check so /auth?token=x and /auth#x
16
- // cannot bypass the prefix match.
17
- const pathOnly = decoded.split(SEARCH_OR_HASH_RE, 1)[0] ?? decoded;
18
- if (pathOnly === '/auth' || pathOnly.startsWith('/auth/')) return undefined;
19
- return value;
12
+ let target;
13
+ try {
14
+ target = new URL(decoded, REDIRECT_ORIGIN);
15
+ } catch {
16
+ return undefined;
17
+ }
18
+ if (target.origin !== REDIRECT_ORIGIN) return undefined;
19
+ if (target.pathname === '/auth' || target.pathname.startsWith('/auth/')) return undefined;
20
+ return `${target.pathname}${target.search}${target.hash}`;
20
21
  }
21
22
 
22
23
  //# sourceMappingURL=redirect-target.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/redirect-target.ts"],"sourcesContent":["const SEARCH_OR_HASH_RE = /[?#]/u;\n\n// Returns the input string only if it is a same-origin internal path safe to\n// navigate to after authentication. We decode before checking so percent-encoded\n// variants like `/%61uth/login` cannot bypass the `/auth/*` rejection.\nexport function sanitizeRedirectPath(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n let decoded: string;\n try {\n decoded = decodeURIComponent(value);\n } catch {\n return undefined;\n }\n if (!decoded.startsWith('/')) return undefined;\n if (decoded.startsWith('//')) return undefined;\n // Strip search/hash before the /auth check so /auth?token=x and /auth#x\n // cannot bypass the prefix match.\n const pathOnly = decoded.split(SEARCH_OR_HASH_RE, 1)[0] ?? decoded;\n if (pathOnly === '/auth' || pathOnly.startsWith('/auth/')) return undefined;\n return value;\n}\n"],"names":["SEARCH_OR_HASH_RE","sanitizeRedirectPath","value","undefined","decoded","decodeURIComponent","startsWith","pathOnly","split"],"mappings":"AAAA,MAAMA,oBAAoB;AAE1B,6EAA6E;AAC7E,iFAAiF;AACjF,uEAAuE;AACvE,OAAO,SAASC,qBAAqBC,KAAc;IACjD,IAAI,OAAOA,UAAU,UAAU,OAAOC;IACtC,IAAIC;IACJ,IAAI;QACFA,UAAUC,mBAAmBH;IAC/B,EAAE,OAAM;QACN,OAAOC;IACT;IACA,IAAI,CAACC,QAAQE,UAAU,CAAC,MAAM,OAAOH;IACrC,IAAIC,QAAQE,UAAU,CAAC,OAAO,OAAOH;IACrC,wEAAwE;IACxE,kCAAkC;IAClC,MAAMI,WAAWH,QAAQI,KAAK,CAACR,mBAAmB,EAAE,CAAC,EAAE,IAAII;IAC3D,IAAIG,aAAa,WAAWA,SAASD,UAAU,CAAC,WAAW,OAAOH;IAClE,OAAOD;AACT"}
1
+ {"version":3,"sources":["../../src/components/redirect-target.ts"],"sourcesContent":["const REDIRECT_ORIGIN = 'https://shipfox-redirect.invalid';\n\n// Resolves and canonicalizes an internal path before returning it, so browser URL\n// parsing cannot turn a seemingly safe path into an external or auth route.\nexport function sanitizeRedirectPath(value: unknown): string | undefined {\n if (typeof value !== 'string' || !value.startsWith('/')) return undefined;\n let decoded: string;\n try {\n decoded = decodeURIComponent(value);\n } catch {\n return undefined;\n }\n let target: URL;\n try {\n target = new URL(decoded, REDIRECT_ORIGIN);\n } catch {\n return undefined;\n }\n if (target.origin !== REDIRECT_ORIGIN) return undefined;\n if (target.pathname === '/auth' || target.pathname.startsWith('/auth/')) return undefined;\n return `${target.pathname}${target.search}${target.hash}`;\n}\n"],"names":["REDIRECT_ORIGIN","sanitizeRedirectPath","value","startsWith","undefined","decoded","decodeURIComponent","target","URL","origin","pathname","search","hash"],"mappings":"AAAA,MAAMA,kBAAkB;AAExB,kFAAkF;AAClF,4EAA4E;AAC5E,OAAO,SAASC,qBAAqBC,KAAc;IACjD,IAAI,OAAOA,UAAU,YAAY,CAACA,MAAMC,UAAU,CAAC,MAAM,OAAOC;IAChE,IAAIC;IACJ,IAAI;QACFA,UAAUC,mBAAmBJ;IAC/B,EAAE,OAAM;QACN,OAAOE;IACT;IACA,IAAIG;IACJ,IAAI;QACFA,SAAS,IAAIC,IAAIH,SAASL;IAC5B,EAAE,OAAM;QACN,OAAOI;IACT;IACA,IAAIG,OAAOE,MAAM,KAAKT,iBAAiB,OAAOI;IAC9C,IAAIG,OAAOG,QAAQ,KAAK,WAAWH,OAAOG,QAAQ,CAACP,UAAU,CAAC,WAAW,OAAOC;IAChF,OAAO,GAAGG,OAAOG,QAAQ,GAAGH,OAAOI,MAAM,GAAGJ,OAAOK,IAAI,EAAE;AAC3D"}
@@ -13,7 +13,7 @@ declare const meta: {
13
13
  };
14
14
  args: {
15
15
  activeWorkspaceId: string;
16
- workspaces: Workspace[];
16
+ workspaces: import("@shipfox/client-shell/runtime").WorkspaceSummary[];
17
17
  };
18
18
  };
19
19
  export default meta;
@@ -0,0 +1,4 @@
1
+ export { AuthActions, AuthShell, type AuthShellProps } from '@shipfox/client-shell/runtime';
2
+ export { EmailCodeVerification, type EmailCodeVerificationProps, } from './components/email-code-verification.js';
3
+ export { parseRedirectContext, type RedirectContext } from './components/redirect-context.js';
4
+ //# sourceMappingURL=continuation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"continuation.d.ts","sourceRoot":"","sources":["../src/continuation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,+BAA+B,CAAC;AAC1F,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAC,oBAAoB,EAAE,KAAK,eAAe,EAAC,MAAM,kCAAkC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { AuthActions, AuthShell } from '@shipfox/client-shell/runtime';
2
+ export { EmailCodeVerification } from './components/email-code-verification.js';
3
+ export { parseRedirectContext } from './components/redirect-context.js';
4
+
5
+ //# sourceMappingURL=continuation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/continuation.ts"],"sourcesContent":["export {AuthActions, AuthShell, type AuthShellProps} from '@shipfox/client-shell/runtime';\nexport {\n EmailCodeVerification,\n type EmailCodeVerificationProps,\n} from './components/email-code-verification.js';\nexport {parseRedirectContext, type RedirectContext} from './components/redirect-context.js';\n"],"names":["AuthActions","AuthShell","EmailCodeVerification","parseRedirectContext"],"mappings":"AAAA,SAAQA,WAAW,EAAEC,SAAS,QAA4B,gCAAgC;AAC1F,SACEC,qBAAqB,QAEhB,0CAA0C;AACjD,SAAQC,oBAAoB,QAA6B,mCAAmC"}
@@ -0,0 +1,21 @@
1
+ import type { SignupResponseDto } from '@shipfox/api-auth-dto';
2
+ import { toAuthenticatedSession, type UserIdentity } from '@shipfox/client-shell/runtime';
3
+ export { toAuthenticatedSession };
4
+ export interface SignupResult {
5
+ user: UserIdentity;
6
+ emailChallenge?: {
7
+ id: string;
8
+ nextResendAvailableAt: string;
9
+ };
10
+ membership?: {
11
+ id: string;
12
+ userId: string;
13
+ workspaceId: string;
14
+ };
15
+ acceptError?: {
16
+ code: string;
17
+ message: string;
18
+ };
19
+ }
20
+ export declare function toSignupResult(dto: SignupResponseDto): SignupResult;
21
+ //# sourceMappingURL=auth-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-mapper.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/auth-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EACL,sBAAsB,EAEtB,KAAK,YAAY,EAClB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAC,sBAAsB,EAAC,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,cAAc,CAAC,EAAE;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,qBAAqB,EAAE,MAAM,CAAA;KAAC,CAAC;IAC7D,UAAU,CAAC,EAAE;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAC;IAC/D,WAAW,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC;CAC/C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,iBAAiB,GAAG,YAAY,CAsBnE"}
@@ -0,0 +1,25 @@
1
+ import { toAuthenticatedSession, toUserIdentity } from '@shipfox/client-shell/runtime';
2
+ export { toAuthenticatedSession };
3
+ export function toSignupResult(dto) {
4
+ return {
5
+ user: toUserIdentity(dto.user),
6
+ ...dto.email_challenge ? {
7
+ emailChallenge: {
8
+ id: dto.email_challenge.id,
9
+ nextResendAvailableAt: dto.email_challenge.next_resend_available_at
10
+ }
11
+ } : {},
12
+ ...dto.membership ? {
13
+ membership: {
14
+ id: dto.membership.id,
15
+ userId: dto.membership.user_id,
16
+ workspaceId: dto.membership.workspace_id
17
+ }
18
+ } : {},
19
+ ...dto.accept_error ? {
20
+ acceptError: dto.accept_error
21
+ } : {}
22
+ };
23
+ }
24
+
25
+ //# sourceMappingURL=auth-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/api/auth-mapper.ts"],"sourcesContent":["import type {SignupResponseDto} from '@shipfox/api-auth-dto';\nimport {\n toAuthenticatedSession,\n toUserIdentity,\n type UserIdentity,\n} from '@shipfox/client-shell/runtime';\n\nexport {toAuthenticatedSession};\n\nexport interface SignupResult {\n user: UserIdentity;\n emailChallenge?: {id: string; nextResendAvailableAt: string};\n membership?: {id: string; userId: string; workspaceId: string};\n acceptError?: {code: string; message: string};\n}\n\nexport function toSignupResult(dto: SignupResponseDto): SignupResult {\n return {\n user: toUserIdentity(dto.user),\n ...(dto.email_challenge\n ? {\n emailChallenge: {\n id: dto.email_challenge.id,\n nextResendAvailableAt: dto.email_challenge.next_resend_available_at,\n },\n }\n : {}),\n ...(dto.membership\n ? {\n membership: {\n id: dto.membership.id,\n userId: dto.membership.user_id,\n workspaceId: dto.membership.workspace_id,\n },\n }\n : {}),\n ...(dto.accept_error ? {acceptError: dto.accept_error} : {}),\n };\n}\n"],"names":["toAuthenticatedSession","toUserIdentity","toSignupResult","dto","user","email_challenge","emailChallenge","id","nextResendAvailableAt","next_resend_available_at","membership","userId","user_id","workspaceId","workspace_id","accept_error","acceptError"],"mappings":"AACA,SACEA,sBAAsB,EACtBC,cAAc,QAET,gCAAgC;AAEvC,SAAQD,sBAAsB,GAAE;AAShC,OAAO,SAASE,eAAeC,GAAsB;IACnD,OAAO;QACLC,MAAMH,eAAeE,IAAIC,IAAI;QAC7B,GAAID,IAAIE,eAAe,GACnB;YACEC,gBAAgB;gBACdC,IAAIJ,IAAIE,eAAe,CAACE,EAAE;gBAC1BC,uBAAuBL,IAAIE,eAAe,CAACI,wBAAwB;YACrE;QACF,IACA,CAAC,CAAC;QACN,GAAIN,IAAIO,UAAU,GACd;YACEA,YAAY;gBACVH,IAAIJ,IAAIO,UAAU,CAACH,EAAE;gBACrBI,QAAQR,IAAIO,UAAU,CAACE,OAAO;gBAC9BC,aAAaV,IAAIO,UAAU,CAACI,YAAY;YAC1C;QACF,IACA,CAAC,CAAC;QACN,GAAIX,IAAIY,YAAY,GAAG;YAACC,aAAab,IAAIY,YAAY;QAAA,IAAI,CAAC,CAAC;IAC7D;AACF"}
@@ -1,28 +1,6 @@
1
- import type { LoginBodyDto } from '@shipfox/api-auth-dto';
2
- export declare function loginAuth(body: LoginBodyDto): Promise<{
3
- token: string;
4
- user: {
5
- id: string;
6
- email: string;
7
- name: string | null;
8
- email_verified_at: string | null;
9
- status: "active" | "suspended" | "deleted";
10
- created_at: string;
11
- updated_at: string;
12
- };
13
- }>;
14
- export declare function useLoginAuth(): import("@tanstack/react-query").UseMutationResult<{
15
- token: string;
16
- user: {
17
- id: string;
18
- email: string;
19
- name: string | null;
20
- email_verified_at: string | null;
21
- status: "active" | "suspended" | "deleted";
22
- created_at: string;
23
- updated_at: string;
24
- };
25
- }, Error, {
1
+ import { type LoginBodyDto } from '@shipfox/api-auth-dto';
2
+ export declare function loginAuth(body: LoginBodyDto): Promise<import("@shipfox/client-shell/runtime").AuthenticatedSession>;
3
+ export declare function useLoginAuth(): import("@tanstack/react-query").UseMutationResult<import("@shipfox/client-shell/runtime").AuthenticatedSession, Error, {
26
4
  email: string;
27
5
  password: string;
28
6
  }, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"login-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/login-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAmB,MAAM,uBAAuB,CAAC;AAQ1E,wBAAsB,SAAS,CAAC,IAAI,EAAE,YAAY;;;;;;;;;;;GAEjD;AAED,wBAAgB,YAAY;;;;;;;;;;;;;;YA4B3B"}
1
+ {"version":3,"file":"login-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/login-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,YAAY,EAAsB,MAAM,uBAAuB,CAAC;AAM7E,wBAAsB,SAAS,CAAC,IAAI,EAAE,YAAY,yEAMjD;AAED,wBAAgB,YAAY;;;YAO3B"}
@@ -1,41 +1,20 @@
1
- import { apiRequest } from '@shipfox/client-api';
2
- import { useMutation, useQueryClient } from '@tanstack/react-query';
3
- import { useSetAtom } from 'jotai';
4
- import { authStateAtom, toAuthenticatedState } from '#state/auth.js';
5
- import { authRefreshQueryKey } from './refresh-auth.js';
6
- import { listUserWorkspaces, userWorkspacesQueryKey } from './workspace-auth.js';
1
+ import { loginResponseSchema } from '@shipfox/api-auth-dto';
2
+ import { checkedApiRequest } from '@shipfox/client-api';
3
+ import { useMutation } from '@tanstack/react-query';
4
+ import { useAuthTransition } from '#state/auth.js';
5
+ import { toAuthenticatedSession } from './auth-mapper.js';
7
6
  export async function loginAuth(body) {
8
- return await apiRequest('/auth/login', {
7
+ const response = await checkedApiRequest(loginResponseSchema, '/auth/login', {
9
8
  method: 'POST',
10
9
  body
11
10
  });
11
+ return toAuthenticatedSession(response);
12
12
  }
13
13
  export function useLoginAuth() {
14
- const queryClient = useQueryClient();
15
- const setState = useSetAtom(authStateAtom);
14
+ const { enterAuthenticated } = useAuthTransition();
16
15
  return useMutation({
17
16
  mutationFn: loginAuth,
18
- onSuccess: async (result)=>{
19
- queryClient.setQueryData(authRefreshQueryKey, result);
20
- // Resolve workspaces before flipping auth state to authenticated. A
21
- // single atomic setState avoids the intermediate window where the user
22
- // appears authenticated with zero workspaces, which sent users with
23
- // workspaces straight to `/setup/workspaces/new` after form login.
24
- let memberships = [];
25
- try {
26
- const workspaces = await queryClient.fetchQuery({
27
- queryKey: userWorkspacesQueryKey,
28
- queryFn: ()=>listUserWorkspaces(result.token),
29
- retry: false,
30
- staleTime: 0
31
- });
32
- memberships = workspaces.memberships;
33
- queryClient.setQueryData(userWorkspacesQueryKey, workspaces);
34
- } catch {
35
- // The user is authenticated even if workspace hydration fails.
36
- }
37
- setState(toAuthenticatedState(result, memberships));
38
- }
17
+ onSuccess: enterAuthenticated
39
18
  });
40
19
  }
41
20
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/login-auth.ts"],"sourcesContent":["import type {LoginBodyDto, LoginResponseDto} from '@shipfox/api-auth-dto';\nimport {apiRequest} from '@shipfox/client-api';\nimport {useMutation, useQueryClient} from '@tanstack/react-query';\nimport {useSetAtom} from 'jotai';\nimport {authStateAtom, toAuthenticatedState} from '#state/auth.js';\nimport {authRefreshQueryKey} from './refresh-auth.js';\nimport {listUserWorkspaces, userWorkspacesQueryKey} from './workspace-auth.js';\n\nexport async function loginAuth(body: LoginBodyDto) {\n return await apiRequest<LoginResponseDto>('/auth/login', {method: 'POST', body});\n}\n\nexport function useLoginAuth() {\n const queryClient = useQueryClient();\n const setState = useSetAtom(authStateAtom);\n\n return useMutation({\n mutationFn: loginAuth,\n onSuccess: async (result) => {\n queryClient.setQueryData(authRefreshQueryKey, result);\n // Resolve workspaces before flipping auth state to authenticated. A\n // single atomic setState avoids the intermediate window where the user\n // appears authenticated with zero workspaces, which sent users with\n // workspaces straight to `/setup/workspaces/new` after form login.\n let memberships: Awaited<ReturnType<typeof listUserWorkspaces>>['memberships'] = [];\n try {\n const workspaces = await queryClient.fetchQuery({\n queryKey: userWorkspacesQueryKey,\n queryFn: () => listUserWorkspaces(result.token),\n retry: false,\n staleTime: 0,\n });\n memberships = workspaces.memberships;\n queryClient.setQueryData(userWorkspacesQueryKey, workspaces);\n } catch {\n // The user is authenticated even if workspace hydration fails.\n }\n setState(toAuthenticatedState(result, memberships));\n },\n });\n}\n"],"names":["apiRequest","useMutation","useQueryClient","useSetAtom","authStateAtom","toAuthenticatedState","authRefreshQueryKey","listUserWorkspaces","userWorkspacesQueryKey","loginAuth","body","method","useLoginAuth","queryClient","setState","mutationFn","onSuccess","result","setQueryData","memberships","workspaces","fetchQuery","queryKey","queryFn","token","retry","staleTime"],"mappings":"AACA,SAAQA,UAAU,QAAO,sBAAsB;AAC/C,SAAQC,WAAW,EAAEC,cAAc,QAAO,wBAAwB;AAClE,SAAQC,UAAU,QAAO,QAAQ;AACjC,SAAQC,aAAa,EAAEC,oBAAoB,QAAO,iBAAiB;AACnE,SAAQC,mBAAmB,QAAO,oBAAoB;AACtD,SAAQC,kBAAkB,EAAEC,sBAAsB,QAAO,sBAAsB;AAE/E,OAAO,eAAeC,UAAUC,IAAkB;IAChD,OAAO,MAAMV,WAA6B,eAAe;QAACW,QAAQ;QAAQD;IAAI;AAChF;AAEA,OAAO,SAASE;IACd,MAAMC,cAAcX;IACpB,MAAMY,WAAWX,WAAWC;IAE5B,OAAOH,YAAY;QACjBc,YAAYN;QACZO,WAAW,OAAOC;YAChBJ,YAAYK,YAAY,CAACZ,qBAAqBW;YAC9C,oEAAoE;YACpE,uEAAuE;YACvE,oEAAoE;YACpE,mEAAmE;YACnE,IAAIE,cAA6E,EAAE;YACnF,IAAI;gBACF,MAAMC,aAAa,MAAMP,YAAYQ,UAAU,CAAC;oBAC9CC,UAAUd;oBACVe,SAAS,IAAMhB,mBAAmBU,OAAOO,KAAK;oBAC9CC,OAAO;oBACPC,WAAW;gBACb;gBACAP,cAAcC,WAAWD,WAAW;gBACpCN,YAAYK,YAAY,CAACV,wBAAwBY;YACnD,EAAE,OAAM;YACN,+DAA+D;YACjE;YACAN,SAAST,qBAAqBY,QAAQE;QACxC;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/hooks/api/login-auth.ts"],"sourcesContent":["import {type LoginBodyDto, loginResponseSchema} from '@shipfox/api-auth-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {useMutation} from '@tanstack/react-query';\nimport {useAuthTransition} from '#state/auth.js';\nimport {toAuthenticatedSession} from './auth-mapper.js';\n\nexport async function loginAuth(body: LoginBodyDto) {\n const response = await checkedApiRequest(loginResponseSchema, '/auth/login', {\n method: 'POST',\n body,\n });\n return toAuthenticatedSession(response);\n}\n\nexport function useLoginAuth() {\n const {enterAuthenticated} = useAuthTransition();\n\n return useMutation({\n mutationFn: loginAuth,\n onSuccess: enterAuthenticated,\n });\n}\n"],"names":["loginResponseSchema","checkedApiRequest","useMutation","useAuthTransition","toAuthenticatedSession","loginAuth","body","response","method","useLoginAuth","enterAuthenticated","mutationFn","onSuccess"],"mappings":"AAAA,SAA2BA,mBAAmB,QAAO,wBAAwB;AAC7E,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,iBAAiB,QAAO,iBAAiB;AACjD,SAAQC,sBAAsB,QAAO,mBAAmB;AAExD,OAAO,eAAeC,UAAUC,IAAkB;IAChD,MAAMC,WAAW,MAAMN,kBAAkBD,qBAAqB,eAAe;QAC3EQ,QAAQ;QACRF;IACF;IACA,OAAOF,uBAAuBG;AAChC;AAEA,OAAO,SAASE;IACd,MAAM,EAACC,kBAAkB,EAAC,GAAGP;IAE7B,OAAOD,YAAY;QACjBS,YAAYN;QACZO,WAAWF;IACb;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"logout-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/logout-auth.ts"],"names":[],"mappings":"AAcA,wBAAgB,aAAa,kFAW5B"}
1
+ {"version":3,"file":"logout-auth.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/logout-auth.ts"],"names":[],"mappings":"AAYA,wBAAgB,aAAa,kFAO5B"}
@@ -1,8 +1,6 @@
1
1
  import { apiRequest } from '@shipfox/client-api';
2
- import { useMutation, useQueryClient } from '@tanstack/react-query';
3
- import { useSetAtom } from 'jotai';
4
- import { authStateAtom } from '#state/auth.js';
5
- import { authRefreshQueryKey } from './refresh-auth.js';
2
+ import { useMutation } from '@tanstack/react-query';
3
+ import { useAuthTransition } from '#state/auth.js';
6
4
  async function logoutAuth() {
7
5
  try {
8
6
  await apiRequest('/auth/logout', {
@@ -14,18 +12,10 @@ async function logoutAuth() {
14
12
  }
15
13
  }
16
14
  export function useLogoutAuth() {
17
- const queryClient = useQueryClient();
18
- const setState = useSetAtom(authStateAtom);
15
+ const { enterGuest } = useAuthTransition();
19
16
  return useMutation({
20
17
  mutationFn: logoutAuth,
21
- onSettled: ()=>{
22
- setState({
23
- status: 'guest'
24
- });
25
- queryClient.removeQueries({
26
- queryKey: authRefreshQueryKey
27
- });
28
- }
18
+ onSettled: enterGuest
29
19
  });
30
20
  }
31
21
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/logout-auth.ts"],"sourcesContent":["import {apiRequest} from '@shipfox/client-api';\nimport {useMutation, useQueryClient} from '@tanstack/react-query';\nimport {useSetAtom} from 'jotai';\nimport {authStateAtom} from '#state/auth.js';\nimport {authRefreshQueryKey} from './refresh-auth.js';\n\nasync function logoutAuth() {\n try {\n await apiRequest<void>('/auth/logout', {method: 'POST', body: {}});\n } catch {\n // Logout is best-effort: local session state must clear even if the API is offline.\n }\n}\n\nexport function useLogoutAuth() {\n const queryClient = useQueryClient();\n const setState = useSetAtom(authStateAtom);\n\n return useMutation({\n mutationFn: logoutAuth,\n onSettled: () => {\n setState({status: 'guest'});\n queryClient.removeQueries({queryKey: authRefreshQueryKey});\n },\n });\n}\n"],"names":["apiRequest","useMutation","useQueryClient","useSetAtom","authStateAtom","authRefreshQueryKey","logoutAuth","method","body","useLogoutAuth","queryClient","setState","mutationFn","onSettled","status","removeQueries","queryKey"],"mappings":"AAAA,SAAQA,UAAU,QAAO,sBAAsB;AAC/C,SAAQC,WAAW,EAAEC,cAAc,QAAO,wBAAwB;AAClE,SAAQC,UAAU,QAAO,QAAQ;AACjC,SAAQC,aAAa,QAAO,iBAAiB;AAC7C,SAAQC,mBAAmB,QAAO,oBAAoB;AAEtD,eAAeC;IACb,IAAI;QACF,MAAMN,WAAiB,gBAAgB;YAACO,QAAQ;YAAQC,MAAM,CAAC;QAAC;IAClE,EAAE,OAAM;IACN,oFAAoF;IACtF;AACF;AAEA,OAAO,SAASC;IACd,MAAMC,cAAcR;IACpB,MAAMS,WAAWR,WAAWC;IAE5B,OAAOH,YAAY;QACjBW,YAAYN;QACZO,WAAW;YACTF,SAAS;gBAACG,QAAQ;YAAO;YACzBJ,YAAYK,aAAa,CAAC;gBAACC,UAAUX;YAAmB;QAC1D;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/hooks/api/logout-auth.ts"],"sourcesContent":["import {apiRequest} from '@shipfox/client-api';\nimport {useMutation} from '@tanstack/react-query';\nimport {useAuthTransition} from '#state/auth.js';\n\nasync function logoutAuth() {\n try {\n await apiRequest<void>('/auth/logout', {method: 'POST', body: {}});\n } catch {\n // Logout is best-effort: local session state must clear even if the API is offline.\n }\n}\n\nexport function useLogoutAuth() {\n const {enterGuest} = useAuthTransition();\n\n return useMutation({\n mutationFn: logoutAuth,\n onSettled: enterGuest,\n });\n}\n"],"names":["apiRequest","useMutation","useAuthTransition","logoutAuth","method","body","useLogoutAuth","enterGuest","mutationFn","onSettled"],"mappings":"AAAA,SAAQA,UAAU,QAAO,sBAAsB;AAC/C,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAQC,iBAAiB,QAAO,iBAAiB;AAEjD,eAAeC;IACb,IAAI;QACF,MAAMH,WAAiB,gBAAgB;YAACI,QAAQ;YAAQC,MAAM,CAAC;QAAC;IAClE,EAAE,OAAM;IACN,oFAAoF;IACtF;AACF;AAEA,OAAO,SAASC;IACd,MAAM,EAACC,UAAU,EAAC,GAAGL;IAErB,OAAOD,YAAY;QACjBO,YAAYL;QACZM,WAAWF;IACb;AACF"}
@@ -1,32 +1,10 @@
1
- import type { PasswordResetConfirmBodyDto, PasswordResetRequestBodyDto } from '@shipfox/api-auth-dto';
1
+ import { type PasswordResetConfirmBodyDto, type PasswordResetRequestBodyDto } from '@shipfox/api-auth-dto';
2
2
  export declare function requestPasswordReset(body: PasswordResetRequestBodyDto): Promise<void>;
3
- export declare function confirmPasswordReset(body: PasswordResetConfirmBodyDto): Promise<{
4
- token: string;
5
- user: {
6
- id: string;
7
- email: string;
8
- name: string | null;
9
- email_verified_at: string | null;
10
- status: "active" | "suspended" | "deleted";
11
- created_at: string;
12
- updated_at: string;
13
- };
14
- }>;
3
+ export declare function confirmPasswordReset(body: PasswordResetConfirmBodyDto): Promise<import("@shipfox/client-shell/runtime").AuthenticatedSession>;
15
4
  export declare function useRequestPasswordResetAuth(): import("@tanstack/react-query").UseMutationResult<void, Error, {
16
5
  email: string;
17
6
  }, unknown>;
18
- export declare function useConfirmPasswordResetAuth(): import("@tanstack/react-query").UseMutationResult<{
19
- token: string;
20
- user: {
21
- id: string;
22
- email: string;
23
- name: string | null;
24
- email_verified_at: string | null;
25
- status: "active" | "suspended" | "deleted";
26
- created_at: string;
27
- updated_at: string;
28
- };
29
- }, Error, {
7
+ export declare function useConfirmPasswordResetAuth(): import("@tanstack/react-query").UseMutationResult<import("@shipfox/client-shell/runtime").AuthenticatedSession, Error, {
30
8
  token: string;
31
9
  new_password: string;
32
10
  }, unknown>;