diff --git a/.changeset/every-adults-study.md b/.changeset/every-adults-study.md new file mode 100644 index 00000000..810df32b --- /dev/null +++ b/.changeset/every-adults-study.md @@ -0,0 +1,7 @@ +--- +'@asgardeo/javascript': patch +'@asgardeo/react': patch +'@asgardeo/i18n': patch +--- + +Fix social button rendering issue in v2 diff --git a/packages/i18n/src/models/i18n.ts b/packages/i18n/src/models/i18n.ts index 286e02ee..43f22a59 100644 --- a/packages/i18n/src/models/i18n.ts +++ b/packages/i18n/src/models/i18n.ts @@ -38,6 +38,9 @@ export interface I18nTranslations { 'elements.buttons.multi.option.text': string; 'elements.buttons.social.text': string; + /* Display */ + 'elements.display.divider.or_separator': string; + /* Fields */ 'elements.fields.generic.placeholder': string; 'elements.fields.username.label': string; diff --git a/packages/i18n/src/translations/en-US.ts b/packages/i18n/src/translations/en-US.ts index 1ba2c182..512cbcce 100644 --- a/packages/i18n/src/translations/en-US.ts +++ b/packages/i18n/src/translations/en-US.ts @@ -41,6 +41,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': 'Continue with {connection}', 'elements.buttons.social.text': 'Continue with {connection}', + /* Display */ + 'elements.display.divider.or_separator': 'OR', + /* Fields */ 'elements.fields.generic.placeholder': 'Enter your {field}', 'elements.fields.username.label': 'Username', diff --git a/packages/i18n/src/translations/fr-FR.ts b/packages/i18n/src/translations/fr-FR.ts index e919918a..afbe0d98 100644 --- a/packages/i18n/src/translations/fr-FR.ts +++ b/packages/i18n/src/translations/fr-FR.ts @@ -41,6 +41,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': 'Continuer avec {connection}', 'elements.buttons.social.text': 'Continuer avec {connection}', + /* Display */ + 'elements.display.divider.or_separator': 'OU', + /* Fields */ 'elements.fields.generic.placeholder': 'Entrez votre {field}', 'elements.fields.username.label': "Nom d'utilisateur", diff --git a/packages/i18n/src/translations/hi-IN.ts b/packages/i18n/src/translations/hi-IN.ts index 94d9b8e8..0a3d78b4 100644 --- a/packages/i18n/src/translations/hi-IN.ts +++ b/packages/i18n/src/translations/hi-IN.ts @@ -40,6 +40,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': '{connection} के साथ जारी रखें', 'elements.buttons.social.text': '{connection} के साथ जारी रखें', + /* Display */ + 'elements.display.divider.or_separator': 'या', + /* Fields */ 'elements.fields.generic.placeholder': '{field} दर्ज करें', 'elements.fields.username.label': 'उपयोगकर्ता नाम', diff --git a/packages/i18n/src/translations/ja-JP.ts b/packages/i18n/src/translations/ja-JP.ts index 28fd557d..2ed9ad1c 100644 --- a/packages/i18n/src/translations/ja-JP.ts +++ b/packages/i18n/src/translations/ja-JP.ts @@ -41,6 +41,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': '{connection}で続行', 'elements.buttons.social.text': '{connection}で続行', + /* Display */ + 'elements.display.divider.or_separator': 'または', + /* Fields */ 'elements.fields.generic.placeholder': '{field}を入力してください', 'elements.fields.username.label': 'ユーザー名', diff --git a/packages/i18n/src/translations/pt-BR.ts b/packages/i18n/src/translations/pt-BR.ts index 12cbdc0d..45f6a87e 100644 --- a/packages/i18n/src/translations/pt-BR.ts +++ b/packages/i18n/src/translations/pt-BR.ts @@ -41,6 +41,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': 'Entrar com {connection}', 'elements.buttons.social.text': 'Entrar com {connection}', + /* Display */ + 'elements.display.divider.or_separator': 'OU', + /* Fields */ 'elements.fields.generic.placeholder': 'Digite seu {field}', 'elements.fields.username.label': 'Nome de usuário', diff --git a/packages/i18n/src/translations/pt-PT.ts b/packages/i18n/src/translations/pt-PT.ts index 555a2f9d..1e4d61bf 100644 --- a/packages/i18n/src/translations/pt-PT.ts +++ b/packages/i18n/src/translations/pt-PT.ts @@ -41,6 +41,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': 'Iniciar Sessão com {connection}', 'elements.buttons.social.text': 'Iniciar Sessão com {connection}', + /* Display */ + 'elements.display.divider.or_separator': 'OU', + /* Fields */ 'elements.fields.generic.placeholder': 'Introduza o seu {field}', 'elements.fields.username.label': 'Nome de utilizador', diff --git a/packages/i18n/src/translations/si-LK.ts b/packages/i18n/src/translations/si-LK.ts index b6474bbd..2b99adc5 100644 --- a/packages/i18n/src/translations/si-LK.ts +++ b/packages/i18n/src/translations/si-LK.ts @@ -41,6 +41,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': '{connection} සමග ඉදිරියට යන්න', 'elements.buttons.social.text': '{connection} සමග ඉදිරියට යන්න', + /* Display */ + 'elements.display.divider.or_separator': 'හෝ', + /* Fields */ 'elements.fields.generic.placeholder': 'ඔබේ {field} ඇතුලත් කරන්න', 'elements.fields.username.label': 'පරිශීලක නාමය', diff --git a/packages/i18n/src/translations/ta-IN.ts b/packages/i18n/src/translations/ta-IN.ts index 820ce4e2..ea90af0c 100644 --- a/packages/i18n/src/translations/ta-IN.ts +++ b/packages/i18n/src/translations/ta-IN.ts @@ -41,6 +41,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': '{connection} மூலம் தொடரவும்', 'elements.buttons.social.text': '{connection} மூலம் தொடரவும்', + /* Display */ + 'elements.display.divider.or_separator': 'அல்லது', + /* Fields */ 'elements.fields.generic.placeholder': '{field} உள்ளிடவும்', 'elements.fields.username.label': 'பயனர்பெயர்', diff --git a/packages/i18n/src/translations/te-IN.ts b/packages/i18n/src/translations/te-IN.ts index 8e9f10df..c651b864 100644 --- a/packages/i18n/src/translations/te-IN.ts +++ b/packages/i18n/src/translations/te-IN.ts @@ -41,6 +41,9 @@ const translations: I18nTranslations = { 'elements.buttons.multi.option.text': '{connection} తో కొనసాగించండి', 'elements.buttons.social.text': '{connection} తో కొనసాగించండి', + /* Display */ + 'elements.display.divider.or_separator': 'లేదా', + /* Fields */ 'elements.fields.generic.placeholder': 'మీ {field} ను నమోదు చేయండి', 'elements.fields.username.label': 'వినియోగదారు పేరు', diff --git a/packages/javascript/src/models/v2/embedded-flow-v2.ts b/packages/javascript/src/models/v2/embedded-flow-v2.ts index 4dbaab7c..fffc4716 100644 --- a/packages/javascript/src/models/v2/embedded-flow-v2.ts +++ b/packages/javascript/src/models/v2/embedded-flow-v2.ts @@ -44,9 +44,7 @@ export enum EmbeddedFlowComponentType { /** Password input field with masking for sensitive data */ PasswordInput = 'PASSWORD_INPUT', - /** - * Email input field with validation for email addresses. - */ + /** Email input field with validation for email addresses. */ EmailInput = 'EMAIL_INPUT', /** Text display component for labels, headings, and messages */ @@ -57,6 +55,9 @@ export enum EmbeddedFlowComponentType { /** Container block component that groups other components */ Block = 'BLOCK', + + /** Divider component for visual separation of content */ + Divider = 'DIVIDER', } /** @@ -65,14 +66,32 @@ export enum EmbeddedFlowComponentType { * @experimental This API may change in future versions */ export enum EmbeddedFlowActionVariant { + /** Primary action button with highest visual emphasis */ Primary = 'PRIMARY', + + /** Secondary action button with moderate visual emphasis */ Secondary = 'SECONDARY', + + /** Tertiary action button with minimal visual emphasis */ Tertiary = 'TERTIARY', + + /** Danger action button for destructive operations */ Danger = 'DANGER', + + /** Success action button for positive confirmations */ Success = 'SUCCESS', + + /** Info action button for informational purposes */ Info = 'INFO', + + /** Warning action button for cautionary actions */ Warning = 'WARNING', + + /** Link-styled action button */ Link = 'LINK', + + /** Social media action button (e.g., Google, Facebook) */ + Social = 'SOCIAL', } /** @@ -81,18 +100,43 @@ export enum EmbeddedFlowActionVariant { * @experimental This API may change in future versions */ export enum EmbeddedFlowTextVariant { + /** Largest heading level for main titles */ Heading1 = 'HEADING_1', + + /** Second level heading for major sections */ Heading2 = 'HEADING_2', + + /** Third level heading for subsections */ Heading3 = 'HEADING_3', + + /** Fourth level heading for minor sections */ Heading4 = 'HEADING_4', + + /** Fifth level heading for detailed sections */ Heading5 = 'HEADING_5', + + /** Smallest heading level for fine-grained sections */ Heading6 = 'HEADING_6', + + /** Primary subtitle text with larger emphasis */ Subtitle1 = 'SUBTITLE_1', + + /** Secondary subtitle text with moderate emphasis */ Subtitle2 = 'SUBTITLE_2', + + /** Primary body text for main content */ Body1 = 'BODY_1', + + /** Secondary body text for supplementary content */ Body2 = 'BODY_2', + + /** Small caption text for annotations and descriptions */ Caption = 'CAPTION', + + /** Overline text for labels and categories */ Overline = 'OVERLINE', + + /** Text styled for button labels */ ButtonText = 'BUTTON_TEXT', } @@ -102,11 +146,22 @@ export enum EmbeddedFlowTextVariant { * @experimental This API may change in future versions */ export enum EmbeddedFlowEventType { + /** Trigger an action or event */ Trigger = 'TRIGGER', + + /** Submit form data to the server */ Submit = 'SUBMIT', + + /** Navigate to a different flow step or page */ Navigate = 'NAVIGATE', + + /** Cancel the current operation */ Cancel = 'CANCEL', + + /** Reset form fields to initial state */ Reset = 'RESET', + + /** Navigate back to the previous step */ Back = 'BACK', } diff --git a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx index cf163924..a498ad15 100644 --- a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx +++ b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx @@ -23,6 +23,8 @@ import { EmbeddedFlowComponentV2 as EmbeddedFlowComponent, EmbeddedFlowComponentTypeV2 as EmbeddedFlowComponentType, EmbeddedFlowTextVariantV2 as EmbeddedFlowTextVariant, + EmbeddedFlowActionVariantV2 as EmbeddedFlowActionVariant, + EmbeddedFlowEventTypeV2 as EmbeddedFlowEventType, } from '@asgardeo/browser'; import {createField} from '../../factories/FieldFactory'; import Button from '../../primitives/Button/Button'; @@ -86,10 +88,16 @@ const matchesSocialProvider = ( buttonText: string, provider: string, authType: AuthType, + componentVariant?: string, ): boolean => { const providerId = `${provider}_auth`; const providerMatches = actionId === providerId || eventType === providerId; + // For social variant, also check button text for provider name + if (componentVariant?.toUpperCase() === EmbeddedFlowActionVariant.Social) { + return buttonText.toLowerCase().includes(provider); + } + // For signup, also check button text if (authType === 'signup') { return providerMatches || buttonText.toLowerCase().includes(provider); @@ -115,7 +123,7 @@ const createAuthComponentFromFlow = ( inputClassName?: string; key?: string | number; onInputBlur?: (name: string) => void; - onSubmit?: (component: EmbeddedFlowComponent, data?: Record) => void; + onSubmit?: (component: EmbeddedFlowComponent, data?: Record, skipValidation?: boolean) => void; size?: 'small' | 'medium' | 'large'; variant?: any; } = {}, @@ -153,6 +161,14 @@ const createAuthComponentFromFlow = ( } case EmbeddedFlowComponentType.Action: { + const actionId: string = component.id; + const eventType: string = (component.eventType as string) || ''; + const buttonText: string = component.label || ''; + const componentVariant: string = (component.variant as string) || ''; + + // Only validate on submit type events. + const shouldSkipValidation: boolean = eventType.toUpperCase() === EmbeddedFlowEventType.Trigger; + const handleClick = () => { if (options.onSubmit) { const formData: Record = {}; @@ -161,31 +177,28 @@ const createAuthComponentFromFlow = ( formData[field] = formValues[field]; } }); - options.onSubmit(component, formData); + options.onSubmit(component, formData, shouldSkipValidation); } }; // Render branded social login buttons for known action IDs - const actionId: string = component.id; - const eventType: string = (component.eventType as string) || ''; - const buttonText: string = component.label || ''; - if (matchesSocialProvider(actionId, eventType, buttonText, 'google', authType)) { + if (matchesSocialProvider(actionId, eventType, buttonText, 'google', authType, componentVariant)) { return ; } - if (matchesSocialProvider(actionId, eventType, buttonText, 'github', authType)) { + if (matchesSocialProvider(actionId, eventType, buttonText, 'github', authType, componentVariant)) { return ; } - if (matchesSocialProvider(actionId, eventType, buttonText, 'facebook', authType)) { + if (matchesSocialProvider(actionId, eventType, buttonText, 'facebook', authType, componentVariant)) { return ; } - if (matchesSocialProvider(actionId, eventType, buttonText, 'microsoft', authType)) { + if (matchesSocialProvider(actionId, eventType, buttonText, 'microsoft', authType, componentVariant)) { return ; } - if (matchesSocialProvider(actionId, eventType, buttonText, 'linkedin', authType)) { + if (matchesSocialProvider(actionId, eventType, buttonText, 'linkedin', authType, componentVariant)) { return ; } - if (matchesSocialProvider(actionId, eventType, buttonText, 'ethereum', authType)) { + if (matchesSocialProvider(actionId, eventType, buttonText, 'ethereum', authType, componentVariant)) { return ; } if (actionId === 'prompt_mobile' || eventType === 'prompt_mobile') { @@ -217,6 +230,10 @@ const createAuthComponentFromFlow = ( ); } + case EmbeddedFlowComponentType.Divider: { + return {component.label || ''}; + } + case EmbeddedFlowComponentType.Block: { if (component.components && component.components.length > 0) { const blockComponents = component.components @@ -238,7 +255,11 @@ const createAuthComponentFromFlow = ( ) .filter(Boolean); - return
{blockComponents}
; + return ( +
+ {blockComponents} +
+ ); } return null; } @@ -268,7 +289,7 @@ export const renderSignInComponents = ( buttonClassName?: string; inputClassName?: string; onInputBlur?: (name: string) => void; - onSubmit?: (component: EmbeddedFlowComponent, data?: Record) => void; + onSubmit?: (component: EmbeddedFlowComponent, data?: Record, skipValidation?: boolean) => void; size?: 'small' | 'medium' | 'large'; variant?: any; }, @@ -307,7 +328,7 @@ export const renderSignUpComponents = ( buttonClassName?: string; inputClassName?: string; onInputBlur?: (name: string) => void; - onSubmit?: (component: EmbeddedFlowComponent, data?: Record) => void; + onSubmit?: (component: EmbeddedFlowComponent, data?: Record, skipValidation?: boolean) => void; size?: 'small' | 'medium' | 'large'; variant?: any; }, diff --git a/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx b/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx index 55aa3e67..a6d36d92 100644 --- a/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx +++ b/packages/react/src/components/presentation/auth/SignIn/v2/BaseSignIn.tsx @@ -379,13 +379,21 @@ const BaseSignInContent: FC = ({ /** * Handle component submission (for buttons and actions). */ - const handleSubmit = async (component: EmbeddedFlowComponent, data?: Record): Promise => { - // Mark all fields as touched before validation - touchAllFields(); - - const validation = validateForm(); - if (!validation.isValid) { - return; + const handleSubmit = async ( + component: EmbeddedFlowComponent, + data?: Record, + skipValidation?: boolean, + ): Promise => { + // Only validate for form submit actions, skip for social/trigger actions + if (!skipValidation) { + // Mark all fields as touched before validation + touchAllFields(); + + const validation: ReturnType = validateForm(); + + if (!validation.isValid) { + return; + } } setIsSubmitting(true); diff --git a/packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx b/packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx index 1766c10e..5695dd58 100644 --- a/packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx +++ b/packages/react/src/components/presentation/auth/SignUp/v2/BaseSignUp.tsx @@ -423,11 +423,23 @@ const BaseSignUpContent: FC = ({ /** * Handle component submission (for buttons outside forms). */ - const handleSubmit = async (component: any, data?: Record) => { + const handleSubmit = async (component: any, data?: Record, skipValidation?: boolean) => { if (!currentFlow) { return; } + // Only validate for form submit actions, skip for social/trigger actions + if (!skipValidation) { + // Mark all fields as touched before validation + touchAllFields(); + + const validation: ReturnType = validateForm(); + + if (!validation.isValid) { + return; + } + } + setIsLoading(true); clearMessages();