diff --git a/packages/javascript/src/models/extensions/components.ts b/packages/javascript/src/models/extensions/components.ts index 94ccdfe..92bb440 100644 --- a/packages/javascript/src/models/extensions/components.ts +++ b/packages/javascript/src/models/extensions/components.ts @@ -31,6 +31,7 @@ export interface ComponentRenderContext { isFormValid: boolean; isLoading: boolean; meta?: FlowMetadataResponse | null; + resetForm?: () => void; onInputBlur?: (name: string) => void; onInputChange: (name: string, value: string) => void; onSubmit?: (component: EmbeddedFlowComponent, data?: Record, skipValidation?: boolean) => void; diff --git a/packages/react/src/components/presentation/auth/AcceptInvite/BaseAcceptInvite.tsx b/packages/react/src/components/presentation/auth/AcceptInvite/BaseAcceptInvite.tsx index 39cf2c7..0d2b2cf 100644 --- a/packages/react/src/components/presentation/auth/AcceptInvite/BaseAcceptInvite.tsx +++ b/packages/react/src/components/presentation/auth/AcceptInvite/BaseAcceptInvite.tsx @@ -660,6 +660,15 @@ const BaseAcceptInvite: FC = ({ ], ); + /** + * reset form values, errors, and touched fields to initial state + */ + const resetForm = useCallback(() => { + setFormValues({}); + setFormErrors({}); + setTouchedFields({}); + }, []); + /** * Validate invite token on component mount. */ @@ -879,6 +888,7 @@ const BaseAcceptInvite: FC = ({ formErrors, isLoading, isFormValid, + resetForm, handleInputChange, { _customRenderers: customRenderers, diff --git a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx index 4be53ec..1dd9454 100644 --- a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx +++ b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx @@ -187,6 +187,7 @@ const createAuthComponentFromFlow = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, authType: AuthType, options: { @@ -230,6 +231,7 @@ const createAuthComponentFromFlow = ( isFormValid, isLoading, meta: options.meta, + resetForm, onInputBlur: options.onInputBlur, onInputChange, onSubmit: options.onSubmit, @@ -304,7 +306,17 @@ const createAuthComponentFromFlow = ( const componentVariant: string = component.variant || ''; // Only validate on submit type events. - const shouldSkipValidation: boolean = eventType.toUpperCase() === EmbeddedFlowEventType.Trigger; + const shouldSkipValidation: boolean = eventType.toUpperCase() !== EmbeddedFlowEventType.Submit; + + // Determine the button type based on the event type. Default to 'submit' if not recognized. + const buttonTypeEnum = { + [EmbeddedFlowEventType.Submit]: 'submit', + [EmbeddedFlowEventType.Reset]: 'reset', + [EmbeddedFlowEventType.Cancel]: 'button', + [EmbeddedFlowEventType.Trigger]: 'button', + [EmbeddedFlowEventType.Back]: 'button', + }; + const buttonType: HTMLButtonElement['type'] = buttonTypeEnum[eventType.toUpperCase()] ?? 'submit'; const handleClick = (): any => { if (options.onSubmit) { @@ -342,7 +354,13 @@ const createAuthComponentFromFlow = ( formData['consent_decisions'] = JSON.stringify(decisions); } - options.onSubmit(component, formData, shouldSkipValidation); + // For submit events, pass the form data to the onSubmit callback. For other event types, reset the form and call onSubmit with an empty data object. + if (eventType.toUpperCase() === EmbeddedFlowEventType.Submit) { + options.onSubmit(component, formData, shouldSkipValidation); + } else { + resetForm(); + options.onSubmit(component, {}, shouldSkipValidation); + } } }; @@ -415,6 +433,7 @@ const createAuthComponentFromFlow = ( color={component.variant?.toLowerCase() === 'primary' ? 'primary' : 'secondary'} startIcon={startIconEl} endIcon={endIconEl} + type={buttonType} > {buttonText || 'Submit'} @@ -520,6 +539,7 @@ const createAuthComponentFromFlow = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, authType, { @@ -672,6 +692,7 @@ const createAuthComponentFromFlow = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, authType, { @@ -736,6 +757,7 @@ export const renderSignInComponents = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, options?: { /** @internal */ @@ -767,6 +789,7 @@ export const renderSignInComponents = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, 'signin', { @@ -787,6 +810,7 @@ export const renderSignUpComponents = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, options?: { /** @internal */ @@ -816,6 +840,7 @@ export const renderSignUpComponents = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, 'signup', { @@ -836,6 +861,7 @@ export const renderRecoveryComponents = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, options?: { /** @internal */ @@ -867,6 +893,7 @@ export const renderRecoveryComponents = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, 'recovery', { @@ -888,6 +915,7 @@ export const renderInviteUserComponents = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, options?: { /** @internal */ @@ -923,6 +951,7 @@ export const renderInviteUserComponents = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, 'signup', { diff --git a/packages/react/src/components/presentation/auth/InviteUser/BaseInviteUser.tsx b/packages/react/src/components/presentation/auth/InviteUser/BaseInviteUser.tsx index f9329e0..b73b1dd 100644 --- a/packages/react/src/components/presentation/auth/InviteUser/BaseInviteUser.tsx +++ b/packages/react/src/components/presentation/auth/InviteUser/BaseInviteUser.tsx @@ -553,6 +553,17 @@ const BaseInviteUser: FC = ({ initializationAttemptedRef.current = false; }, []); + /** + * reset form values, errors, and touched fields to initial state + */ + const resetForm = useCallback(() => { + setFormValues({}); + setFormErrors({}); + setTouchedFields({}); + setApiError(null); + setIsFormValid(true); + }, []); + /** * Initialize the flow on component mount. */ @@ -647,6 +658,7 @@ const BaseInviteUser: FC = ({ formErrors, isLoading, isFormValid, + resetForm, handleInputChange, { _customRenderers: customRenderers, diff --git a/packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx b/packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx index 95c6d8d..db1e7c0 100644 --- a/packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx +++ b/packages/react/src/components/presentation/auth/Recovery/BaseRecovery.tsx @@ -398,6 +398,7 @@ const BaseRecoveryContent: FC = ({ formErrors, isLoading, isFormValid, + resetForm, handleInputChange, { _customRenderers: customRenderers, diff --git a/packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx b/packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx index d8ca32d..fd3f25f 100644 --- a/packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx +++ b/packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx @@ -357,6 +357,7 @@ const BaseSignInContent: FC = ({ setTouched: setFormTouched, validateForm, touchAllFields, + reset: resetForm, } = form; /** @@ -471,6 +472,7 @@ const BaseSignInContent: FC = ({ formErrors, isLoading, isFormValid, + resetForm, handleInputChange, { _customRenderers: customRenderers, diff --git a/packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx b/packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx index 18bce96..4732ee7 100644 --- a/packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx +++ b/packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx @@ -967,6 +967,7 @@ const BaseSignUpContent: FC = ({ formErrors, isLoading, isFormValid, + resetForm, handleInputChange, { _customRenderers: customRenderers, diff --git a/packages/react/src/components/presentation/auth/__tests__/AuthOptionFactory.test.tsx b/packages/react/src/components/presentation/auth/__tests__/AuthOptionFactory.test.tsx index 8061512..b10f056 100644 --- a/packages/react/src/components/presentation/auth/__tests__/AuthOptionFactory.test.tsx +++ b/packages/react/src/components/presentation/auth/__tests__/AuthOptionFactory.test.tsx @@ -40,6 +40,7 @@ const renderInto = ( false, true, () => undefined, + () => undefined, {onSubmit}, ); return render(
{elements}
); diff --git a/packages/react/src/contexts/ComponentRenderer/ComponentRendererContext.ts b/packages/react/src/contexts/ComponentRenderer/ComponentRendererContext.ts index 5fe2896..0ca608d 100644 --- a/packages/react/src/contexts/ComponentRenderer/ComponentRendererContext.ts +++ b/packages/react/src/contexts/ComponentRenderer/ComponentRendererContext.ts @@ -27,6 +27,7 @@ export interface ComponentRenderContext { isFormValid: boolean; isLoading: boolean; meta?: FlowMetadataResponse | null; + resetForm?: () => void; onInputBlur?: (name: string) => void; onInputChange: (name: string, value: string) => void; onSubmit?: (component: EmbeddedFlowComponent, data?: Record, skipValidation?: boolean) => void; diff --git a/packages/vue/src/components/auth/sign-in/AuthOptionFactoryCore.ts b/packages/vue/src/components/auth/sign-in/AuthOptionFactoryCore.ts index 05289d3..fbb35bf 100644 --- a/packages/vue/src/components/auth/sign-in/AuthOptionFactoryCore.ts +++ b/packages/vue/src/components/auth/sign-in/AuthOptionFactoryCore.ts @@ -144,6 +144,7 @@ const createAuthComponentFromFlow = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, options: { additionalData?: Record; @@ -200,7 +201,17 @@ const createAuthComponentFromFlow = ( const buttonText: string = resolve(component.label); const componentVariant: string = component.variant || ''; - const shouldSkipValidation: boolean = eventType.toUpperCase() === EmbeddedFlowEventType.Trigger; + const shouldSkipValidation: boolean = eventType.toUpperCase() !== EmbeddedFlowEventType.Submit; + + // Determine the button type based on the event type. Default to 'submit' if not recognized. + const buttonTypeEnum = { + [EmbeddedFlowEventType.Submit]: 'submit', + [EmbeddedFlowEventType.Reset]: 'reset', + [EmbeddedFlowEventType.Cancel]: 'button', + [EmbeddedFlowEventType.Trigger]: 'button', + [EmbeddedFlowEventType.Back]: 'button', + }; + const buttonType: HTMLButtonElement['type'] = buttonTypeEnum[eventType.toUpperCase()] ?? 'submit'; const handleClick = (): void => { if (options.onSubmit) { @@ -234,7 +245,13 @@ const createAuthComponentFromFlow = ( formData['consent_decisions'] = JSON.stringify(decisions); } - options.onSubmit(component, formData, shouldSkipValidation); + // For submit events, pass the form data to the onSubmit callback. For other event types, reset the form and call onSubmit with an empty data object. + if (eventType.toUpperCase() === EmbeddedFlowEventType.Submit) { + options.onSubmit(component, formData, shouldSkipValidation); + } else { + resetForm(); + options.onSubmit(component, {}, shouldSkipValidation); + } } }; @@ -288,6 +305,7 @@ const createAuthComponentFromFlow = ( onClick: handleClick, startIcon: startIconVNode ?? undefined, variant: component.variant?.toLowerCase() === 'primary' ? 'solid' : 'outline', + type: buttonType, }, {default: () => buttonText || 'Submit'}, ); @@ -350,6 +368,7 @@ const createAuthComponentFromFlow = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, { ...options, @@ -420,6 +439,7 @@ const createAuthComponentFromFlow = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, { ...options, @@ -466,6 +486,7 @@ export const renderSignInComponents = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, options?: { additionalData?: Record; @@ -489,6 +510,7 @@ export const renderSignInComponents = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, { ...options, @@ -509,6 +531,7 @@ export const renderSignUpComponents = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, options?: { additionalData?: Record; @@ -532,6 +555,7 @@ export const renderSignUpComponents = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, { ...options, @@ -552,6 +576,7 @@ export const renderInviteUserComponents = ( formErrors: Record, isLoading: boolean, isFormValid: boolean, + resetForm: () => void, onInputChange: (name: string, value: string) => void, options?: { additionalData?: Record; @@ -575,6 +600,7 @@ export const renderInviteUserComponents = ( formErrors, isLoading, isFormValid, + resetForm, onInputChange, { ...options, diff --git a/packages/vue/src/components/auth/sign-in/BaseSignIn.ts b/packages/vue/src/components/auth/sign-in/BaseSignIn.ts index 7faae76..ea356c3 100644 --- a/packages/vue/src/components/auth/sign-in/BaseSignIn.ts +++ b/packages/vue/src/components/auth/sign-in/BaseSignIn.ts @@ -286,6 +286,23 @@ const BaseSignIn: Component = defineComponent({ } }; + /** + * + * Reset the formvalues + */ + const resetForm = (): void => { + const fields: FieldDefinition[] = extractFormFields(props.components || []); + const freshValues: Record = {}; + fields.forEach((f: FieldDefinition) => { + freshValues[f.name] = ''; + }); + formValues.value = freshValues; + touchedFields.value = {}; + isSubmitting.value = false; + apiError.value = null; + clearMessages(); + }; + const renderComponents = (): VNode[] => renderSignInComponents( props.components || [], @@ -294,6 +311,7 @@ const BaseSignIn: Component = defineComponent({ formErrors.value, isLoading.value, isFormValid.value, + resetForm, handleInputChange, { additionalData: props.additionalData, diff --git a/packages/vue/src/components/auth/sign-up/BaseSignUp.ts b/packages/vue/src/components/auth/sign-up/BaseSignUp.ts index 008e9dc..a6e9f40 100644 --- a/packages/vue/src/components/auth/sign-up/BaseSignUp.ts +++ b/packages/vue/src/components/auth/sign-up/BaseSignUp.ts @@ -273,6 +273,23 @@ const BaseSignUp: Component = defineComponent({ return {fieldErrors: errors, isValid: valid}; }; + /** + * + * Reset the formvalues + */ + const resetForm = (): void => { + const fields: FieldDefinition[] = extractFormFields(props.components || []); + const freshValues: Record = {}; + fields.forEach((f: FieldDefinition) => { + freshValues[f.name] = ''; + }); + formValues.value = freshValues; + touchedFields.value = {}; + apiError.value = null; + formErrors.value = {}; + isFormValid.value = true; + }; + // ── Input handlers ── const handleInputChange = (name: string, value: string): void => { @@ -650,6 +667,7 @@ const BaseSignUp: Component = defineComponent({ formErrors.value, isLoading.value, isFormValid.value, + resetForm, handleInputChange, { buttonClassName: props.buttonClassName, diff --git a/packages/vue/src/components/presentation/accept-invite/BaseAcceptInvite.ts b/packages/vue/src/components/presentation/accept-invite/BaseAcceptInvite.ts index e122f6e..9e6abcc 100644 --- a/packages/vue/src/components/presentation/accept-invite/BaseAcceptInvite.ts +++ b/packages/vue/src/components/presentation/accept-invite/BaseAcceptInvite.ts @@ -247,6 +247,18 @@ const BaseAcceptInvite: Component = defineComponent({ return {errors, isValid: Object.keys(errors).length === 0}; }; + /** + * + * Reset the formvalues + */ + const resetForm = (): void => { + formValues.value = {}; + formErrors.value = {}; + touchedFields.value = {}; + apiError.value = null; + isFormValid.value = true; + }; + // ── Submit handler ── const handleSubmit = async (component: any, data?: Record): Promise => { @@ -494,6 +506,7 @@ const BaseAcceptInvite: Component = defineComponent({ formErrors.value, isLoading.value, isFormValid.value, + resetForm, handleInputChange, { meta, diff --git a/packages/vue/src/components/presentation/invite-user/BaseInviteUser.ts b/packages/vue/src/components/presentation/invite-user/BaseInviteUser.ts index cac77f6..418e911 100644 --- a/packages/vue/src/components/presentation/invite-user/BaseInviteUser.ts +++ b/packages/vue/src/components/presentation/invite-user/BaseInviteUser.ts @@ -324,6 +324,18 @@ const BaseInviteUser: Component = defineComponent({ initializationAttempted = false; }; + /** + * + * Reset the formvalues + */ + const resetForm = (): void => { + formValues.value = {}; + formErrors.value = {}; + touchedFields.value = {}; + apiError.value = null; + isFormValid.value = true; + }; + // ── Flow initialization ── watch( @@ -494,6 +506,7 @@ const BaseInviteUser: Component = defineComponent({ formErrors.value, isLoading.value, isFormValid.value, + resetForm, handleInputChange, { meta,