Skip to content

Commit e45408d

Browse files
committed
Refactor authentication components: consolidate SignIn and SignUp option factories into a single AuthOptionFactory, add EmailInput type, and update imports accordingly.
1 parent 17f0a42 commit e45408d

File tree

5 files changed

+118
-349
lines changed

5 files changed

+118
-349
lines changed

packages/javascript/src/models/v2/embedded-flow-v2.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export enum EmbeddedFlowComponentType {
4444
/** Password input field with masking for sensitive data */
4545
PasswordInput = 'PASSWORD_INPUT',
4646

47+
/**
48+
* Email input field with validation for email addresses.
49+
*/
50+
EmailInput = 'EMAIL_INPUT',
51+
4752
/** Text display component for labels, headings, and messages */
4853
Text = 'TEXT',
4954

@@ -133,7 +138,7 @@ export interface EmbeddedFlowComponent {
133138
* Unique identifier for the component
134139
*/
135140
id: string;
136-
141+
137142
/**
138143
* Reference identifier for the component (e.g., field name, action ref)
139144
*/

packages/react/src/components/presentation/auth/SignIn/v2/SignInOptionFactory.tsx renamed to packages/react/src/components/presentation/auth/AuthOptionFactory.tsx

Lines changed: 110 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,31 @@ import {
2424
EmbeddedFlowComponentTypeV2 as EmbeddedFlowComponentType,
2525
EmbeddedFlowTextVariantV2 as EmbeddedFlowTextVariant,
2626
} from '@asgardeo/browser';
27-
import {createField} from '../../../../factories/FieldFactory';
28-
import Button from '../../../../primitives/Button/Button';
29-
import GoogleButton from '../../../../adapters/GoogleButton';
30-
import GitHubButton from '../../../../adapters/GitHubButton';
31-
import FacebookButton from '../../../../adapters/FacebookButton';
32-
import Typography from '../../../../primitives/Typography/Typography';
33-
import Divider from '../../../../primitives/Divider/Divider';
34-
import SmsOtpButton from '../../../../adapters/SmsOtpButton';
35-
import {TypographyVariant} from '../../../../primitives/Typography/Typography.styles';
27+
import {createField} from '../../factories/FieldFactory';
28+
import Button from '../../primitives/Button/Button';
29+
import GoogleButton from '../../adapters/GoogleButton';
30+
import GitHubButton from '../../adapters/GitHubButton';
31+
import FacebookButton from '../../adapters/FacebookButton';
32+
import Typography from '../../primitives/Typography/Typography';
33+
import Divider from '../../primitives/Divider/Divider';
34+
import SmsOtpButton from '../../adapters/SmsOtpButton';
35+
import MicrosoftButton from '../../adapters/MicrosoftButton';
36+
import LinkedInButton from '../../adapters/LinkedInButton';
37+
import SignInWithEthereumButton from '../../adapters/SignInWithEthereumButton';
38+
import {TypographyVariant} from '../../primitives/Typography/Typography.styles';
39+
40+
export type AuthType = 'signin' | 'signup';
3641

3742
/**
3843
* Get the appropriate FieldType for an input component.
3944
*/
40-
const getFieldType = (variant: string): FieldType => {
45+
const getFieldType = (variant: EmbeddedFlowComponentType): FieldType => {
4146
switch (variant) {
42-
case 'EMAIL':
47+
case EmbeddedFlowComponentType.EmailInput:
4348
return FieldType.Email;
44-
case 'PASSWORD':
49+
case EmbeddedFlowComponentType.PasswordInput:
4550
return FieldType.Password;
46-
case 'TEXT':
51+
case EmbeddedFlowComponentType.TextInput:
4752
default:
4853
return FieldType.Text;
4954
}
@@ -73,16 +78,38 @@ const getTypographyVariant = (variant: string) => {
7378
};
7479

7580
/**
76-
* Create a sign-in component from flow component configuration.
81+
* Check if a button text or action matches a social provider.
7782
*/
78-
const createSignInComponentFromFlow = (
83+
const matchesSocialProvider = (
84+
actionId: string,
85+
eventType: string,
86+
buttonText: string,
87+
provider: string,
88+
authType: AuthType,
89+
): boolean => {
90+
const providerId = `${provider}_auth`;
91+
const providerMatches = actionId === providerId || eventType === providerId;
92+
93+
// For signup, also check button text
94+
if (authType === 'signup') {
95+
return providerMatches || buttonText.toLowerCase().includes(provider);
96+
}
97+
98+
return providerMatches;
99+
};
100+
101+
/**
102+
* Create an auth component from flow component configuration.
103+
*/
104+
const createAuthComponentFromFlow = (
79105
component: EmbeddedFlowComponent,
80106
formValues: Record<string, string>,
81107
touchedFields: Record<string, boolean>,
82108
formErrors: Record<string, string>,
83109
isLoading: boolean,
84110
isFormValid: boolean,
85111
onInputChange: (name: string, value: string) => void,
112+
authType: AuthType,
86113
options: {
87114
buttonClassName?: string;
88115
inputClassName?: string;
@@ -95,37 +122,19 @@ const createSignInComponentFromFlow = (
95122
): ReactElement | null => {
96123
const key: string | number = options.key || component.id;
97124

98-
console.log('Creating sign-in component for:', component);
99-
switch (component.type) {
100-
case EmbeddedFlowComponentType.TextInput: {
101-
const identifier: string = component.ref;
102-
const value: string = formValues[identifier] || '';
103-
const isTouched: boolean = touchedFields[identifier] || false;
104-
const error: string = isTouched ? formErrors[identifier] : undefined;
105-
const fieldType: string = getFieldType('TEXT');
106-
107-
const field = createField({
108-
type: fieldType as FieldType,
109-
name: identifier,
110-
label: component.label || '',
111-
placeholder: component.placeholder || '',
112-
required: component.required || false,
113-
value,
114-
error,
115-
onChange: (newValue: string) => onInputChange(identifier, newValue),
116-
onBlur: () => options.onInputBlur?.(identifier),
117-
className: options.inputClassName,
118-
});
119-
120-
return React.cloneElement(field, {key});
121-
}
125+
if (authType === 'signin') {
126+
console.log('Creating sign-in component for:', component);
127+
}
122128

123-
case EmbeddedFlowComponentType.PasswordInput: {
129+
switch (component.type) {
130+
case EmbeddedFlowComponentType.TextInput:
131+
case EmbeddedFlowComponentType.PasswordInput:
132+
case EmbeddedFlowComponentType.EmailInput: {
124133
const identifier: string = component.ref;
125134
const value: string = formValues[identifier] || '';
126135
const isTouched: boolean = touchedFields[identifier] || false;
127136
const error: string = isTouched ? formErrors[identifier] : undefined;
128-
const fieldType: string = getFieldType('PASSWORD');
137+
const fieldType: string = getFieldType(component.type);
129138

130139
const field = createField({
131140
type: fieldType as FieldType,
@@ -159,16 +168,26 @@ const createSignInComponentFromFlow = (
159168
// Render branded social login buttons for known action IDs
160169
const actionId: string = component.id;
161170
const eventType: string = (component.eventType as string) || '';
171+
const buttonText: string = component.label || '';
162172

163-
if (actionId === 'google_auth' || eventType === 'google_auth') {
173+
if (matchesSocialProvider(actionId, eventType, buttonText, 'google', authType)) {
164174
return <GoogleButton key={key} onClick={handleClick} className={options.buttonClassName} />;
165175
}
166-
if (actionId === 'github_auth' || eventType === 'github_auth') {
176+
if (matchesSocialProvider(actionId, eventType, buttonText, 'github', authType)) {
167177
return <GitHubButton key={key} onClick={handleClick} className={options.buttonClassName} />;
168178
}
169-
if (actionId === 'facebook_auth' || eventType === 'facebook_auth') {
179+
if (matchesSocialProvider(actionId, eventType, buttonText, 'facebook', authType)) {
170180
return <FacebookButton key={key} onClick={handleClick} className={options.buttonClassName} />;
171181
}
182+
if (matchesSocialProvider(actionId, eventType, buttonText, 'microsoft', authType)) {
183+
return <MicrosoftButton key={key} onClick={handleClick} className={options.buttonClassName} />;
184+
}
185+
if (matchesSocialProvider(actionId, eventType, buttonText, 'linkedin', authType)) {
186+
return <LinkedInButton key={key} onClick={handleClick} className={options.buttonClassName} />;
187+
}
188+
if (matchesSocialProvider(actionId, eventType, buttonText, 'ethereum', authType)) {
189+
return <SignInWithEthereumButton key={key} onClick={handleClick} className={options.buttonClassName} />;
190+
}
172191
if (actionId === 'prompt_mobile' || eventType === 'prompt_mobile') {
173192
return <SmsOtpButton key={key} onClick={handleClick} className={options.buttonClassName} />;
174193
}
@@ -184,7 +203,7 @@ const createSignInComponentFromFlow = (
184203
variant={component.variant?.toLowerCase() === 'primary' ? 'solid' : 'outline'}
185204
color={component.variant?.toLowerCase() === 'primary' ? 'primary' : 'secondary'}
186205
>
187-
{component.label || 'Submit'}
206+
{buttonText || 'Submit'}
188207
</Button>
189208
);
190209
}
@@ -202,14 +221,15 @@ const createSignInComponentFromFlow = (
202221
if (component.components && component.components.length > 0) {
203222
const blockComponents = component.components
204223
.map((childComponent, index) =>
205-
createSignInComponentFromFlow(
224+
createAuthComponentFromFlow(
206225
childComponent,
207226
formValues,
208227
touchedFields,
209228
formErrors,
210229
isLoading,
211230
isFormValid,
212231
onInputChange,
232+
authType,
213233
{
214234
...options,
215235
key: childComponent.id || `${component.id}_${index}`,
@@ -223,22 +243,18 @@ const createSignInComponentFromFlow = (
223243
return null;
224244
}
225245

226-
// case EmbeddedFlowComponentType.Divider: {
227-
// return <Divider key={key} />;
228-
// }
229-
230246
default:
231247
throw new AsgardeoRuntimeError(
232248
`Unsupported component type: ${component.type}`,
233-
'SignIn-UnsupportedComponentType-001',
249+
`${authType === 'signin' ? 'SignIn' : 'SignUp'}-UnsupportedComponentType-001`,
234250
'react',
235-
'Something went wrong while rendering the sign-in component. Please try again later.',
251+
`Something went wrong while rendering the ${authType} component. Please try again later.`,
236252
);
237253
}
238254
};
239255

240256
/**
241-
* Processes an array of components and renders them as React elements.
257+
* Processes an array of components and renders them as React elements for sign-in.
242258
*/
243259
export const renderSignInComponents = (
244260
components: EmbeddedFlowComponent[],
@@ -259,14 +275,54 @@ export const renderSignInComponents = (
259275
): ReactElement[] =>
260276
components
261277
.map((component, index) =>
262-
createSignInComponentFromFlow(
278+
createAuthComponentFromFlow(
279+
component,
280+
formValues,
281+
touchedFields,
282+
formErrors,
283+
isLoading,
284+
isFormValid,
285+
onInputChange,
286+
'signin',
287+
{
288+
...options,
289+
key: component.id || index,
290+
},
291+
),
292+
)
293+
.filter(Boolean);
294+
295+
/**
296+
* Processes an array of components and renders them as React elements for sign-up.
297+
*/
298+
export const renderSignUpComponents = (
299+
components: EmbeddedFlowComponent[],
300+
formValues: Record<string, string>,
301+
touchedFields: Record<string, boolean>,
302+
formErrors: Record<string, string>,
303+
isLoading: boolean,
304+
isFormValid: boolean,
305+
onInputChange: (name: string, value: string) => void,
306+
options?: {
307+
buttonClassName?: string;
308+
inputClassName?: string;
309+
onInputBlur?: (name: string) => void;
310+
onSubmit?: (component: EmbeddedFlowComponent, data?: Record<string, any>) => void;
311+
size?: 'small' | 'medium' | 'large';
312+
variant?: any;
313+
},
314+
): ReactElement[] =>
315+
components
316+
.map((component, index) =>
317+
createAuthComponentFromFlow(
263318
component,
264319
formValues,
265320
touchedFields,
266321
formErrors,
267322
isLoading,
268323
isFormValid,
269324
onInputChange,
325+
'signup',
270326
{
271327
...options,
272328
key: component.id || index,

packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import useStyles from '../BaseSignIn.styles';
3434
import useFlow from '../../../../../contexts/Flow/useFlow';
3535
import FlowProvider from '../../../../../contexts/Flow/FlowProvider';
3636
import {FormField, useForm} from '../../../../../hooks/useForm';
37-
import {renderSignInComponents} from './SignInOptionFactory';
37+
import {renderSignInComponents} from '../../AuthOptionFactory';
3838
import {extractErrorMessage} from '../../../../../utils/v2/flowTransformer';
3939
import getAuthComponentHeadings from '../../../../../utils/v2/getAuthComponentHeadings';
4040

packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
} from '@asgardeo/browser';
2727
import {cx} from '@emotion/css';
2828
import {FC, ReactElement, ReactNode, useEffect, useState, useCallback, useRef} from 'react';
29-
import {renderSignUpComponents} from './SignUpOptionFactory';
29+
import {renderSignUpComponents} from '../../AuthOptionFactory';
3030
import {normalizeFlowResponse, extractErrorMessage} from '../../../../../utils/v2/flowTransformer';
3131
import FlowProvider from '../../../../../contexts/Flow/FlowProvider';
3232
import useFlow from '../../../../../contexts/Flow/useFlow';

0 commit comments

Comments
 (0)