@slashid/jump-page 0.0.7-beta.1 → 0.0.7-beta.2
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/cli.js +4 -4
- package/dist/_astro/app.f7d04798.js +39 -0
- package/dist/_astro/client.a5ffbec0.js +24 -0
- package/dist/_astro/index.03be2d59.js +9 -0
- package/dist/_astro/index.2dd0ad08.css +1 -0
- package/dist/_astro/index.cc62d81f.css +1 -0
- package/dist/favicon.png +0 -0
- package/dist/index.html +7 -0
- package/package.json +2 -2
- package/src/components/README.md +0 -3
- package/src/components/app/app.component.tsx +0 -83
- package/src/components/app/app.context.tsx +0 -41
- package/src/components/app/app.css.ts +0 -15
- package/src/components/app/index.ts +0 -1
- package/src/components/card/card.component.tsx +0 -42
- package/src/components/card/card.css.ts +0 -40
- package/src/components/card/index.ts +0 -1
- package/src/components/flow/flow.component.tsx +0 -99
- package/src/components/flow/flow.css.ts +0 -20
- package/src/components/flow/flow.domain.ts +0 -18
- package/src/components/flow/flow.error.tsx +0 -32
- package/src/components/flow/flow.initial.tsx +0 -14
- package/src/components/flow/flow.loader.tsx +0 -9
- package/src/components/flow/flow.progress.tsx +0 -119
- package/src/components/flow/flow.recover.tsx +0 -121
- package/src/components/flow/flow.success.tsx +0 -40
- package/src/components/flow/flow.types.ts +0 -48
- package/src/components/flow/index.ts +0 -3
- package/src/components/text/index.ts +0 -1
- package/src/components/text/text.component.tsx +0 -8
- package/src/components/text/use-i18n.ts +0 -13
- package/src/config.ts +0 -7
- package/src/domain/i18n.ts +0 -115
- package/src/env.d.ts +0 -1
- package/src/layouts/README.md +0 -3
- package/src/layouts/page.astro +0 -33
- package/src/pages/index.astro +0 -8
package/src/components/README.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { SlashID } from "@slashid/slashid";
|
|
2
|
-
import * as Sentry from "@sentry/react";
|
|
3
|
-
import {
|
|
4
|
-
Logo,
|
|
5
|
-
TextProvider,
|
|
6
|
-
ThemeRoot,
|
|
7
|
-
isBrowser,
|
|
8
|
-
} from "@slashid/react-primitives";
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
AppContext,
|
|
12
|
-
initialAppContextState,
|
|
13
|
-
type AppState,
|
|
14
|
-
type AppContextState,
|
|
15
|
-
type AppReadyState,
|
|
16
|
-
} from "./app.context";
|
|
17
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
18
|
-
import { I18N, detectLanguage } from "../../domain/i18n";
|
|
19
|
-
import { Flow, Error } from "../flow";
|
|
20
|
-
import { Card } from "../card";
|
|
21
|
-
|
|
22
|
-
import * as styles from "./app.css";
|
|
23
|
-
import { config } from "../../config";
|
|
24
|
-
|
|
25
|
-
export function App() {
|
|
26
|
-
const isClientSide = isBrowser();
|
|
27
|
-
const [appState, setAppState] = useState<AppState>("initial");
|
|
28
|
-
const sidRef = useRef<SlashID | undefined>(undefined);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (appState === "initial") {
|
|
32
|
-
Sentry.init({
|
|
33
|
-
dsn: "https://6435e8a20ba8b61404fb4621d2aedd4a@o4505317722619904.ingest.sentry.io/4506275681075200",
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
sidRef.current = new SlashID({
|
|
37
|
-
analyticsEnabled: false,
|
|
38
|
-
sdkURL: config.sdkURL,
|
|
39
|
-
baseURL: config.baseURL,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
setAppState("ready");
|
|
43
|
-
}
|
|
44
|
-
}, [appState]);
|
|
45
|
-
|
|
46
|
-
const state: AppContextState = useMemo(() => {
|
|
47
|
-
// checking for hydration makes the first client side render the same as the one on the server side
|
|
48
|
-
// language check and URL params can only be done on the client side so we need to wait for the first render
|
|
49
|
-
if (!isClientSide || appState == "initial") {
|
|
50
|
-
return initialAppContextState;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const appReadyState: AppReadyState = {
|
|
54
|
-
// TODO read customisation props from the URL
|
|
55
|
-
logo: <Logo />,
|
|
56
|
-
language: detectLanguage(),
|
|
57
|
-
state: appState,
|
|
58
|
-
sdk: sidRef.current as SlashID,
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return appReadyState;
|
|
62
|
-
}, [appState, isClientSide]);
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<ThemeRoot theme="light">
|
|
66
|
-
<Sentry.ErrorBoundary
|
|
67
|
-
fallback={
|
|
68
|
-
<Card>
|
|
69
|
-
<Error type="error" />
|
|
70
|
-
</Card>
|
|
71
|
-
}
|
|
72
|
-
>
|
|
73
|
-
<AppContext.Provider value={state}>
|
|
74
|
-
<TextProvider text={I18N[state.language]}>
|
|
75
|
-
<main className={styles.app}>
|
|
76
|
-
<Flow />
|
|
77
|
-
</main>
|
|
78
|
-
</TextProvider>
|
|
79
|
-
</AppContext.Provider>
|
|
80
|
-
</Sentry.ErrorBoundary>
|
|
81
|
-
</ThemeRoot>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from "react";
|
|
2
|
-
import { Logo } from "@slashid/react-primitives";
|
|
3
|
-
import type { SlashID } from "@slashid/slashid";
|
|
4
|
-
|
|
5
|
-
import { type Language } from "../../domain/i18n";
|
|
6
|
-
|
|
7
|
-
export type AppState = "initial" | "ready";
|
|
8
|
-
|
|
9
|
-
type AppConfig = {
|
|
10
|
-
logo: string | React.ReactNode;
|
|
11
|
-
language: Language;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type AppInitialState = AppConfig & {
|
|
15
|
-
state: "initial";
|
|
16
|
-
sdk: undefined;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type AppReadyState = AppConfig & {
|
|
20
|
-
state: "ready";
|
|
21
|
-
sdk: SlashID;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type AppContextState = AppInitialState | AppReadyState;
|
|
25
|
-
|
|
26
|
-
export const initialAppContextState: AppContextState = {
|
|
27
|
-
logo: <Logo />,
|
|
28
|
-
language: "en",
|
|
29
|
-
state: "initial",
|
|
30
|
-
sdk: undefined,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export const AppContext = createContext<AppContextState>(
|
|
34
|
-
initialAppContextState
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
export const useAppContext = () => {
|
|
38
|
-
const appContext = useContext(AppContext);
|
|
39
|
-
|
|
40
|
-
return appContext;
|
|
41
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { publicVariables } from "@slashid/react-primitives";
|
|
2
|
-
import { style } from "@vanilla-extract/css";
|
|
3
|
-
|
|
4
|
-
export const app = style({
|
|
5
|
-
fontFamily: publicVariables.font.fontFamily,
|
|
6
|
-
backgroundColor: publicVariables.color.background,
|
|
7
|
-
boxSizing: "border-box",
|
|
8
|
-
display: "flex",
|
|
9
|
-
justifyContent: "center",
|
|
10
|
-
alignItems: "center",
|
|
11
|
-
width: "100%",
|
|
12
|
-
height: "100vh",
|
|
13
|
-
padding: 0,
|
|
14
|
-
margin: 0,
|
|
15
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./app.component";
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { useAppContext } from "../app/app.context";
|
|
2
|
-
import { Text } from "../text";
|
|
3
|
-
import * as styles from "./card.css";
|
|
4
|
-
|
|
5
|
-
export type Props = {
|
|
6
|
-
children: React.ReactNode;
|
|
7
|
-
footer?: React.ReactNode;
|
|
8
|
-
header?: React.ReactNode;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
function DefaultHeader() {
|
|
12
|
-
const { logo } = useAppContext();
|
|
13
|
-
|
|
14
|
-
return <div className={styles.logo}>{logo}</div>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function DefaultFooter() {
|
|
18
|
-
return (
|
|
19
|
-
<Text
|
|
20
|
-
t="footer.text"
|
|
21
|
-
variant={{
|
|
22
|
-
size: "xs",
|
|
23
|
-
weight: "semibold",
|
|
24
|
-
color: "placeholder",
|
|
25
|
-
}}
|
|
26
|
-
/>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function Card({
|
|
31
|
-
children,
|
|
32
|
-
header = <DefaultHeader />,
|
|
33
|
-
footer = <DefaultFooter />,
|
|
34
|
-
}: Props) {
|
|
35
|
-
return (
|
|
36
|
-
<article className={styles.card}>
|
|
37
|
-
{header && <header className={styles.header}>{header}</header>}
|
|
38
|
-
<div>{children}</div>
|
|
39
|
-
{footer && <footer className={styles.footer}>{footer}</footer>}
|
|
40
|
-
</article>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { publicVariables, theme } from "@slashid/react-primitives";
|
|
2
|
-
import { globalStyle, style } from "@vanilla-extract/css";
|
|
3
|
-
|
|
4
|
-
export const card = style({
|
|
5
|
-
display: "grid",
|
|
6
|
-
gridTemplateRows: "auto 1fr auto",
|
|
7
|
-
backgroundColor: publicVariables.color.panel,
|
|
8
|
-
width: "100%",
|
|
9
|
-
minHeight: "100vh",
|
|
10
|
-
padding: "32px 32px 16px 32px",
|
|
11
|
-
boxSizing: "border-box",
|
|
12
|
-
|
|
13
|
-
"@media": {
|
|
14
|
-
"screen and (min-width: 768px)": {
|
|
15
|
-
borderRadius: `calc(2 * ${publicVariables.border.radius})`,
|
|
16
|
-
width: "465px",
|
|
17
|
-
minHeight: "330px",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export const header = style({
|
|
23
|
-
marginBottom: theme.space[1],
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
export const footer = style({
|
|
27
|
-
width: "100%",
|
|
28
|
-
textAlign: "center",
|
|
29
|
-
marginTop: theme.space[8],
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export const logo = style({
|
|
33
|
-
width: 32,
|
|
34
|
-
height: 32,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
globalStyle(`${logo} > svg`, {
|
|
38
|
-
height: 32,
|
|
39
|
-
width: 32,
|
|
40
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./card.component";
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import * as Sentry from "@sentry/react";
|
|
3
|
-
import { Card } from "../card";
|
|
4
|
-
import { Initial } from "./flow.initial";
|
|
5
|
-
import { Error } from "./flow.error";
|
|
6
|
-
import type { Challenges, State, FlowType } from "./flow.types";
|
|
7
|
-
import { useAppContext } from "../app/app.context";
|
|
8
|
-
import { Progress } from "./flow.progress";
|
|
9
|
-
import type { ChallengeListInner } from "@slashid/slashid";
|
|
10
|
-
import { Success } from "./flow.success";
|
|
11
|
-
import { ensureError } from "./flow.domain";
|
|
12
|
-
|
|
13
|
-
function isPasswordRecoveryFlow(challenges: Challenges): boolean {
|
|
14
|
-
return challenges.some((challenge) => challenge.type === "password_reset");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getFlowTypeFromChallenges(challenges: Challenges): FlowType {
|
|
18
|
-
if (isPasswordRecoveryFlow(challenges)) {
|
|
19
|
-
return "password-recovery";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return "catch-all";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function Flow() {
|
|
26
|
-
const [flowState, setFlowState] = useState<State>({ state: "initial" });
|
|
27
|
-
const { sdk, state: appState } = useAppContext();
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (flowState.state !== "initial") return;
|
|
31
|
-
|
|
32
|
-
async function processChallenges() {
|
|
33
|
-
if (!sdk) return;
|
|
34
|
-
setFlowState({ state: "parsing-url" });
|
|
35
|
-
|
|
36
|
-
let challenges: ChallengeListInner[] | null = [];
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
challenges = await sdk.getChallengesFromURL();
|
|
40
|
-
} catch (e: unknown) {
|
|
41
|
-
Sentry.captureException(ensureError(e));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!challenges) {
|
|
45
|
-
setFlowState({ state: "no-challenges", challenges: null });
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const flowType = getFlowTypeFromChallenges(challenges);
|
|
50
|
-
|
|
51
|
-
setFlowState({ state: "progress", challenges, flowType });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
processChallenges();
|
|
55
|
-
}, [appState, flowState.state, sdk]);
|
|
56
|
-
|
|
57
|
-
const handleSuccess = () => {
|
|
58
|
-
if (flowState.state !== "progress") return;
|
|
59
|
-
|
|
60
|
-
setFlowState({
|
|
61
|
-
state: "success",
|
|
62
|
-
challenges: flowState.challenges,
|
|
63
|
-
flowType: flowState.flowType,
|
|
64
|
-
});
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const handleError = ({ error }: { error: Error }) => {
|
|
68
|
-
if (flowState.state !== "progress") return;
|
|
69
|
-
|
|
70
|
-
setFlowState({
|
|
71
|
-
state: "error",
|
|
72
|
-
challenges: flowState.challenges,
|
|
73
|
-
flowType: flowState.flowType,
|
|
74
|
-
error,
|
|
75
|
-
});
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<Card>
|
|
80
|
-
{(appState !== "ready" || flowState.state === "parsing-url") && (
|
|
81
|
-
<Initial />
|
|
82
|
-
)}
|
|
83
|
-
{appState === "ready" && (
|
|
84
|
-
<>
|
|
85
|
-
{flowState.state === "progress" && (
|
|
86
|
-
<Progress
|
|
87
|
-
onSuccess={handleSuccess}
|
|
88
|
-
onError={handleError}
|
|
89
|
-
flowType={flowState.flowType}
|
|
90
|
-
/>
|
|
91
|
-
)}
|
|
92
|
-
{flowState.state === "no-challenges" && <Error type="warning" />}
|
|
93
|
-
{flowState.state === "success" && <Success />}
|
|
94
|
-
{flowState.state === "error" && <Error type="error" />}
|
|
95
|
-
</>
|
|
96
|
-
)}
|
|
97
|
-
</Card>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { publicVariables, theme } from "@slashid/react-primitives";
|
|
2
|
-
import { style } from "@vanilla-extract/css";
|
|
3
|
-
|
|
4
|
-
export const formInputs = style({
|
|
5
|
-
margin: "24px 0",
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
export const passwordRecoveryPrompt = style({
|
|
9
|
-
display: "flex",
|
|
10
|
-
alignItems: "baseline",
|
|
11
|
-
marginTop: "8px",
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
export const errorMessage = style({
|
|
15
|
-
display: "block",
|
|
16
|
-
marginTop: theme.space[2],
|
|
17
|
-
color: publicVariables.color.error,
|
|
18
|
-
fontWeight: "600",
|
|
19
|
-
lineHeight: "122%",
|
|
20
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ensure that a valid Error instance is returned, since in JS you can throw anything.
|
|
3
|
-
*/
|
|
4
|
-
export function ensureError(value: unknown): Error {
|
|
5
|
-
if (value instanceof Error) return value;
|
|
6
|
-
|
|
7
|
-
let stringified = "[Unable to stringify the thrown value]";
|
|
8
|
-
try {
|
|
9
|
-
stringified = JSON.stringify(value);
|
|
10
|
-
} catch {
|
|
11
|
-
// ignore
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const error = new Error(
|
|
15
|
-
`This value was thrown as is, not through an Error: ${stringified}`
|
|
16
|
-
);
|
|
17
|
-
return error;
|
|
18
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Circle, Exclamation, Stack } from "@slashid/react-primitives";
|
|
2
|
-
import { Text } from "../text";
|
|
3
|
-
|
|
4
|
-
export type ErrorType = "warning" | "error";
|
|
5
|
-
|
|
6
|
-
function ErrorIcon({ type }: { type: ErrorType }) {
|
|
7
|
-
return (
|
|
8
|
-
<Circle variant={type === "warning" ? "blue" : "red"} shouldAnimate={false}>
|
|
9
|
-
<Exclamation />
|
|
10
|
-
</Circle>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function Error({ type }: { type: ErrorType }) {
|
|
15
|
-
return (
|
|
16
|
-
<>
|
|
17
|
-
<Stack space="0.25">
|
|
18
|
-
<Text
|
|
19
|
-
as="h1"
|
|
20
|
-
variant={{ size: "2xl-title", weight: "bold" }}
|
|
21
|
-
t="error.title"
|
|
22
|
-
/>
|
|
23
|
-
<Text
|
|
24
|
-
as="h2"
|
|
25
|
-
t="error.detail"
|
|
26
|
-
variant={{ color: "contrast", weight: "semibold" }}
|
|
27
|
-
/>
|
|
28
|
-
</Stack>
|
|
29
|
-
<ErrorIcon type={type} />
|
|
30
|
-
</>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Skeleton, Stack } from "@slashid/react-primitives";
|
|
2
|
-
import { Loader } from "./flow.loader";
|
|
3
|
-
|
|
4
|
-
export function Initial() {
|
|
5
|
-
return (
|
|
6
|
-
<>
|
|
7
|
-
<Stack space="2">
|
|
8
|
-
<Skeleton width={"75%"} height={24} />
|
|
9
|
-
<Skeleton width={"50%"} height={16} />
|
|
10
|
-
</Stack>
|
|
11
|
-
<Loader />
|
|
12
|
-
</>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
2
|
-
import * as Sentry from "@sentry/react";
|
|
3
|
-
import { Stack } from "@slashid/react-primitives";
|
|
4
|
-
|
|
5
|
-
import { Loader } from "./flow.loader";
|
|
6
|
-
import type { FlowType } from "./flow.types";
|
|
7
|
-
import { Text } from "../text";
|
|
8
|
-
import { useAppContext } from "../app/app.context";
|
|
9
|
-
import { Recover } from "./flow.recover";
|
|
10
|
-
|
|
11
|
-
type SlashIDErrorMessage = { message: string };
|
|
12
|
-
class SlashIDError extends Error {
|
|
13
|
-
errors: SlashIDErrorMessage[];
|
|
14
|
-
|
|
15
|
-
constructor(errors: SlashIDErrorMessage[]) {
|
|
16
|
-
super(errors.length > 0 ? errors[0].message : "Unknown SlashID error");
|
|
17
|
-
this.errors = errors;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function isSlashIDError(e: unknown): e is SlashIDError {
|
|
22
|
-
if (
|
|
23
|
-
typeof e === "object" &&
|
|
24
|
-
e !== null &&
|
|
25
|
-
"errors" in e &&
|
|
26
|
-
Array.isArray(e.errors)
|
|
27
|
-
) {
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function ensureError(value: unknown): Error {
|
|
35
|
-
if (value instanceof Error) return value;
|
|
36
|
-
|
|
37
|
-
// special case - sometimes the core SDK throws a non-error object
|
|
38
|
-
if (isSlashIDError(value)) {
|
|
39
|
-
const error = new SlashIDError(value.errors);
|
|
40
|
-
return error;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let stringified = "[Unable to stringify the thrown value]";
|
|
44
|
-
try {
|
|
45
|
-
stringified = JSON.stringify(value);
|
|
46
|
-
} catch {
|
|
47
|
-
// ignore
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const error = new Error(
|
|
51
|
-
`This value was thrown as is, not through an Error: ${stringified}`
|
|
52
|
-
);
|
|
53
|
-
return error;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type Props = {
|
|
57
|
-
flowType: FlowType;
|
|
58
|
-
onSuccess: () => void;
|
|
59
|
-
onError: ({ error }: { error: Error }) => void;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export function Progress({ onSuccess, onError, flowType }: Props) {
|
|
63
|
-
const { sdk } = useAppContext();
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
if (!sdk) return;
|
|
67
|
-
|
|
68
|
-
async function authenticate() {
|
|
69
|
-
try {
|
|
70
|
-
await sdk?.getUserFromURL();
|
|
71
|
-
onSuccess();
|
|
72
|
-
} catch (e: unknown) {
|
|
73
|
-
const safeError = ensureError(e);
|
|
74
|
-
Sentry.captureException(safeError);
|
|
75
|
-
onError({ error: safeError });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function handlePasswordResetEvent() {
|
|
80
|
-
console.log("Password reset - input ready");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (flowType === "password-recovery") {
|
|
84
|
-
console.log("Subscribed");
|
|
85
|
-
sdk.subscribe("passwordResetReady", handlePasswordResetEvent);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
authenticate();
|
|
89
|
-
|
|
90
|
-
return () => {
|
|
91
|
-
if (flowType === "password-recovery") {
|
|
92
|
-
console.log("Cleaned up");
|
|
93
|
-
sdk.unsubscribe("passwordResetReady", handlePasswordResetEvent);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
}, [flowType, onError, onSuccess, sdk]);
|
|
97
|
-
|
|
98
|
-
if (flowType === "password-recovery") {
|
|
99
|
-
return <Recover />;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<>
|
|
104
|
-
<Stack space="0.25">
|
|
105
|
-
<Text
|
|
106
|
-
as="h1"
|
|
107
|
-
variant={{ size: "2xl-title", weight: "bold" }}
|
|
108
|
-
t="initial.title"
|
|
109
|
-
/>
|
|
110
|
-
<Text
|
|
111
|
-
variant={{ color: "contrast", weight: "semibold" }}
|
|
112
|
-
as="h2"
|
|
113
|
-
t="initial.details"
|
|
114
|
-
/>
|
|
115
|
-
</Stack>
|
|
116
|
-
<Loader />
|
|
117
|
-
</>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { InvalidPasswordSubmittedEvent } from "@slashid/slashid";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
Input,
|
|
5
|
-
Stack,
|
|
6
|
-
Text,
|
|
7
|
-
Button,
|
|
8
|
-
sprinkles,
|
|
9
|
-
} from "@slashid/react-primitives";
|
|
10
|
-
|
|
11
|
-
import * as styles from "./flow.css";
|
|
12
|
-
import { useI18n } from "../text/use-i18n";
|
|
13
|
-
import { useAppContext } from "../app/app.context";
|
|
14
|
-
import type { TranslationKeys } from "../../domain/i18n";
|
|
15
|
-
|
|
16
|
-
function getValidationI18nKey(
|
|
17
|
-
errorEvent: InvalidPasswordSubmittedEvent
|
|
18
|
-
): TranslationKeys {
|
|
19
|
-
const textKey = `authenticating.setPassword.validation.${errorEvent.failedRules[0].name}`;
|
|
20
|
-
|
|
21
|
-
// prefer the first error
|
|
22
|
-
return textKey as TranslationKeys;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function ErrorMessage({ message }: { message: string }) {
|
|
26
|
-
return <span className={styles.errorMessage}>{message}</span>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function Recover() {
|
|
30
|
-
const i18n = useI18n();
|
|
31
|
-
const { sdk } = useAppContext();
|
|
32
|
-
const [password, setPassword] = useState("");
|
|
33
|
-
const [passwordConfirm, setPasswordConfirm] = useState("");
|
|
34
|
-
const [error, setError] = useState<string | null>(null);
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const onInvalidPassword = (
|
|
38
|
-
invalidPasswordEvent: InvalidPasswordSubmittedEvent
|
|
39
|
-
) => {
|
|
40
|
-
console.log("onInvalidPassword", invalidPasswordEvent);
|
|
41
|
-
setError(i18n(getValidationI18nKey(invalidPasswordEvent)));
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
sdk?.subscribe("invalidPasswordSubmitted", onInvalidPassword);
|
|
45
|
-
|
|
46
|
-
return () => {
|
|
47
|
-
sdk?.unsubscribe("invalidPasswordSubmitted", onInvalidPassword);
|
|
48
|
-
};
|
|
49
|
-
}, [i18n, sdk, setError]);
|
|
50
|
-
|
|
51
|
-
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
|
52
|
-
event.preventDefault();
|
|
53
|
-
|
|
54
|
-
if (!sdk) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
sdk.publish("passwordSubmitted", password);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
62
|
-
setPassword(event.target.value);
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const handleConfirmPasswordChange = (
|
|
66
|
-
event: React.ChangeEvent<HTMLInputElement>
|
|
67
|
-
) => {
|
|
68
|
-
setPasswordConfirm(event.target.value);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<>
|
|
73
|
-
<Stack space="0.25">
|
|
74
|
-
<Text
|
|
75
|
-
as="h1"
|
|
76
|
-
variant={{ size: "2xl-title", weight: "bold" }}
|
|
77
|
-
t="recover.password.title"
|
|
78
|
-
/>
|
|
79
|
-
<Text
|
|
80
|
-
variant={{ color: "contrast", weight: "semibold" }}
|
|
81
|
-
as="h2"
|
|
82
|
-
t="recover.password.details"
|
|
83
|
-
/>
|
|
84
|
-
</Stack>
|
|
85
|
-
<form onSubmit={handleSubmit}>
|
|
86
|
-
<div className={styles.formInputs}>
|
|
87
|
-
{/* TODO add an error state to the input (change border color) */}
|
|
88
|
-
<Input
|
|
89
|
-
id="password-input"
|
|
90
|
-
label={i18n("recover.password.input.password.label")}
|
|
91
|
-
placeholder={i18n("recover.password.input.placeholder")}
|
|
92
|
-
name="password"
|
|
93
|
-
type="password"
|
|
94
|
-
value={password ?? ""}
|
|
95
|
-
onChange={handlePasswordChange}
|
|
96
|
-
/>
|
|
97
|
-
<Input
|
|
98
|
-
id="password-input-confirm"
|
|
99
|
-
label={i18n("recover.password.input.confirmPassword.label")}
|
|
100
|
-
placeholder={i18n("recover.password.input.placeholder")}
|
|
101
|
-
name="passwordConfirm"
|
|
102
|
-
type="password"
|
|
103
|
-
value={passwordConfirm ?? ""}
|
|
104
|
-
onChange={handleConfirmPasswordChange}
|
|
105
|
-
className={sprinkles({ marginTop: "4" })}
|
|
106
|
-
/>
|
|
107
|
-
{error && <ErrorMessage message={error} />}
|
|
108
|
-
</div>
|
|
109
|
-
|
|
110
|
-
<Button
|
|
111
|
-
type="submit"
|
|
112
|
-
variant="primary"
|
|
113
|
-
testId="sid-form-initial-submit-button"
|
|
114
|
-
disabled={!password || password !== passwordConfirm}
|
|
115
|
-
>
|
|
116
|
-
{i18n("recover.password.submit")}
|
|
117
|
-
</Button>
|
|
118
|
-
</form>
|
|
119
|
-
</>
|
|
120
|
-
);
|
|
121
|
-
}
|