Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 58 additions & 16 deletions src/Components/DynamicFields.res
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module FormBody = {
~formRef: React.ref<option<ReactFinalForm.Form.formMethods>>,
~languagePreferenceFields: array<fieldConfig>,
~missingRequiredFieldsFiltered: array<fieldConfig>,
~persistableFields: array<fieldConfig>,
~dynamicFieldsOutsideBilling: array<fieldConfig>,
~dynamicFieldsInsideBilling: array<fieldConfig>,
~allEmailFields: array<fieldConfig>,
Expand All @@ -39,6 +40,7 @@ module FormBody = {
let {config, themeObj, localeString} = Jotai.useAtomValue(configAtom)
let setAreRequiredFieldsValid = Jotai.useSetAtom(areRequiredFieldsValid)
let setAreRequiredFieldsEmpty = Jotai.useSetAtom(areRequiredFieldsEmpty)
let setUserDynamicFieldsValues = Jotai.useSetAtom(userDynamicFieldsValues)

let isSpacedInnerLayout = config.appearance.innerLayout === Spaced
let isRenderDynamicFieldsInsideBilling = dynamicFieldsInsideBilling->Array.length > 0
Expand All @@ -62,6 +64,17 @@ module FormBody = {
)

setRequiredFieldsBody(_ => flatValues)

// Persist field values so they survive the remount on payment-method-type switch.
setUserDynamicFieldsValues(prev => {
let next = prev->Dict.copy
persistableFields->Array.forEach(field => {
let path = field.confirmRequestWritePath
next->Dict.set(path, Utils.getString(flatValues, path, ""))
})
next
})

syncEmitAddressAtoms(flatValues)

let isEmpty = missingRequiredFieldsFiltered->Array.some(field => {
Expand Down Expand Up @@ -159,6 +172,7 @@ let make = (
let {localeString} = Jotai.useAtomValue(configAtom)
let {billingAddress, redirectionInfo, defaultValues} = Jotai.useAtomValue(optionAtom)
let syncEmitAddressAtoms = DynamicFieldsUtils.useSyncEmitAddressAtoms()
let cachedUserDynamicFieldsValues = Jotai.useAtomValue(userDynamicFieldsValues)

let intentData = paymentMethodListValue.intent_data.intentDataObject

Expand All @@ -183,6 +197,18 @@ let make = (
None
}, [requiredFields])

let rendersVisibleInput = (field: fieldConfig) =>
switch field.fieldRenderType {
| CardNumber
| Cvc
| CardExpiryMonth
| CardExpiryYear
| CardNetwork
| LanguagePreference => false
| Dropdown => field.dropdownOptions->Option.getOr([])->Array.length > 0
| _ => true
}

let missingRequiredFieldsFiltered = React.useMemo(() => {
let firstEmailPath =
missingRequiredFields
Expand All @@ -200,28 +226,43 @@ let make = (
// - Any card-data fields (card_exp_month, card_exp_year, card_network, etc.)
// - Duplicate Email / CardHolderName fields (only the first path is rendered)
// - Dropdown fields with no options (would render React.null anyway)
missingRequiredFields->Array.filter(field => {
switch field.fieldRenderType {
| CardNumber
| Cvc
| CardExpiryMonth
| CardExpiryYear
| CardNetwork
| LanguagePreference => false
| Dropdown =>
let options = field.dropdownOptions->Option.getOr([])
options->Array.length > 0
| Email => firstEmailPath === Some(field.confirmRequestWritePath)
| CardHolderName => firstCardHolderNamePath === Some(field.confirmRequestWritePath)
| _ => true
missingRequiredFields->Array.filter(field =>
if !rendersVisibleInput(field) {
false
} else {
switch field.fieldRenderType {
| Email => firstEmailPath === Some(field.confirmRequestWritePath)
| CardHolderName => firstCardHolderNamePath === Some(field.confirmRequestWritePath)
| _ => true
}
}
})
)
}, [missingRequiredFields])

let persistableFields = React.useMemo(() => {
missingRequiredFields->Array.filter(field => rendersVisibleInput(field))
}, [missingRequiredFields])

let initialValuesWithBillingDataOverride = React.useMemo(() => {
DynamicFieldsUtils.applyBillingDetailsOverride(initialValues, defaultValues.billingDetails)
}, (initialValues, defaultValues.billingDetails))

let initialValuesWithUserInputOverride = React.useMemo(() => {
let merged = initialValuesWithBillingDataOverride
persistableFields->Array.forEach(field =>
switch cachedUserDynamicFieldsValues->Dict.get(field.confirmRequestWritePath) {
| Some(value) =>
DynamicFieldsUtils.setValueAtNestedPathAllowingEmpty(
merged,
field.confirmRequestWritePath->String.split("."),
value,
)->ignore
| None => ()
}
)
merged
}, (initialValuesWithBillingDataOverride, cachedUserDynamicFieldsValues, persistableFields))

let isInsideBillingField = (field: fieldConfig) =>
field.fieldRenderType === Email ||
(field.confirmRequestWritePath->String.startsWith(billingPrefix) &&
Expand Down Expand Up @@ -309,14 +350,15 @@ let make = (
<>
<RenderIf condition={!isSavedCardFlow && shouldRenderForm}>
<ReactFinalForm.Form
initialValues={Some(initialValuesWithBillingDataOverride)}
initialValues={Some(initialValuesWithUserInputOverride)}
onSubmit={_values => ()}
render={formProps =>
<FormBody
formProps
formRef
languagePreferenceFields
missingRequiredFieldsFiltered
persistableFields
dynamicFieldsOutsideBilling
dynamicFieldsInsideBilling
allEmailFields
Expand Down
11 changes: 10 additions & 1 deletion src/Components/DynamicFields/PhoneCountryCodeDropdownField.res
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,16 @@ let make = (~fieldConfig: fieldConfig, ~isLabelHidden=false) => {
~config={validate, initialValue: Some(defaultCode)},
)

let (valueDropDown, setValueDropDown) = React.useState(_ => defaultDropdownValue)
// RFF stores only the dial code, while the dropdown needs its full flag#code option value.
// Rebuild that UI value from RFF so a cached code survives the form remount.
let storedCode = field.input.value->Option.getOr(defaultCode)
let initialDropdownValue =
phoneNumberCodeOptions
->Array.find(option => option.value->getPhoneCode === storedCode)
->Option.map(option => option.value)
->Option.getOr(defaultDropdownValue)

let (valueDropDown, setValueDropDown) = React.useState(_ => initialDropdownValue)
let (displayValue, setDisplayValue) = React.useState(_ => "")

React.useEffect(() => {
Expand Down
41 changes: 41 additions & 0 deletions src/Utilities/DynamicFieldsUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,47 @@ let getKlarnaRequiredFields = (
})
}

/*
Function based on 'SuperpositionHelper.setValueAtNestedPath',
but allows empty string values to be set at the final key in the path.
*/
let setValueAtNestedPathAllowingEmpty = (
dict: Dict.t<JSON.t>,
keys: array<string>,
value: string,
): Dict.t<JSON.t> => {
let keysLength = keys->Array.length

if keysLength === 0 {
dict
} else if keysLength === 1 {
let key = CommonUtils.getArrayElement(keys, 0, "")
if key !== "" {
dict->Dict.set(key, value->JSON.Encode.string)
}
dict
} else {
let currentDict = ref(dict)
let pathLength = keysLength - 1

for i in 0 to pathLength - 1 {
let key = CommonUtils.getArrayElement(keys, i, "")
if key !== "" {
let nestedDict = SuperpositionHelper.getOrCreateNestedDictionary(currentDict.contents, key)
currentDict.contents->Dict.set(key, nestedDict->JSON.Encode.object)
currentDict := nestedDict
}
}

let finalKey = CommonUtils.getArrayElement(keys, pathLength, "")
if finalKey !== "" {
currentDict.contents->Dict.set(finalKey, value->JSON.Encode.string)
}

dict
}
}

let applyBillingDetailsOverride = (
initialValues: Dict.t<JSON.t>,
billingDetails: PaymentType.billingDetails,
Expand Down
1 change: 1 addition & 0 deletions src/Utilities/JotaiAtoms.res
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ let userAddressCity = Jotai.atom(defaultFieldValues)
let userAddressPincode = Jotai.atom(defaultFieldValues)
let userAddressState = Jotai.atom(defaultFieldValues)
let userAddressCountry = Jotai.atom(defaultFieldValues)
let userDynamicFieldsValues: Jotai.atom<Dict.t<string>> = Jotai.atom(Dict.make())
let userBlikCode = Jotai.atom(defaultFieldValues)
let userGiftCardNumber = Jotai.atom(defaultFieldValues)
let userGiftCardPin = Jotai.atom(defaultFieldValues)
Expand Down
Loading