@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
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { PropsWithChildren, ReactNode } from 'react';
|
|
2
|
-
interface AuthShellProps {
|
|
3
|
-
title: string;
|
|
4
|
-
description: string;
|
|
5
|
-
children: ReactNode;
|
|
6
|
-
className?: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function AuthShell({ title, description, children, className }: AuthShellProps): import("react").JSX.Element;
|
|
9
|
-
export declare function AuthActions({ children }: PropsWithChildren): import("react").JSX.Element;
|
|
10
|
-
export {};
|
|
11
|
-
//# sourceMappingURL=auth-shell.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-shell.d.ts","sourceRoot":"","sources":["../../src/components/auth-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,iBAAiB,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;AAExD,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,EAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAC,EAAE,cAAc,+BAqClF;AAED,wBAAgB,WAAW,CAAC,EAAC,QAAQ,EAAC,EAAE,iBAAiB,+BAExD"}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Icon } from '@shipfox/react-ui/icon';
|
|
3
|
-
import { Header, Text } from '@shipfox/react-ui/typography';
|
|
4
|
-
export function AuthShell({ title, description, children, className }) {
|
|
5
|
-
return /*#__PURE__*/ _jsxs("main", {
|
|
6
|
-
className: "relative flex min-h-screen items-center justify-center overflow-hidden bg-background-subtle-base px-24 py-32 max-[520px]:items-start max-[520px]:px-20 max-[520px]:pb-32 max-[520px]:pt-56",
|
|
7
|
-
children: [
|
|
8
|
-
/*#__PURE__*/ _jsx("div", {
|
|
9
|
-
className: "pointer-events-none absolute left-1/2 top-0 h-[380px] w-full max-w-[880px] -translate-x-1/2 -translate-y-[120px] opacity-55 max-[520px]:-translate-y-[170px] max-[520px]:opacity-35",
|
|
10
|
-
"aria-hidden": "true",
|
|
11
|
-
style: {
|
|
12
|
-
maskImage: 'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)',
|
|
13
|
-
WebkitMaskImage: 'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)'
|
|
14
|
-
},
|
|
15
|
-
children: /*#__PURE__*/ _jsx("div", {
|
|
16
|
-
className: "h-full w-full bg-[radial-gradient(circle,rgba(230,62,0,0.48)_1.6px,transparent_1.8px)] bg-[length:44px_44px]"
|
|
17
|
-
})
|
|
18
|
-
}),
|
|
19
|
-
/*#__PURE__*/ _jsxs("section", {
|
|
20
|
-
className: className ?? 'relative flex w-full max-w-[384px] flex-col items-stretch gap-28',
|
|
21
|
-
"aria-labelledby": "auth-title",
|
|
22
|
-
children: [
|
|
23
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
24
|
-
className: "flex flex-col items-center gap-16",
|
|
25
|
-
children: [
|
|
26
|
-
/*#__PURE__*/ _jsx("div", {
|
|
27
|
-
className: "flex size-64 items-center justify-center rounded-12 border border-border-neutral-base bg-background-neutral-base p-10 shadow-button-neutral",
|
|
28
|
-
children: /*#__PURE__*/ _jsx(Icon, {
|
|
29
|
-
name: "shipfox",
|
|
30
|
-
className: "size-42 text-background-highlight-interactive"
|
|
31
|
-
})
|
|
32
|
-
}),
|
|
33
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
34
|
-
className: "flex min-w-[128px] flex-col items-center gap-4 text-center",
|
|
35
|
-
children: [
|
|
36
|
-
/*#__PURE__*/ _jsx(Header, {
|
|
37
|
-
id: "auth-title",
|
|
38
|
-
variant: "h1",
|
|
39
|
-
children: title
|
|
40
|
-
}),
|
|
41
|
-
/*#__PURE__*/ _jsx(Text, {
|
|
42
|
-
size: "sm",
|
|
43
|
-
className: "text-foreground-neutral-subtle",
|
|
44
|
-
children: description
|
|
45
|
-
})
|
|
46
|
-
]
|
|
47
|
-
})
|
|
48
|
-
]
|
|
49
|
-
}),
|
|
50
|
-
children
|
|
51
|
-
]
|
|
52
|
-
})
|
|
53
|
-
]
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
export function AuthActions({ children }) {
|
|
57
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
58
|
-
className: "flex flex-col gap-20",
|
|
59
|
-
children: children
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
//# sourceMappingURL=auth-shell.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/auth-shell.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport type {PropsWithChildren, ReactNode} from 'react';\n\ninterface AuthShellProps {\n title: string;\n description: string;\n children: ReactNode;\n className?: string;\n}\n\nexport function AuthShell({title, description, children, className}: AuthShellProps) {\n return (\n <main className=\"relative flex min-h-screen items-center justify-center overflow-hidden bg-background-subtle-base px-24 py-32 max-[520px]:items-start max-[520px]:px-20 max-[520px]:pb-32 max-[520px]:pt-56\">\n <div\n className=\"pointer-events-none absolute left-1/2 top-0 h-[380px] w-full max-w-[880px] -translate-x-1/2 -translate-y-[120px] opacity-55 max-[520px]:-translate-y-[170px] max-[520px]:opacity-35\"\n aria-hidden=\"true\"\n style={{\n maskImage:\n 'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)',\n WebkitMaskImage:\n 'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)',\n }}\n >\n <div className=\"h-full w-full bg-[radial-gradient(circle,rgba(230,62,0,0.48)_1.6px,transparent_1.8px)] bg-[length:44px_44px]\" />\n </div>\n\n <section\n className={className ?? 'relative flex w-full max-w-[384px] flex-col items-stretch gap-28'}\n aria-labelledby=\"auth-title\"\n >\n <div className=\"flex flex-col items-center gap-16\">\n <div className=\"flex size-64 items-center justify-center rounded-12 border border-border-neutral-base bg-background-neutral-base p-10 shadow-button-neutral\">\n <Icon name=\"shipfox\" className=\"size-42 text-background-highlight-interactive\" />\n </div>\n <div className=\"flex min-w-[128px] flex-col items-center gap-4 text-center\">\n <Header id=\"auth-title\" variant=\"h1\">\n {title}\n </Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {description}\n </Text>\n </div>\n </div>\n {children}\n </section>\n </main>\n );\n}\n\nexport function AuthActions({children}: PropsWithChildren) {\n return <div className=\"flex flex-col gap-20\">{children}</div>;\n}\n"],"names":["Icon","Header","Text","AuthShell","title","description","children","className","main","div","aria-hidden","style","maskImage","WebkitMaskImage","section","aria-labelledby","name","id","variant","size","AuthActions"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAU1D,OAAO,SAASC,UAAU,EAACC,KAAK,EAAEC,WAAW,EAAEC,QAAQ,EAAEC,SAAS,EAAiB;IACjF,qBACE,MAACC;QAAKD,WAAU;;0BACd,KAACE;gBACCF,WAAU;gBACVG,eAAY;gBACZC,OAAO;oBACLC,WACE;oBACFC,iBACE;gBACJ;0BAEA,cAAA,KAACJ;oBAAIF,WAAU;;;0BAGjB,MAACO;gBACCP,WAAWA,aAAa;gBACxBQ,mBAAgB;;kCAEhB,MAACN;wBAAIF,WAAU;;0CACb,KAACE;gCAAIF,WAAU;0CACb,cAAA,KAACP;oCAAKgB,MAAK;oCAAUT,WAAU;;;0CAEjC,MAACE;gCAAIF,WAAU;;kDACb,KAACN;wCAAOgB,IAAG;wCAAaC,SAAQ;kDAC7Bd;;kDAEH,KAACF;wCAAKiB,MAAK;wCAAKZ,WAAU;kDACvBF;;;;;;oBAINC;;;;;AAIT;AAEA,OAAO,SAASc,YAAY,EAACd,QAAQ,EAAoB;IACvD,qBAAO,KAACG;QAAIF,WAAU;kBAAwBD;;AAChD"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare function getResendRemainingSeconds(params: {
|
|
2
|
-
nextResendAvailableAt: number | undefined;
|
|
3
|
-
now: number;
|
|
4
|
-
}): number;
|
|
5
|
-
export declare function parseNextResendAvailableAt(value: string): number | undefined;
|
|
6
|
-
//# sourceMappingURL=email-verification-resend-model.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"email-verification-resend-model.d.ts","sourceRoot":"","sources":["../../src/pages/email-verification-resend-model.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,MAAM,CAIT;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK5E"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export function getResendRemainingSeconds(params) {
|
|
2
|
-
if (!params.nextResendAvailableAt) return 0;
|
|
3
|
-
return Math.max(0, Math.ceil((params.nextResendAvailableAt - params.now) / 1000));
|
|
4
|
-
}
|
|
5
|
-
export function parseNextResendAvailableAt(value) {
|
|
6
|
-
const nextAvailableAt = Date.parse(value);
|
|
7
|
-
if (!Number.isFinite(nextAvailableAt)) return undefined;
|
|
8
|
-
return nextAvailableAt;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
//# sourceMappingURL=email-verification-resend-model.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pages/email-verification-resend-model.ts"],"sourcesContent":["export function getResendRemainingSeconds(params: {\n nextResendAvailableAt: number | undefined;\n now: number;\n}): number {\n if (!params.nextResendAvailableAt) return 0;\n\n return Math.max(0, Math.ceil((params.nextResendAvailableAt - params.now) / 1000));\n}\n\nexport function parseNextResendAvailableAt(value: string): number | undefined {\n const nextAvailableAt = Date.parse(value);\n if (!Number.isFinite(nextAvailableAt)) return undefined;\n\n return nextAvailableAt;\n}\n"],"names":["getResendRemainingSeconds","params","nextResendAvailableAt","Math","max","ceil","now","parseNextResendAvailableAt","value","nextAvailableAt","Date","parse","Number","isFinite","undefined"],"mappings":"AAAA,OAAO,SAASA,0BAA0BC,MAGzC;IACC,IAAI,CAACA,OAAOC,qBAAqB,EAAE,OAAO;IAE1C,OAAOC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,IAAI,CAAC,AAACJ,CAAAA,OAAOC,qBAAqB,GAAGD,OAAOK,GAAG,AAAD,IAAK;AAC7E;AAEA,OAAO,SAASC,2BAA2BC,KAAa;IACtD,MAAMC,kBAAkBC,KAAKC,KAAK,CAACH;IACnC,IAAI,CAACI,OAAOC,QAAQ,CAACJ,kBAAkB,OAAOK;IAE9C,OAAOL;AACT"}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import {Icon} from '@shipfox/react-ui/icon';
|
|
2
|
-
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
3
|
-
import type {PropsWithChildren, ReactNode} from 'react';
|
|
4
|
-
|
|
5
|
-
interface AuthShellProps {
|
|
6
|
-
title: string;
|
|
7
|
-
description: string;
|
|
8
|
-
children: ReactNode;
|
|
9
|
-
className?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function AuthShell({title, description, children, className}: AuthShellProps) {
|
|
13
|
-
return (
|
|
14
|
-
<main className="relative flex min-h-screen items-center justify-center overflow-hidden bg-background-subtle-base px-24 py-32 max-[520px]:items-start max-[520px]:px-20 max-[520px]:pb-32 max-[520px]:pt-56">
|
|
15
|
-
<div
|
|
16
|
-
className="pointer-events-none absolute left-1/2 top-0 h-[380px] w-full max-w-[880px] -translate-x-1/2 -translate-y-[120px] opacity-55 max-[520px]:-translate-y-[170px] max-[520px]:opacity-35"
|
|
17
|
-
aria-hidden="true"
|
|
18
|
-
style={{
|
|
19
|
-
maskImage:
|
|
20
|
-
'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)',
|
|
21
|
-
WebkitMaskImage:
|
|
22
|
-
'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)',
|
|
23
|
-
}}
|
|
24
|
-
>
|
|
25
|
-
<div className="h-full w-full bg-[radial-gradient(circle,rgba(230,62,0,0.48)_1.6px,transparent_1.8px)] bg-[length:44px_44px]" />
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<section
|
|
29
|
-
className={className ?? 'relative flex w-full max-w-[384px] flex-col items-stretch gap-28'}
|
|
30
|
-
aria-labelledby="auth-title"
|
|
31
|
-
>
|
|
32
|
-
<div className="flex flex-col items-center gap-16">
|
|
33
|
-
<div className="flex size-64 items-center justify-center rounded-12 border border-border-neutral-base bg-background-neutral-base p-10 shadow-button-neutral">
|
|
34
|
-
<Icon name="shipfox" className="size-42 text-background-highlight-interactive" />
|
|
35
|
-
</div>
|
|
36
|
-
<div className="flex min-w-[128px] flex-col items-center gap-4 text-center">
|
|
37
|
-
<Header id="auth-title" variant="h1">
|
|
38
|
-
{title}
|
|
39
|
-
</Header>
|
|
40
|
-
<Text size="sm" className="text-foreground-neutral-subtle">
|
|
41
|
-
{description}
|
|
42
|
-
</Text>
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
{children}
|
|
46
|
-
</section>
|
|
47
|
-
</main>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function AuthActions({children}: PropsWithChildren) {
|
|
52
|
-
return <div className="flex flex-col gap-20">{children}</div>;
|
|
53
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getResendRemainingSeconds,
|
|
3
|
-
parseNextResendAvailableAt,
|
|
4
|
-
} from './email-verification-resend-model.js';
|
|
5
|
-
|
|
6
|
-
describe('email verification resend model', () => {
|
|
7
|
-
test('rounds remaining cooldown seconds up for display', () => {
|
|
8
|
-
const now = Date.UTC(2026, 4, 11, 12, 0, 0);
|
|
9
|
-
|
|
10
|
-
const result = getResendRemainingSeconds({
|
|
11
|
-
nextResendAvailableAt: now + 1_001,
|
|
12
|
-
now,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
expect(result).toBe(2);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('does not return negative remaining cooldown seconds', () => {
|
|
19
|
-
const now = Date.UTC(2026, 4, 11, 12, 0, 0);
|
|
20
|
-
|
|
21
|
-
const result = getResendRemainingSeconds({
|
|
22
|
-
nextResendAvailableAt: now - 1_000,
|
|
23
|
-
now,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
expect(result).toBe(0);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('parses valid server cooldown timestamps', () => {
|
|
30
|
-
const timestamp = '2026-05-11T12:01:00.000Z';
|
|
31
|
-
|
|
32
|
-
const result = parseNextResendAvailableAt(timestamp);
|
|
33
|
-
|
|
34
|
-
expect(result).toBe(Date.parse(timestamp));
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('ignores invalid server cooldown timestamps', () => {
|
|
38
|
-
const result = parseNextResendAvailableAt('not-a-date');
|
|
39
|
-
|
|
40
|
-
expect(result).toBeUndefined();
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export function getResendRemainingSeconds(params: {
|
|
2
|
-
nextResendAvailableAt: number | undefined;
|
|
3
|
-
now: number;
|
|
4
|
-
}): number {
|
|
5
|
-
if (!params.nextResendAvailableAt) return 0;
|
|
6
|
-
|
|
7
|
-
return Math.max(0, Math.ceil((params.nextResendAvailableAt - params.now) / 1000));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function parseNextResendAvailableAt(value: string): number | undefined {
|
|
11
|
-
const nextAvailableAt = Date.parse(value);
|
|
12
|
-
if (!Number.isFinite(nextAvailableAt)) return undefined;
|
|
13
|
-
|
|
14
|
-
return nextAvailableAt;
|
|
15
|
-
}
|