Skip to content

Commit ffec16d

Browse files
Add support for Recover Account form component in webauthn
1 parent de98ff4 commit ffec16d

File tree

6 files changed

+142
-2
lines changed

6 files changed

+142
-2
lines changed

lib/ts/recipe/webauthn/components/themes/signUp/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function PasskeySignUpTheme(props: SignUpThemeProps): JSX.Element {
4949
props.factorIds.length > 1 ? "multiFactor" : "singleFactor"
5050
}`}>
5151
<div data-supertokens="row">
52-
{activeScreen !== SignUpScreen.Error && (
52+
{![SignUpScreen.Error, SignUpScreen.RecoverAccount].includes(activeScreen) && (
5353
<AuthPageHeader
5454
factorIds={props.factorIds}
5555
isSignUp={true}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved.
2+
*
3+
* This software is licensed under the Apache License, Version 2.0 (the
4+
* "License") as published by the Apache Software Foundation.
5+
*
6+
* You may not use this file except in compliance with the License. You may
7+
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
* License for the specific language governing permissions and limitations
13+
* under the License.
14+
*/
15+
16+
import { useState } from "react";
17+
import STGeneralError from "supertokens-web-js/lib/build/error";
18+
19+
import { withOverride } from "../../../../../components/componentOverride/withOverride";
20+
import { useTranslation } from "../../../../../translation/translationContext";
21+
import { Label } from "../../../../emailpassword/components/library";
22+
import BackButton from "../../../../emailpassword/components/library/backButton";
23+
import FormBase from "../../../../emailpassword/components/library/formBase";
24+
import { defaultEmailValidator } from "../../../../emailpassword/validators";
25+
26+
import type { RecoverFromProps } from "../../../types";
27+
28+
export const PasskeyRecoverAccountFormInner = withOverride(
29+
"PasskeyRecoverAccountFormInner",
30+
(props: RecoverFromProps): JSX.Element => {
31+
const [, setError] = useState<string | undefined>(undefined);
32+
33+
return (
34+
<FormBase
35+
clearError={() => setError(undefined)}
36+
onFetchError={() => setError("Failed to fetch")}
37+
onError={(error) => setError(error)}
38+
formFields={[
39+
{
40+
id: "email",
41+
label: "",
42+
labelComponent: (
43+
<div data-supertokens="formLabelWithLinkWrapper">
44+
<Label value={"WEBAUTHN_SIGN_UP_LABEL"} data-supertokens="emailInputLabel" />
45+
</div>
46+
),
47+
optional: false,
48+
autofocus: true,
49+
placeholder: "",
50+
autoComplete: "email",
51+
// We are using the default validator that allows any string
52+
validate: defaultEmailValidator,
53+
},
54+
]}
55+
buttonLabel={"WEBAUTHN_EMAIL_CONTINUE_BUTTON"}
56+
onSuccess={props.onSuccess}
57+
callAPI={async (formFields) => {
58+
const email = formFields.find((field) => field.id === "email")?.value;
59+
if (email === undefined) {
60+
throw new STGeneralError("GENERAL_ERROR_EMAIL_UNDEFINED");
61+
}
62+
// TODO: Define code to make the API call to send reset email.
63+
return {
64+
status: "OK",
65+
email,
66+
};
67+
}}
68+
validateOnBlur={false}
69+
showLabels={true}
70+
/>
71+
);
72+
}
73+
);
74+
75+
export const PasskeyRecoverAccountForm = withOverride(
76+
"PasskeyRecoverAccountForm",
77+
(props: RecoverFromProps): JSX.Element => {
78+
const t = useTranslation();
79+
80+
return (
81+
<div data-supertokens="passkeyRecoverAccountFormContainer">
82+
<div data-supertokens="passkeyRecoverAccountFormHeaderWrapper">
83+
<div data-supertokens="passkeyRecoverAccountFormHeader headerTitle withBackButton">
84+
<BackButton onClick={props.onBackClick} />
85+
{t("WEBAUTHN_RECOVER_ACCOUNT_LABEL")}
86+
<span data-supertokens="backButtonPlaceholder backButtonCommon">
87+
{/* empty span for spacing the back button */}
88+
</span>
89+
</div>
90+
<div data-supertokens="passkeyRecoverAccountFormSubHeader">
91+
{t("WEBAUTHN_RECOVER_ACCOUNT_SUBHEADER_LABEL")}
92+
</div>
93+
</div>
94+
<PasskeyRecoverAccountFormInner {...props} />
95+
</div>
96+
);
97+
}
98+
);

lib/ts/recipe/webauthn/components/themes/signUp/signUpForm.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { defaultEmailValidator } from "../../../../emailpassword/validators";
2626

2727
import { PasskeyConfirmation } from "./confirmation";
2828
import { ContinueWithoutPasskey } from "./continueWithoutPasskey";
29+
import { PasskeyRecoverAccountForm } from "./recoverAccountForm";
2930
import { SignUpSomethingWentWrong } from "./somethingWentWrong";
3031

3132
import type { APIFormField } from "../../../../../types";
@@ -35,6 +36,8 @@ export enum SignUpScreen {
3536
SignUpForm,
3637
PasskeyConfirmation,
3738
Error,
39+
RecoverAccount,
40+
RecoverEmailSent,
3841
}
3942

4043
export const SignUpFormInner = withOverride(
@@ -43,6 +46,7 @@ export const SignUpFormInner = withOverride(
4346
props: SignUpFormProps & {
4447
footer?: JSX.Element;
4548
onContinueClick: (params: ContinueOnSuccessParams) => void;
49+
setActiveScreen: React.Dispatch<React.SetStateAction<SignUpScreen>>;
4650
}
4751
): JSX.Element {
4852
const t = useTranslation();
@@ -68,7 +72,7 @@ export const SignUpFormInner = withOverride(
6872
<div data-supertokens="formLabelWithLinkWrapper">
6973
<Label value={"WEBAUTHN_SIGN_UP_LABEL"} data-supertokens="emailInputLabel" />
7074
<a
71-
onClick={() => alert("That is not defined yet!")}
75+
onClick={() => props.setActiveScreen(SignUpScreen.RecoverAccount)}
7276
data-supertokens="link linkButton formLabelLinkBtn recoverAccountTrigger">
7377
{t("WEBAUTHN_RECOVER_ACCOUNT_LABEL")}
7478
</a>
@@ -162,6 +166,14 @@ export const SignUpForm = (
162166
});
163167
}, [callAPI, props]);
164168

169+
const onRecoverAccountFormSuccess = () => {
170+
props.setActiveScreen(SignUpScreen.RecoverEmailSent);
171+
};
172+
173+
const onRecoverAccountBackClick = () => {
174+
props.setActiveScreen(SignUpScreen.SignUpForm);
175+
};
176+
165177
return props.activeScreen === SignUpScreen.SignUpForm ? (
166178
<SignUpFormInner {...props} onContinueClick={onContinueClickCallback} />
167179
) : props.activeScreen === SignUpScreen.PasskeyConfirmation ? (
@@ -174,5 +186,7 @@ export const SignUpForm = (
174186
/>
175187
) : props.activeScreen === SignUpScreen.Error ? (
176188
<SignUpSomethingWentWrong onClick={() => props.setActiveScreen(SignUpScreen.SignUpForm)} />
189+
) : props.activeScreen === SignUpScreen.RecoverAccount ? (
190+
<PasskeyRecoverAccountForm onSuccess={onRecoverAccountFormSuccess} onBackClick={onRecoverAccountBackClick} />
177191
) : null;
178192
};

lib/ts/recipe/webauthn/components/themes/styles.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,25 @@
170170
[data-supertokens~="passkeySignInContainer"] {
171171
margin-bottom: 6px;
172172
}
173+
174+
[data-supertokens~="passkeyRecoverAccountFormContainer"]
175+
[data-supertokens~="passkeyRecoverAccountFormHeaderWrapper"]
176+
[data-supertokens~="passkeyRecoverAccountFormHeader"] {
177+
padding-top: 10px;
178+
}
179+
180+
[data-supertokens~="passkeyRecoverAccountFormContainer"]
181+
[data-supertokens~="passkeyRecoverAccountFormHeaderWrapper"]
182+
[data-supertokens~="passkeyRecoverAccountFormSubHeader"] {
183+
width: 70%;
184+
margin: 0 auto;
185+
font-family: Arial;
186+
font-size: 14px;
187+
font-weight: 400;
188+
line-height: 16.1px;
189+
text-align: center;
190+
text-underline-position: from-font;
191+
text-decoration-skip-ink: none;
192+
color: #808080;
193+
margin-bottom: 20px;
194+
}

lib/ts/recipe/webauthn/components/themes/translations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const defaultTranslationsWebauthn = {
66
WEBAUTHN_EMAIL_CONTINUE_BUTTON: "CONTINUE",
77
WEBAUTHN_SIGN_UP_LABEL: "Email",
88
WEBAUTHN_RECOVER_ACCOUNT_LABEL: "Recover Account",
9+
WEBAUTHN_RECOVER_ACCOUNT_SUBHEADER_LABEL: "We will send you an email to recover your account.",
910
WEBAUTHN_CONTINUE_WITHOUT_PASSKEY_BUTTON: "Continue without passkey",
1011
WEBAUTHN_CREATE_A_PASSKEY_HEADER: "Create a passkey",
1112
WEBAUTHN_CONTINUE_WITH_EMAIL_SUBTEXT: "Continue with",

lib/ts/recipe/webauthn/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,10 @@ export type FeatureBlockDetailProps = {
175175
icon: JSX.Element;
176176
};
177177

178+
export type RecoverFromProps = {
179+
onSuccess: () => void;
180+
onBackClick: () => void;
181+
};
182+
178183
// Type to indicate what the `Continue with` button is being used for.
179184
export type ContinueFor = "SIGN_UP" | "SIGN_IN";

0 commit comments

Comments
 (0)