@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.
Files changed (37) hide show
  1. package/cli.js +4 -4
  2. package/dist/_astro/app.f7d04798.js +39 -0
  3. package/dist/_astro/client.a5ffbec0.js +24 -0
  4. package/dist/_astro/index.03be2d59.js +9 -0
  5. package/dist/_astro/index.2dd0ad08.css +1 -0
  6. package/dist/_astro/index.cc62d81f.css +1 -0
  7. package/dist/favicon.png +0 -0
  8. package/dist/index.html +7 -0
  9. package/package.json +2 -2
  10. package/src/components/README.md +0 -3
  11. package/src/components/app/app.component.tsx +0 -83
  12. package/src/components/app/app.context.tsx +0 -41
  13. package/src/components/app/app.css.ts +0 -15
  14. package/src/components/app/index.ts +0 -1
  15. package/src/components/card/card.component.tsx +0 -42
  16. package/src/components/card/card.css.ts +0 -40
  17. package/src/components/card/index.ts +0 -1
  18. package/src/components/flow/flow.component.tsx +0 -99
  19. package/src/components/flow/flow.css.ts +0 -20
  20. package/src/components/flow/flow.domain.ts +0 -18
  21. package/src/components/flow/flow.error.tsx +0 -32
  22. package/src/components/flow/flow.initial.tsx +0 -14
  23. package/src/components/flow/flow.loader.tsx +0 -9
  24. package/src/components/flow/flow.progress.tsx +0 -119
  25. package/src/components/flow/flow.recover.tsx +0 -121
  26. package/src/components/flow/flow.success.tsx +0 -40
  27. package/src/components/flow/flow.types.ts +0 -48
  28. package/src/components/flow/index.ts +0 -3
  29. package/src/components/text/index.ts +0 -1
  30. package/src/components/text/text.component.tsx +0 -8
  31. package/src/components/text/use-i18n.ts +0 -13
  32. package/src/config.ts +0 -7
  33. package/src/domain/i18n.ts +0 -115
  34. package/src/env.d.ts +0 -1
  35. package/src/layouts/README.md +0 -3
  36. package/src/layouts/page.astro +0 -33
  37. package/src/pages/index.astro +0 -8
@@ -1,3 +0,0 @@
1
- # Components
2
-
3
- This folder should only contain components meant to render server side and then hydrate on the client side.
@@ -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,9 +0,0 @@
1
- import { Circle, Spinner } from "@slashid/react-primitives";
2
-
3
- export function Loader() {
4
- return (
5
- <Circle>
6
- <Spinner />
7
- </Circle>
8
- );
9
- }
@@ -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
- }