@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +32 -0
- package/dist/components/email-code-verification.d.ts +20 -0
- package/dist/components/email-code-verification.d.ts.map +1 -0
- package/dist/components/email-code-verification.js +136 -0
- package/dist/components/email-code-verification.js.map +1 -0
- package/dist/components/redirect-context.d.ts +11 -0
- package/dist/components/redirect-context.d.ts.map +1 -0
- package/dist/components/redirect-context.js +22 -0
- package/dist/components/redirect-context.js.map +1 -0
- package/dist/components/redirect-target.d.ts.map +1 -1
- package/dist/components/redirect-target.js +13 -12
- package/dist/components/redirect-target.js.map +1 -1
- package/dist/components/workspace-switcher.stories.d.ts +1 -1
- package/dist/continuation.d.ts +4 -0
- package/dist/continuation.d.ts.map +1 -0
- package/dist/continuation.js +5 -0
- package/dist/continuation.js.map +1 -0
- package/dist/hooks/api/auth-mapper.d.ts +21 -0
- package/dist/hooks/api/auth-mapper.d.ts.map +1 -0
- package/dist/hooks/api/auth-mapper.js +25 -0
- package/dist/hooks/api/auth-mapper.js.map +1 -0
- package/dist/hooks/api/login-auth.d.ts +3 -25
- package/dist/hooks/api/login-auth.d.ts.map +1 -1
- package/dist/hooks/api/login-auth.js +9 -30
- package/dist/hooks/api/login-auth.js.map +1 -1
- package/dist/hooks/api/logout-auth.d.ts.map +1 -1
- package/dist/hooks/api/logout-auth.js +4 -14
- package/dist/hooks/api/logout-auth.js.map +1 -1
- package/dist/hooks/api/password-reset-auth.d.ts +3 -25
- package/dist/hooks/api/password-reset-auth.d.ts.map +1 -1
- package/dist/hooks/api/password-reset-auth.js +9 -25
- package/dist/hooks/api/password-reset-auth.js.map +1 -1
- package/dist/hooks/api/signup-auth.d.ts +1 -25
- package/dist/hooks/api/signup-auth.d.ts.map +1 -1
- package/dist/hooks/api/signup-auth.js +5 -2
- package/dist/hooks/api/signup-auth.js.map +1 -1
- package/dist/hooks/api/verify-email-auth.d.ts +1 -12
- package/dist/hooks/api/verify-email-auth.d.ts.map +1 -1
- package/dist/hooks/api/verify-email-auth.js +10 -26
- package/dist/hooks/api/verify-email-auth.js.map +1 -1
- package/dist/hooks/api/workspace-auth.d.ts +3 -17
- package/dist/hooks/api/workspace-auth.d.ts.map +1 -1
- package/dist/hooks/api/workspace-auth.js +5 -2
- package/dist/hooks/api/workspace-auth.js.map +1 -1
- package/dist/hooks/api/workspace-mapper.d.ts +8 -0
- package/dist/hooks/api/workspace-mapper.d.ts.map +1 -0
- package/dist/hooks/api/workspace-mapper.js +9 -0
- package/dist/hooks/api/workspace-mapper.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/pages/invitation-context.d.ts +9 -19
- package/dist/pages/invitation-context.d.ts.map +1 -1
- package/dist/pages/invitation-context.js +7 -46
- package/dist/pages/invitation-context.js.map +1 -1
- package/dist/pages/login-page.js +2 -2
- package/dist/pages/login-page.js.map +1 -1
- package/dist/pages/logout-page.js +1 -1
- package/dist/pages/logout-page.js.map +1 -1
- package/dist/pages/password-reset-page.d.ts.map +1 -1
- package/dist/pages/password-reset-page.js +1 -4
- package/dist/pages/password-reset-page.js.map +1 -1
- package/dist/pages/signup-page.d.ts.map +1 -1
- package/dist/pages/signup-page.js +58 -152
- package/dist/pages/signup-page.js.map +1 -1
- package/dist/state/auth.d.ts +1 -1
- package/dist/state/auth.d.ts.map +1 -1
- package/dist/state/auth.js +1 -1
- package/dist/state/auth.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -39
- package/src/components/auth-provider.test.tsx +173 -3
- package/src/components/email-code-verification.test.tsx +159 -0
- package/src/components/email-code-verification.tsx +177 -0
- package/src/components/redirect-context.test.ts +51 -0
- package/src/components/redirect-context.ts +26 -0
- package/src/components/redirect-target.test.ts +8 -0
- package/src/components/redirect-target.ts +13 -12
- package/src/continuation.test.ts +15 -0
- package/src/continuation.ts +6 -0
- package/src/hooks/api/auth-mapper.test.ts +77 -0
- package/src/hooks/api/auth-mapper.ts +39 -0
- package/src/hooks/api/login-auth.test.ts +1 -1
- package/src/hooks/api/login-auth.ts +12 -31
- package/src/hooks/api/logout-auth.ts +4 -10
- package/src/hooks/api/password-reset-auth.test.ts +1 -1
- package/src/hooks/api/password-reset-auth.ts +19 -32
- package/src/hooks/api/signup-auth.ts +8 -3
- package/src/hooks/api/verify-email-auth.ts +21 -34
- package/src/hooks/api/workspace-auth.ts +8 -3
- package/src/hooks/api/workspace-mapper.test.ts +21 -0
- package/src/hooks/api/workspace-mapper.ts +11 -0
- package/src/index.ts +0 -1
- package/src/pages/invitation-context.ts +10 -51
- package/src/pages/login-page.test.tsx +1 -1
- package/src/pages/login-page.tsx +2 -2
- package/src/pages/logout-page.tsx +1 -1
- package/src/pages/password-reset-page.tsx +1 -4
- package/src/pages/signup-page.test.tsx +1 -1
- package/src/pages/signup-page.tsx +41 -137
- package/src/state/auth.ts +1 -0
- package/src/state/last-workspace.test.ts +16 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/auth-shell.d.ts +0 -11
- package/dist/components/auth-shell.d.ts.map +0 -1
- package/dist/components/auth-shell.js +0 -63
- package/dist/components/auth-shell.js.map +0 -1
- package/dist/pages/email-verification-resend-model.d.ts +0 -6
- package/dist/pages/email-verification-resend-model.d.ts.map +0 -1
- package/dist/pages/email-verification-resend-model.js +0 -11
- package/dist/pages/email-verification-resend-model.js.map +0 -1
- package/src/components/auth-shell.tsx +0 -53
- package/src/pages/email-verification-resend-model.test.ts +0 -42
- package/src/pages/email-verification-resend-model.ts +0 -15
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
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":"
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return
|
|
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
|
|
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"}
|
|
@@ -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
|
|
2
|
-
export declare function loginAuth(body: LoginBodyDto): Promise<
|
|
3
|
-
|
|
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,
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
-
|
|
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
|
|
15
|
-
const setState = useSetAtom(authStateAtom);
|
|
14
|
+
const { enterAuthenticated } = useAuthTransition();
|
|
16
15
|
return useMutation({
|
|
17
16
|
mutationFn: loginAuth,
|
|
18
|
-
onSuccess:
|
|
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
|
|
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":"
|
|
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
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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>;
|