Skip to content
Closed
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
44 changes: 44 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"react": "^18.2.0",
"react-datepicker": "^8.4.0",
"react-dom": "^18.2.0",
"react-final-form": "^7.0.0",
"recoil": "^0.7.7",
"webpack-merge": "^6.0.1"
},
Expand Down
2 changes: 1 addition & 1 deletion shared-code
Submodule shared-code updated 40 files
+29 −0 assets/v1/icons/mobile/idealwero.svg
+29 −0 assets/v2/icons/idealwero.svg
+1 −4 assets/v2/icons/trustly.svg
+2 −1 assets/v2/jsons/locales/ar.json
+2 −1 assets/v2/jsons/locales/bs.json
+2 −1 assets/v2/jsons/locales/ca.json
+2 −1 assets/v2/jsons/locales/cs.json
+2 −1 assets/v2/jsons/locales/cy.json
+2 −1 assets/v2/jsons/locales/da.json
+2 −1 assets/v2/jsons/locales/de.json
+2 −1 assets/v2/jsons/locales/el.json
+2 −1 assets/v2/jsons/locales/en-GB.json
+2 −1 assets/v2/jsons/locales/en.json
+2 −1 assets/v2/jsons/locales/es.json
+2 −1 assets/v2/jsons/locales/et.json
+2 −1 assets/v2/jsons/locales/fi.json
+2 −1 assets/v2/jsons/locales/fr-BE.json
+2 −1 assets/v2/jsons/locales/fr.json
+2 −1 assets/v2/jsons/locales/he.json
+2 −1 assets/v2/jsons/locales/is.json
+2 −1 assets/v2/jsons/locales/it.json
+2 −1 assets/v2/jsons/locales/ja.json
+2 −1 assets/v2/jsons/locales/lt.json
+2 −1 assets/v2/jsons/locales/ms.json
+2 −1 assets/v2/jsons/locales/nl-BE.json
+2 −1 assets/v2/jsons/locales/nl.json
+2 −1 assets/v2/jsons/locales/no.json
+2 −1 assets/v2/jsons/locales/pl.json
+2 −1 assets/v2/jsons/locales/pt.json
+2 −1 assets/v2/jsons/locales/ru.json
+2 −1 assets/v2/jsons/locales/sk.json
+2 −1 assets/v2/jsons/locales/sv.json
+2 −1 assets/v2/jsons/locales/tr-CY.json
+2 −1 assets/v2/jsons/locales/tr-ZH.json
+2 −1 assets/v2/jsons/locales/zh.json
+7 −2 sdk-utils/components/ReactFinalForm.res
+258 −0 sdk-utils/events/PaymentEventData.res
+51 −0 sdk-utils/events/PaymentEventTypes.res
+2 −0 sdk-utils/types/LocaleDataType.res
+2 −0 sdk-utils/utils/CommonUtils.res
2 changes: 2 additions & 0 deletions src/App.res
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ let make = () => {
let clientSecret = getQueryParamsDictforKey(url.search, "clientSecret")
let sessionId = getQueryParamsDictforKey(url.search, "sessionId")
let publishableKey = getQueryParamsDictforKey(url.search, "publishableKey")
let profileId = getQueryParamsDictforKey(url.search, "profileId")
let endpoint = getQueryParamsDictforKey(url.search, "endpoint")
let pmSessionId = getQueryParamsDictforKey(url.search, "pmSessionId")
let hyperComponentName =
Expand All @@ -129,6 +130,7 @@ let make = () => {

<PreMountLoader
publishableKey
profileId
sessionId
sdkAuthorization
clientSecret
Expand Down
56 changes: 35 additions & 21 deletions src/Components/CryptoCurrencyNetworks.res
Original file line number Diff line number Diff line change
@@ -1,40 +1,54 @@
open SuperpositionTypes

@react.component
let make = () => {
open DropdownField
let currencyVal = Recoil.useRecoilValueFromAtom(RecoilAtoms.userCurrency)
let make = (~networkField: fieldConfig, ~currencyField: fieldConfig) => {
let networkPath = networkField.confirmRequestWritePath
let currencyFieldPath = currencyField.confirmRequestWritePath
let {config, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
let (cryptoCurrencyNetworks, setCryptoCurrencyNetworks) = Recoil.useRecoilState(
RecoilAtoms.cryptoCurrencyNetworks,
let {label} = DynamicFieldsUtils.resolveFieldTexts(
~field=networkField,
~localeObject=localeString,
)
let validate = DynamicFieldsUtils.resolveValidator(
~field=networkField,
~localeObject=localeString,
)

let currencyFieldProps = ReactFinalForm.useField(currencyFieldPath)
let currencyVal = currencyFieldProps.input.value->Option.getOr("")

let dropdownOptions =
Utils.currencyNetworksDict
->Dict.get(currencyVal)
->Option.getOr([])
->Array.map(item => {
label: Utils.toSpacedUpperCase(~str=item, ~delimiter="_"),
value: item,
->Array.map((item): DropdownField.optionType => {
{
label: Utils.toSpacedUpperCase(~str=item, ~delimiter="_"),
value: item,
}
})

let initialValue = (
dropdownOptions
->Array.get(0)
->Option.getOr({
label: "",
value: "",
})
).value
let initialNetwork = dropdownOptions->Array.get(0)->Option.map(opt => opt.value)->Option.getOr("")

let field = ReactFinalForm.useField(
networkPath,
~config={validate, initialValue: Some(initialNetwork)},
)

React.useEffect(() => {
setCryptoCurrencyNetworks(_ => initialValue)
if initialNetwork !== "" {
field.input.onChange(initialNetwork)
}
None
}, [initialValue])
}, [initialNetwork])

let value = field.input.value->Option.getOr(initialNetwork)

<DropdownField
appearance=config.appearance
fieldName=localeString.currencyNetwork
value=cryptoCurrencyNetworks
setValue=setCryptoCurrencyNetworks
fieldName={label}
value
setValue={setter => field.input.onChange(setter(value))}
disabled=false
options=dropdownOptions
/>
Expand Down
212 changes: 212 additions & 0 deletions src/Components/DynamicFieldInput.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
open SuperpositionTypes

// Groups an array of fieldConfig by layoutRowId.
// Fields with no layoutRowId each form their own singleton row.
let groupFieldsByRow = (fields: array<fieldConfig>): array<array<fieldConfig>> => {
let rows: array<array<fieldConfig>> = []
let rowMap: Dict.t<array<fieldConfig>> = Dict.make()

fields->Array.forEach(field => {
switch field.layoutRowId {
| None => rows->Array.push([field])
| Some(rowId) =>
switch rowMap->Dict.get(rowId) {
| Some(row) => row->Array.push(field)
| None =>
let row = [field]
rowMap->Dict.set(rowId, row)
rows->Array.push(row)
}
}
})

rows
}

// Renders a single fieldConfig entry as a RFF-connected input.
let renderSingleField = (
field: fieldConfig,
~allFields: array<fieldConfig>,
~fieldRef: React.ref<Nullable.t<'a>>,
~globalEmailPaths: option<array<string>>=?,
) => {
switch field.fieldRenderType {
| CardNumber
| Cvc => React.null

| CardHolderName =>
// last_name is rendered as part of the first_name field — skip it here
if field.confirmRequestWritePath->String.endsWith(".last_name") {
React.null
} else {
let lastNameField =
allFields->Array.find(f =>
f.fieldRenderType === CardHolderName &&
f.confirmRequestWritePath->String.endsWith(".last_name")
)
switch lastNameField {
| Some(lastNameField) => <CardHolderNameField firstNameField=field lastNameField />
| None =>
// No separate last_name field — render as a plain Generic input
let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
let {label, placeholder} = DynamicFieldsUtils.resolveFieldTexts(
~field,
~localeObject=localeString,
)
let autocomplete = field.htmlAutocompleteAttribute->Option.getOr("cc-name")
let validate = DynamicFieldsUtils.resolveValidator(~field, ~localeObject=localeString)
<ReactFinalForm.Field name={field.confirmRequestWritePath} validate={Some(validate)}>
{(fieldProps: ReactFinalForm.Field.fieldProps) => {
let {input, meta} = fieldProps
let value = input.value->Option.getOr("")
let isValid = if meta.touched {
Some(meta.valid)
} else {
None
}
let errorString = if meta.touched && meta.invalid {
meta.error->Option.getOr("")
} else {
""
}
<PaymentInputField
fieldName={label}
value
onChange={ev => input.onChange(ReactEvent.Form.target(ev)["value"])}
onBlur={_ev => input.onBlur()}
isValid
errorString
placeholder
inputRef={fieldRef}
autocomplete
maxLength=?{field.maxInputLength}
/>
}}
</ReactFinalForm.Field>
}
}

| Email =>
let allEmailPaths = switch globalEmailPaths {
| Some(paths) => paths
| None => []
}
let firstEmailPath = allEmailPaths->Array.get(0)
if firstEmailPath !== Some(field.confirmRequestWritePath) {
React.null
} else {
<EmailField fieldConfig=field paths=allEmailPaths />
}

| Date => <DateOfBirth fieldConfig=field />

| Generic =>
let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
let {label, placeholder} = DynamicFieldsUtils.resolveFieldTexts(
~field,
~localeObject=localeString,
)
let autocomplete = field.htmlAutocompleteAttribute->Option.getOr("on")
let validate = DynamicFieldsUtils.resolveValidator(~field, ~localeObject=localeString)

<ReactFinalForm.Field name={field.confirmRequestWritePath} validate={Some(validate)}>
{(fieldProps: ReactFinalForm.Field.fieldProps) => {
let {input, meta} = fieldProps
let value = input.value->Option.getOr("")
let isValid = if meta.touched {
Some(meta.valid)
} else {
None
}
let errorString = if meta.touched && meta.invalid {
meta.error->Option.getOr("")
} else {
""
}
<PaymentInputField
fieldName={label}
value
onChange={ev => input.onChange(ReactEvent.Form.target(ev)["value"])}
onBlur={_ev => input.onBlur()}
isValid
errorString
placeholder
inputRef={fieldRef}
autocomplete
maxLength=?{field.maxInputLength}
/>
}}
</ReactFinalForm.Field>

| Dropdown =>
if field.confirmRequestWritePath->String.endsWith(".state") {
let countryFieldPath = field.confirmRequestWritePath->String.replace(".state", ".country")
<StateDropdownField field countryFieldPath />
} else if field.confirmRequestWritePath->String.endsWith(".country") {
let isoCodes = field.dropdownOptions->Option.getOr([])
let options =
isoCodes
->Utils.isoOptionsToCountryNames
->DropdownField.updateArrayOfStringToOptionsTypeArray
if options->Array.length === 0 {
React.null
} else {
<CountryDropdownField fieldConfig=field options />
}
} else if field.confirmRequestWritePath->String.endsWith(".country_code") {
<PhoneCountryCodeDropdownField fieldConfig=field />
} else if field.confirmRequestWritePath->String.endsWith("crypto.network") {
let currencyField =
allFields->Array.find(f =>
f.confirmRequestWritePath->String.endsWith("crypto.pay_currency")
)
switch currencyField {
| None => React.null
| Some(currencyField) => <CryptoCurrencyNetworks networkField=field currencyField />
}
} else {
let options =
field.dropdownOptions->Option.getOr([])->DropdownField.updateArrayOfStringToOptionsTypeArray
if options->Array.length === 0 {
React.null
} else {
let initialValue = options->Array.get(0)->Option.map(o => o.value)->Option.getOr("")
<GenericDropdownField fieldConfig=field options initialValue />
}
}

| Phone => <PhoneField fieldConfig=field />
}
}

// Renders a row of fields side-by-side using flex layout.
@react.component
let makeRow = (
~fields: array<fieldConfig>,
~allFields: array<fieldConfig>,
~globalEmailPaths: option<array<string>>=?,
) => {
let fieldRef = React.useRef(Nullable.null)

switch fields->Array.length {
| 0 => React.null
| 1 =>
switch fields->Array.get(0) {
| None => React.null
| Some(field) => renderSingleField(field, ~allFields, ~fieldRef, ~globalEmailPaths?)
}
| _ =>
<div className="flex gap-4 w-full">
{fields
->Array.mapWithIndex((field, i) => {
let flex = field.layoutWidthRatio->Option.getOr(1.0)
<div
key={field.confirmRequestWritePath ++ "-" ++ i->Int.toString}
style={flexGrow: flex->Float.toString, flexShrink: "1", flexBasis: "0%"}>
{renderSingleField(field, ~allFields, ~fieldRef, ~globalEmailPaths?)}
</div>
})
->React.array}
</div>
}
}
Loading
Loading