Skip to content

Commit 9ecb812

Browse files
committed
chore: Code review fixes
1 parent 7cf150d commit 9ecb812

File tree

7 files changed

+286
-237
lines changed

7 files changed

+286
-237
lines changed

lib/ts/recipe/webauthn/components/features/mfa/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const useFeatureReducer = (): [WebAuthnMFAState, React.Dispatch<WebAuthnM
5252
case "setError":
5353
return {
5454
...oldState,
55+
loaded: true,
5556
error: action.error,
5657
accessDenied: action.accessDenied || false,
5758
};
@@ -72,7 +73,7 @@ export const useFeatureReducer = (): [WebAuthnMFAState, React.Dispatch<WebAuthnM
7273
deviceSupported: false,
7374
loaded: false,
7475
showBackButton: true,
75-
email: "",
76+
email: undefined,
7677
accessDenied: false,
7778
}
7879
);
@@ -239,7 +240,8 @@ export const MFAFeature: React.FC<
239240
<ComponentOverrideContext.Provider value={recipeComponentOverrides}>
240241
<FeatureWrapper
241242
useShadowDom={SuperTokens.getInstanceOrThrow().useShadowDom}
242-
defaultStore={defaultTranslationsWebauthn}>
243+
defaultStore={defaultTranslationsWebauthn}
244+
>
243245
<MFAFeatureInner {...props} />
244246
</FeatureWrapper>
245247
</ComponentOverrideContext.Provider>
@@ -314,7 +316,6 @@ function useOnLoad(
314316

315317
const onLoad = React.useCallback(
316318
async (mfaInfo: Awaited<ReturnType<typeof fetchMFAInfo>>) => {
317-
console.log("onLoad", mfaInfo);
318319
let error: string | undefined = undefined;
319320
const errorQueryParam = getQueryParams("error");
320321
const doSetup = getQueryParams("setup");
Lines changed: 21 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
import * as React from "react";
2-
import STGeneralError from "supertokens-web-js/utils/error";
3-
import { Fragment } from "react";
4-
import { defaultEmailValidator } from "../../../../emailpassword/validators";
5-
import Button from "../../../../emailpassword/components/library/button";
6-
import SpinnerIcon from "../../../../../components/assets/spinnerIcon";
7-
import PasskeyIcon from "../../../../../components/assets/passkeyIcon";
82
import { WebAuthnMFAProps } from "../../../types";
93
import SuperTokens from "../../../../../superTokens";
10-
import FormBase from "../../../../emailpassword/components/library/formBase";
11-
import { Label } from "../../../../emailpassword/components/library";
12-
import { useTranslation } from "../../../../../translation/translationContext";
134
import UserContextWrapper from "../../../../../usercontext/userContextWrapper";
14-
import GeneralError from "../../../../emailpassword/components/library/generalError";
5+
import { useTranslation } from "../../../../../translation/translationContext";
6+
import { AccessDeniedScreen } from "../../../../session/prebuiltui";
157
import { ThemeBase } from "../themeBase";
16-
import BackButton from "../../../../emailpassword/components/library/backButton";
178
import { SuperTokensBranding } from "../../../../../components/SuperTokensBranding";
18-
import { withOverride } from "../../../../../components/componentOverride/withOverride";
19-
import { PasskeyFeatureBlockList } from "../signUp/featureBlocks";
20-
import { PasskeyNotSupportedError } from "../error/passkeyNotSupportedError";
9+
import { WebauthnMFALoadingScreen } from "./loadingScreen";
10+
import { WebauthnMFASignIn } from "./signIn";
11+
import { WebauthnMFASignUp } from "./signUp";
12+
import { WebauthnMFASignUpConfirmation } from "./signUpConfirmation";
13+
14+
export { WebauthnMFALoadingScreen, WebauthnMFASignIn, WebauthnMFASignUp, WebauthnMFASignUpConfirmation };
15+
export type { MFASignInProps } from "./signIn";
16+
export type { MFASignUpProps } from "./signUp";
17+
export type { MFASignUpConfirmationProps } from "./signUpConfirmation";
2118

2219
export enum MFAScreens {
2320
SignIn,
@@ -43,6 +40,7 @@ export function MFATheme(props: WebAuthnMFAProps): JSX.Element {
4340
const { onBackButtonClicked, onSignIn } = props;
4441
const [activeScreen, setActiveScreen] = React.useState<MFAScreens>(MFAScreens.SignIn);
4542
const [signUpEmail, setSignUpEmail] = React.useState<string>("");
43+
const t = useTranslation();
4644

4745
const onRegisterPasskeyClick = React.useCallback(() => {
4846
if (props.featureState.email) {
@@ -88,6 +86,15 @@ export function MFATheme(props: WebAuthnMFAProps): JSX.Element {
8886
return <WebauthnMFALoadingScreen />;
8987
}
9088

89+
if (props.featureState.accessDenied) {
90+
return (
91+
<AccessDeniedScreen
92+
useShadowDom={false /* We set this to false, because we are already inside a shadowDom (if required) */}
93+
error={t(props.featureState.error!)}
94+
/>
95+
);
96+
}
97+
9198
return (
9299
<div data-supertokens="container webauthn-mfa">
93100
<div data-supertokens="row">
@@ -123,220 +130,3 @@ export function MFATheme(props: WebAuthnMFAProps): JSX.Element {
123130
</div>
124131
);
125132
}
126-
127-
export const WebauthnMFALoadingScreen = withOverride("WebauthnMFALoadingScreen", function WebauthnMFALoadingScreen() {
128-
return (
129-
<div data-supertokens="container delayedRender pwless-mfa loadingScreen">
130-
<div data-supertokens="row">
131-
<div data-supertokens="spinner delayedRender">
132-
<SpinnerIcon />
133-
</div>
134-
</div>
135-
</div>
136-
);
137-
});
138-
139-
type MFASignInProps = {
140-
onBackButtonClicked?: () => void;
141-
onSignIn: () => Promise<void>;
142-
onRegisterPasskeyClick: () => void;
143-
error: string | undefined;
144-
deviceSupported: boolean;
145-
};
146-
147-
export const WebauthnMFASignIn = withOverride(
148-
"WebauthnMFASignIn",
149-
function WebauthnMFASignIn(props: MFASignInProps): JSX.Element {
150-
const t = useTranslation();
151-
const [isLoading, setIsLoading] = React.useState(false);
152-
153-
const onClick = React.useCallback(async () => {
154-
setIsLoading(true);
155-
await props.onSignIn();
156-
setIsLoading(false);
157-
}, [props]);
158-
159-
return (
160-
<Fragment>
161-
{props.onBackButtonClicked ? (
162-
<div data-supertokens="headerTitle withBackButton webauthn-mfa">
163-
<BackButton onClick={props.onBackButtonClicked} />
164-
{t("WEBAUTHN_MFA_SIGN_IN_HEADER_TITLE")}
165-
<span data-supertokens="backButtonPlaceholder backButtonCommon">
166-
{/* empty span for spacing the back button */}
167-
</span>
168-
</div>
169-
) : (
170-
<div data-supertokens="headerTitle ebauthn-mfa">{t("WEBAUTHN_MFA_SIGN_IN_HEADER_TITLE")}</div>
171-
)}
172-
<div data-supertokens="headerSubtitle secondaryText">{t("WEBAUTHN_MFA_SIGN_IN_HEADER_SUBTITLE")}</div>
173-
<Button
174-
disabled={!props.deviceSupported || isLoading}
175-
isLoading={isLoading}
176-
type="button"
177-
onClick={onClick}
178-
label="WEBAUTHN_EMAIL_CONTINUE_BUTTON"
179-
isGreyedOut={!props.deviceSupported}
180-
icon={PasskeyIcon}
181-
/>
182-
{props.error !== undefined && <GeneralError error={props.error} />}
183-
<div data-supertokens="passkeyMfaSignInDivider">
184-
<div data-supertokens="divider" />
185-
<span>or</span>
186-
<div data-supertokens="divider" />
187-
</div>
188-
<div data-supertokens="headerSubtitle secondaryText">
189-
<span data-supertokens="link" onClick={props.onRegisterPasskeyClick}>
190-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_LINK")}
191-
</span>
192-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_SUBTITLE")}
193-
</div>
194-
{!props.deviceSupported && <PasskeyNotSupportedError />}
195-
</Fragment>
196-
);
197-
}
198-
);
199-
200-
type MFASignUpProps = {
201-
onContinueClick: (email: string) => void;
202-
clearError: () => void;
203-
email?: string;
204-
error?: string;
205-
onError: (error: string) => void;
206-
onFetchError?: (error: Response) => void;
207-
onRecoverAccountClick: () => void;
208-
onBackButtonClicked: () => void;
209-
};
210-
211-
export const WebauthnMFASignUp = withOverride(
212-
"WebauthnMFASignUp",
213-
function WebauthnMFASignUp(props: MFASignUpProps): JSX.Element {
214-
const t = useTranslation();
215-
216-
const onSuccess = React.useCallback(
217-
({ email }: { email: string }) => {
218-
props.onContinueClick(email);
219-
},
220-
[props.onContinueClick]
221-
);
222-
223-
return (
224-
<Fragment>
225-
<div data-supertokens="headerTitle withBackButton webauthn-mfa">
226-
<BackButton onClick={props.onBackButtonClicked} />
227-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_LINK")}
228-
<span data-supertokens="backButtonPlaceholder backButtonCommon">
229-
{/* empty span for spacing the back button */}
230-
</span>
231-
</div>
232-
{props.error !== undefined && <GeneralError error={props.error} />}
233-
<div data-supertokens="signUpFormInnerContainer">
234-
<div data-supertokens="cautionMessage">{t("WEBAUTHN_SIGN_UP_CAUTION_MESSAGE_LABEL")}</div>
235-
<FormBase
236-
clearError={props.clearError}
237-
onFetchError={props.onFetchError}
238-
onError={props.onError}
239-
formFields={[
240-
{
241-
id: "email",
242-
label: "",
243-
labelComponent: (
244-
<div data-supertokens="formLabelWithLinkWrapper">
245-
<Label value={"WEBAUTHN_SIGN_UP_LABEL"} data-supertokens="emailInputLabel" />
246-
<a
247-
onClick={props.onRecoverAccountClick}
248-
data-supertokens="link linkButton formLabelLinkBtn recoverAccountTrigger">
249-
{t("WEBAUTHN_RECOVER_ACCOUNT_LABEL")}
250-
</a>
251-
</div>
252-
),
253-
optional: false,
254-
autofocus: true,
255-
placeholder: "",
256-
getDefaultValue: () => props.email as string,
257-
autoComplete: "email",
258-
// We are using the default validator that allows any string
259-
validate: defaultEmailValidator,
260-
},
261-
]}
262-
buttonLabel={"WEBAUTHN_EMAIL_CONTINUE_BUTTON"}
263-
onSuccess={onSuccess}
264-
callAPI={async (formFields) => {
265-
const email = formFields.find((field) => field.id === "email")?.value;
266-
if (email === undefined) {
267-
throw new STGeneralError("GENERAL_ERROR_EMAIL_UNDEFINED");
268-
}
269-
270-
if (email === "") {
271-
throw new STGeneralError("EMAIL_INPUT_NOT_POPULATED_ERROR");
272-
}
273-
274-
// We do not want the form to make the API call since we have
275-
// an intermediary step here so we will just mock an OK status
276-
// to render the next step.
277-
return {
278-
status: "OK",
279-
email,
280-
};
281-
}}
282-
validateOnBlur={false}
283-
showLabels={true}
284-
footer={undefined}
285-
/>
286-
</div>
287-
</Fragment>
288-
);
289-
}
290-
);
291-
292-
type MFASignUpConfirmationProps = {
293-
onSignUp: (email: string) => Promise<void>;
294-
onBackButtonClicked: () => void;
295-
email: string;
296-
error?: string;
297-
};
298-
299-
export const WebauthnMFASignUpConfirmation = withOverride(
300-
"WebauthnMFASignUpConfirmation",
301-
function WebauthnMFASignUpConfirmation(props: MFASignUpConfirmationProps): JSX.Element {
302-
const t = useTranslation();
303-
const [isLoading, setIsLoading] = React.useState(false);
304-
305-
const onClick = React.useCallback(async () => {
306-
setIsLoading(true);
307-
await props.onSignUp(props.email);
308-
setIsLoading(false);
309-
}, [props]);
310-
311-
return (
312-
<Fragment>
313-
<div data-supertokens="headerTitle withBackButton webauthn-mfa">
314-
<BackButton onClick={props.onBackButtonClicked} />
315-
{t("WEBAUTHN_MFA_REGISTER_PASSKEY_LINK")}
316-
<span data-supertokens="backButtonPlaceholder backButtonCommon">
317-
{/* empty span for spacing the back button */}
318-
</span>
319-
</div>
320-
<div data-supertokens="divider" />
321-
<div data-supertokens="passkeyConfirmationContainer">
322-
<div data-supertokens="passkeyConfirmationEmailContainer">
323-
<div data-supertokens="continueWithLabel">{t("WEBAUTHN_CONTINUE_WITH_EMAIL_SUBTEXT")}</div>
324-
<div data-supertokens="enteredEmailId">{props.email}</div>
325-
</div>
326-
<PasskeyFeatureBlockList />
327-
{props.error !== undefined && <GeneralError error={props.error} />}
328-
<div data-supertokens="passkeyConfirmationFooter">
329-
<Button
330-
disabled={isLoading}
331-
isLoading={isLoading}
332-
type="button"
333-
onClick={onClick}
334-
label="WEBAUTHN_EMAIL_CONTINUE_BUTTON"
335-
isGreyedOut={false}
336-
/>
337-
</div>
338-
</div>
339-
</Fragment>
340-
);
341-
}
342-
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import SpinnerIcon from "../../../../../components/assets/spinnerIcon";
2+
import { withOverride } from "../../../../../components/componentOverride/withOverride";
3+
4+
export const WebauthnMFALoadingScreen = withOverride("WebauthnMFALoadingScreen", function WebauthnMFALoadingScreen() {
5+
return (
6+
<div data-supertokens="container delayedRender pwless-mfa loadingScreen">
7+
<div data-supertokens="row">
8+
<div data-supertokens="spinner delayedRender">
9+
<SpinnerIcon />
10+
</div>
11+
</div>
12+
</div>
13+
);
14+
});
15+

0 commit comments

Comments
 (0)