Skip to content

Commit b6b7ce3

Browse files
Add init support for using FormBase for rendering the sign up form
1 parent 0d3b409 commit b6b7ce3

File tree

10 files changed

+115
-12
lines changed

10 files changed

+115
-12
lines changed

lib/ts/recipe/authRecipe/components/feature/authPage/authPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ async function buildAndSetChildProps(
508508

509509
for (const a of authComps) {
510510
if (a.type === "FULL_PAGE") {
511-
const preloadRes = await a.preloadInfoAndRunChecks(firstFactors, userContext, isSignUp);
511+
const preloadRes = await a.preloadInfoAndRunChecks(firstFactors, userContext);
512512
// We skip setting if the auth page unmounted while we were checking
513513
// if we should show any full page comps
514514
if (abort.aborted) {

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ import { useUserContext } from "../../../../../usercontext";
2626
import { useRethrowInRender } from "../../../../../utils";
2727
import Session from "../../../../session/recipe";
2828
import useSessionContext from "../../../../session/useSessionContext";
29+
import { WEBAUTHN_IS_SIGN_UP_STATE_KEY } from "../../../constants";
2930
import { ContinueWithPasskeyTheme } from "../../themes/continueWithPasskey";
3031
import SignUpTheme from "../../themes/signUp";
3132
import { defaultTranslationsWebauthn } from "../../themes/translations";
3233

3334
import type { UserContext, PartialAuthComponentProps } from "../../../../../types";
3435
import type { AuthSuccessContext } from "../../../../authRecipe/types";
3536
import type Recipe from "../../../recipe";
36-
import type { ComponentOverrideMap } from "../../../types";
37+
import type { ComponentOverrideMap, ContinueFor } from "../../../types";
3738
import type { SignUpThemeProps } from "../../../types";
3839
import type { User } from "supertokens-web-js/types";
3940

@@ -43,7 +44,8 @@ export function useChildProps(
4344
onAuthSuccess: (successContext: AuthSuccessContext) => Promise<void>,
4445
error: string | undefined,
4546
onError: (err: string) => void,
46-
userContext: UserContext
47+
userContext: UserContext,
48+
clearError: () => void
4749
): SignUpThemeProps {
4850
const session = useSessionContext();
4951
const recipeImplementation = recipe.webJSRecipe;
@@ -74,6 +76,7 @@ export function useChildProps(
7476
},
7577
error,
7678
onError,
79+
clearError,
7780
onFetchError: async (/* err: Response*/) => {
7881
// TODO: Do we need to do something else?
7982
onError("SOMETHING_WENT_WRONG_ERROR");
@@ -103,7 +106,8 @@ const SignUpFeatureInner: React.FC<
103106
props.onAuthSuccess,
104107
props.error,
105108
props.onError,
106-
userContext
109+
userContext,
110+
props.clearError
107111
)!;
108112

109113
return (
@@ -152,14 +156,22 @@ export const SignUpWithPasskeyFeature: React.FC<
152156
> = (props) => {
153157
const recipeComponentOverrides = props.useComponentOverrides();
154158

159+
const handleContinueClick = React.useCallback(
160+
(continueFor: ContinueFor) => {
161+
props.setFactorList(props.factorIds);
162+
props.userContext[WEBAUTHN_IS_SIGN_UP_STATE_KEY] = continueFor === "SIGN_UP";
163+
},
164+
[props]
165+
);
166+
155167
return (
156168
<AuthComponentWrapper recipeComponentOverrides={recipeComponentOverrides}>
157169
<FeatureWrapper
158170
useShadowDom={SuperTokens.getInstanceOrThrow().useShadowDom}
159171
defaultStore={defaultTranslationsWebauthn}>
160172
<ContinueWithPasskeyTheme
161173
{...props}
162-
continueWithPasskeyClicked={() => props.setFactorList(props.factorIds)}
174+
continueWithPasskeyClicked={handleContinueClick}
163175
config={props.recipe.config}
164176
continueFor="SIGN_UP"
165177
/>

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import SuperTokens from "../../../../../superTokens";
1717
import UserContextWrapper from "../../../../../usercontext/userContextWrapper";
1818
import { ThemeBase } from "../themeBase";
1919

20+
import { SignUpForm } from "./signUpForm";
21+
2022
import type { SignUpThemeProps } from "../../../types";
2123

2224
function SignUpTheme(props: SignUpThemeProps): JSX.Element {
@@ -27,7 +29,7 @@ function SignUpTheme(props: SignUpThemeProps): JSX.Element {
2729
return (
2830
<UserContextWrapper userContext={props.userContext}>
2931
<ThemeBase userStyles={[rootStyle, props.config.recipeRootStyle, activeStyle]}>
30-
<div></div>
32+
<SignUpForm {...props} />
3133
</ThemeBase>
3234
</UserContextWrapper>
3335
);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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 STGeneralError from "supertokens-web-js/utils/error";
17+
18+
import { withOverride } from "../../../../../components/componentOverride/withOverride";
19+
// import { useUserContext } from "../../../../../usercontext";
20+
import FormBase from "../../../../emailpassword/components/library/formBase";
21+
import { defaultValidate } from "../../../../emailpassword/validators";
22+
23+
import type { SignUpFormProps } from "../../../types";
24+
25+
export const SignUpForm = withOverride(
26+
"PasskeySignUpForm",
27+
function PasswordlessEmailForm(
28+
props: SignUpFormProps & {
29+
footer?: JSX.Element;
30+
}
31+
): JSX.Element {
32+
// const userContext = useUserContext();
33+
34+
return (
35+
<FormBase
36+
clearError={props.clearError}
37+
onFetchError={props.onFetchError}
38+
onError={props.onError}
39+
formFields={[
40+
{
41+
id: "email",
42+
label: "WEBAUTHN_SIGN_UP_LABEL",
43+
optional: false,
44+
autofocus: true,
45+
placeholder: "",
46+
autoComplete: "email",
47+
// We are using the default validator that allows any string
48+
validate: defaultValidate,
49+
},
50+
]}
51+
buttonLabel={"WEBAUTHN_EMAIL_CONTINUE_BUTTON"}
52+
onSuccess={props.onSuccess}
53+
callAPI={async (formFields) => {
54+
const email = formFields.find((field) => field.id === "email")?.value;
55+
if (email === undefined) {
56+
throw new STGeneralError("GENERAL_ERROR_EMAIL_UNDEFINED");
57+
}
58+
return null;
59+
}}
60+
validateOnBlur={false}
61+
showLabels={true}
62+
footer={props.footer}
63+
/>
64+
);
65+
}
66+
);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { defaultTranslationsCommon } from "../../../../translation/translations"
33
export const defaultTranslationsWebauthn = {
44
en: {
55
...defaultTranslationsCommon.en,
6-
// WEBAUTHN_EMAIL_CONTINUE_BUTTON: "Continue",
6+
WEBAUTHN_EMAIL_CONTINUE_BUTTON: "Continue",
7+
WEBAUTHN_SIGN_UP_LABEL: "Email",
78
// WEBAUTHN_CONTINUE_WITHOUT_PASSKEY_BUTTON: "Continue without passkey",
89
// WEBAUTHN_CREATE_A_PASSKEY_HEADER: "Create a passkey",
910
// WEBAUTHN_CONTINUE_WITH_EMAIL_SUBTEXT: "Continue with",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const WEBAUTHN_IS_SIGN_UP_STATE_KEY = "__webauthn-is-sign-up";

lib/ts/recipe/webauthn/prebuiltui.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useRecipeComponentOverrideContext } from "./componentOverrideContext";
88
import SignInWithPasskeyFeature from "./components/features/signIn";
99
import SignUpFeature, { SignUpWithPasskeyFeature } from "./components/features/signUp";
1010
import { defaultTranslationsWebauthn } from "./components/themes/translations";
11+
import { WEBAUTHN_IS_SIGN_UP_STATE_KEY } from "./constants";
1112
import WebauthnRecipe from "./recipe";
1213

1314
import type { GenericComponentOverrideMap } from "../../components/componentOverride/componentOverrideContext";
@@ -83,7 +84,8 @@ export class WebauthnPreBuiltUI extends RecipeRouter {
8384
return [
8485
{
8586
type: "FULL_PAGE",
86-
async preloadInfoAndRunChecks(firstFactors, _, isSignUp) {
87+
async preloadInfoAndRunChecks(firstFactors, userContext) {
88+
const isSignUp = userContext[WEBAUTHN_IS_SIGN_UP_STATE_KEY] || false;
8789
return {
8890
shouldDisplay:
8991
isSignUp && firstFactors.length === 1 && firstFactors.includes(FactorIds.WEBAUTHN),

lib/ts/recipe/webauthn/types.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ export type UserInput = Record<string, unknown> & {
102102
override?: {
103103
functions?: (originalImplementation: RecipeInterface) => RecipeInterface;
104104
};
105-
linkClickedScreenFeature?: WebauthnFeatureBaseConfig;
106-
mfaFeature?: WebauthnFeatureBaseConfig;
105+
signUpFeature?: NormalisedSignUpFormFeatureConfig;
107106
} & AuthRecipeModuleUserInput<GetRedirectionURLContext, PreAndPostAPIHookAction, OnHandleEventContext>;
108107

109108
export type Config = UserInput &
@@ -137,6 +136,7 @@ export type WebauthnSignUpState = {
137136
};
138137

139138
export type SignUpThemeProps = {
139+
clearError: () => void;
140140
recipeImplementation: RecipeImplementation;
141141
factorIds: string[];
142142
config: NormalisedConfig;
@@ -147,5 +147,15 @@ export type SignUpThemeProps = {
147147
userContext: UserContext;
148148
};
149149

150+
export type SignUpFormProps = {
151+
clearError: () => void;
152+
onError: (error: string) => void;
153+
onFetchError: (error: Response) => void;
154+
error: string | undefined;
155+
recipeImplementation: RecipeImplementation;
156+
config: NormalisedConfig;
157+
onSuccess?: (result: { createdNewRecipeUser: boolean; user: User }) => void;
158+
};
159+
150160
// Type to indicate what the `Continue with` button is being used for.
151161
export type ContinueFor = "SIGN_UP" | "SIGN_IN";

lib/ts/recipe/webauthn/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { normaliseAuthRecipe } from "../authRecipe/utils";
22

33
import type { Config, NormalisedConfig } from "./types";
4+
import type { FeatureBaseConfig, NormalisedBaseConfig } from "../../types";
45
import type { RecipeInterface } from "supertokens-web-js/recipe/webauthn";
56

67
export function normaliseWebauthnConfig(config: Config): NormalisedConfig {
@@ -17,6 +18,15 @@ export function normaliseWebauthnConfig(config: Config): NormalisedConfig {
1718

1819
return {
1920
...normaliseAuthRecipe(config),
21+
signUpFeature: normalisePasskeyBaseConfig(config.signUpFeature),
2022
override,
2123
};
2224
}
25+
26+
function normalisePasskeyBaseConfig<T>(config?: T & FeatureBaseConfig): T & NormalisedBaseConfig {
27+
const style = config && config.style !== undefined ? config.style : "";
28+
return {
29+
...(config as T),
30+
style,
31+
};
32+
}

lib/ts/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,7 @@ export type FullPageAuthComponent<PreloadInfoType = any> = {
437437
type: "FULL_PAGE";
438438
preloadInfoAndRunChecks: (
439439
firstFactors: string[],
440-
userContext: UserContext,
441-
isSignUp: boolean
440+
userContext: UserContext
442441
) => Promise<{ shouldDisplay: true; preloadInfo: PreloadInfoType } | { shouldDisplay: false }>;
443442
component: React.FC<FullPageAuthComponentProps<PreloadInfoType>>;
444443
};

0 commit comments

Comments
 (0)