UNPKG

@adyen/kyc-components

Version:

This guide assumes that you have already an account with Adyen. A legalEntity needs to be created, and you need to have a `legalEntityId` to instatiate a Component.

276 lines (275 loc) 10.4 kB
try { let e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {}, n = new e.Error().stack; n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "b9ca1acd-0d9f-443e-94bb-5c21c14ccdbd", e._sentryDebugIdIdentifier = "sentry-dbid-b9ca1acd-0d9f-443e-94bb-5c21c14ccdbd"); } catch (e) {} import { o as createLogger } from "./translation-BFxyJ1c5.js"; import { t as useAnalyticsContext } from "./useAnalyticsContext-BVFDMrVE.js"; import { t as formDebugInfo } from "./debugStore-bZ_bkJeS.js"; import { r as FormRouterContext } from "./ErrorPanel-B536hgSc.js"; import { t as trackNavigation } from "./trackNavigation-LvCP5Vyc.js"; import { n as summaryStep } from "./Summary-B5IkOGJV.js"; import { useCallback, useMemo, useRef, useState } from "preact/hooks"; import { jsx } from "preact/jsx-runtime"; //#region src/hooks/usePageLandedEvent.ts /** * Hook that automatically sends a "Landed on page" analytics event on when active form changes. * Used on form component level (i.e. Basic information, Additional details etc.) to explicitly track page visits. * This centralizes the common analytics pattern used across form components. */ var usePageLandedEvent = (formName) => { const userEvents = useAnalyticsContext(); const oldFormNameRef = useRef(void 0); if (formName && oldFormNameRef.current !== formName) { userEvents.updateSharedEventProperties({ page: formName }); userEvents.addPageEvent("Landed on page", { actionType: "navigate" }); oldFormNameRef.current = formName; } }; //#endregion //#region src/hooks/useFormComposer.ts var logger$1 = createLogger(); /** * The step we open to for editing an existing entity should be the summary if there are verification errors * Otherwise (including for a new entity), just show the first step */ var getOpeningStep = (forms, remediationActions) => { if (remediationActions && Object.keys(remediationActions).length > 0) { const allRemediationActions = Object.values(remediationActions).flat().filter((rem) => rem.forms?.length > 0); if (allRemediationActions.length === 1 && allRemediationActions[0]?.forms.length === 1) { const form = forms.find((form) => form?.formId === allRemediationActions[0]?.forms[0]); if (form) return form; } return forms[forms.length - 1]; } return forms[0]; }; var useFormComposer = ({ problems, navigationTrackingParams, forms, externalBackClick, onSubmit, triggerValidation }) => { const userEvents = useAnalyticsContext(); const [shouldValidate, setShouldValidate] = useState(false); const [activeForm, setActiveForm] = useState(forms[0]); const isFormSummaryStep = (form) => form.formId === summaryStep.formId; const getFormIndex = (formId) => forms.findIndex((form) => form.formId === formId); const currentStep = getFormIndex(activeForm.formId); const totalSteps = forms.length; const isFinalStep = currentStep === totalSteps - 1; const isFirstStep = currentStep === 0; const firstStepId = forms[0]?.formId; usePageLandedEvent(activeForm.formName); const [oldForms, setOldForms] = useState(forms); if (oldForms !== forms) { setActiveForm((activeForm) => forms.find(({ formId }) => formId === activeForm.formId) ?? activeForm); setOldForms(forms); } /** * Opening step can be calculated once since the problems are already known on drop-in load */ const [oldFirstStepId, setOldFirstStepId] = useState(void 0); if (oldFirstStepId !== firstStepId) { /** TODO: need a better solution * If firstStepId changes it means form configurations are being setup, * not able to put forms as the dependency here as it also updates whenever validity changes * should separate out forms into forms and validity ***/ setActiveForm(getOpeningStep(forms, problems?.remediationActions)); setOldFirstStepId(firstStepId); } const gotoFormByFormIndex = (nextFormIndex, currentForms) => { setActiveForm(currentForms?.[nextFormIndex] ?? forms[nextFormIndex]); }; /** * We often run into a situation where we want to navigate to a specific form immediately after * rearranging the forms in the flow. * * The issue here is that the callback, (e.g. {@link IdVerificationMethod.handleVerifyByInvite}) * has a reference only to the current forms, not what the forms will be post-rearrangement. * Hence, it is impossible to call {@link gotoFormByFormIndex} because we have no idea what the * eventual index of the form will be. * * The solution: set a desired form ID, then resolve the navigation as part of the render * once {@link forms} contains it. The subsequent {@link gotoFormByFormIndex} will immediately * cancel the current render and trigger a new one. */ const [desiredFormId, setDesiredFormId] = useState(); if (desiredFormId) { const targetFormIndex = forms.findIndex((form) => form.formId === desiredFormId); if (targetFormIndex === -1) logger$1.warn(`Desired form ${desiredFormId} not present. \nCurrent forms: ${forms.map((f) => f.formId).join(", ")}`); else { setDesiredFormId(void 0); gotoFormByFormIndex(targetFormIndex); } } const gotoFormByFormId = (formId) => { setDesiredFormId(formId); }; const trackSubmitClick = () => { trackNavigation({ userEvents, actionType: "submit", label: "submit", additionalTrackingParams: navigationTrackingParams }); }; const handleNextClick = async (updatedForms) => { const currentForms = updatedForms ?? forms; if (isFormSummaryStep(activeForm)) { trackSubmitClick(); onSubmit(); return; } /** * Returns validation result of async validators if any */ const isAsyncValidationValid = await triggerValidation?.(activeForm.formId) ?? true; /** * Checks if both static validations and asyncValidations pass */ if (!activeForm.isValid || !isAsyncValidationValid) { /** * shouldValidate is not required with useMultiForm */ setShouldValidate(true); trackNavigation({ userEvents, actionType: "next", label: "next", returnValue: "validation error", additionalTrackingParams: navigationTrackingParams }); return; } if (isFinalStep) { trackSubmitClick(); onSubmit(); return; } setShouldValidate(false); const toFormIndex = currentStep + 1; gotoFormByFormIndex(toFormIndex, currentForms); trackNavigation({ userEvents, actionType: "next", toForm: currentForms[toFormIndex]?.formName, label: "next", returnValue: "success", additionalTrackingParams: navigationTrackingParams }); }; const handleBackClick = () => { if (currentStep > 0) { const toForm = forms[currentStep - 1]; setActiveForm(toForm); trackNavigation({ userEvents, actionType: "back", toForm: toForm?.formName, label: "back", additionalTrackingParams: navigationTrackingParams }); } }; return { handleBackClick: isFirstStep ? externalBackClick : handleBackClick, handleNextClick, gotoFormByFormIndex, gotoFormByFormId, activeForm, shouldValidate, setShouldValidate, steps: { current: currentStep, total: totalSteps } }; }; //#endregion //#region src/context/FormRouterContext/FormRouterContextProvider.tsx var logger = createLogger(); function FormRouterContextProvider({ children, forms, setFormIndex, goToFormByFormId }) { const fieldActionsRef = useRef({}); const registerFieldAction = useCallback((fieldName, action) => { fieldActionsRef.current[fieldName] = action; }, []); const unregisterFieldAction = useCallback((fieldName) => { delete fieldActionsRef.current[fieldName]; }, []); const getFieldAction = useCallback((fieldName) => { return fieldActionsRef.current[fieldName]; }, []); const contextValue = useMemo(() => ({ setFormIndex, goToFormByFormId, goToFormByFieldName: (fieldName) => { if (fieldName === "idDocument") fieldName = "idVerificationMethod"; const formIndex = forms.findIndex((form) => form?.fields?.some((field) => field === fieldName)); if (formIndex > -1) setFormIndex(formIndex); else logger.error("No form was found to have that field so form navigation failed."); }, registerFieldAction, unregisterFieldAction, getFieldAction }), [ setFormIndex, goToFormByFormId, registerFieldAction, unregisterFieldAction, getFieldAction, forms ]); return /* @__PURE__ */ jsx(FormRouterContext.Provider, { value: contextValue, children }); } //#endregion //#region src/utils/dropinUtils.ts /** * @@description Based requiredfields and optionalFields determine what forms need to be shown and add a summaryStep in the dropin * * @param forms * @param requiredFields * @param optionalFields * @param skipSummary */ var getRequiredForms = (forms, requiredFields, optionalFields, skipSummary) => { const reasons = []; const requiredForms = Object.values(forms).filter(({ formId }) => { if (requiredFields || optionalFields) { const hasRequired = Boolean(requiredFields?.[formId]?.length); const hasOptional = Boolean(optionalFields?.[formId]?.length); const isIncluded = hasRequired || hasOptional; const inclusionReasons = []; if (hasRequired) inclusionReasons.push("has required fields"); if (hasOptional) inclusionReasons.push("has optional fields"); if (isIncluded) reasons.push(`Form '${formId}': INCLUDED (${inclusionReasons.join(" & ")})`); else reasons.push(`Form '${formId}': EXCLUDED (no required or optional fields)`); return isIncluded; } reasons.push(`Form '${formId}': INCLUDED (no required/optional fields check)`); return true; }); formDebugInfo.value = { ...formDebugInfo.value, __FORMS__: { status: "INFO", reasons } }; if (skipSummary) return requiredForms; return [...requiredForms, summaryStep]; }; /** * @description Based on validity of fields from formValidity and existence of validationErrors/Verification errors from backend determine whether the forms needs to be shown as valid or not * * @param forms * @param formValidity * @param problems */ var addValidityToForms = (forms, formValidity, problems) => forms.map(({ formId, formName, fields }) => ({ formId, formName, fields, isValid: formValidity?.[formId] ?? false, hasServerValidationErrors: Boolean(problems?.validationErrors?.[formId]) })); //#endregion export { useFormComposer as i, getRequiredForms as n, FormRouterContextProvider as r, addValidityToForms as t };