Skip to content

Commit 01ad827

Browse files
feat: consuming /sdk-configs for dynamic fields
1 parent 290f87b commit 01ad827

21 files changed

Lines changed: 909 additions & 373 deletions

src/components/dynamic/AddressElement.res

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/components/dynamic/CardElement.res

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,16 @@ let make = (
3939
~accessible=?,
4040
~checkEligibility: option<string> => unit=_ => (),
4141
) => {
42-
switch (
43-
fields->Array.get(0),
44-
fields->Array.get(1),
45-
fields->Array.get(2),
46-
fields->Array.get(3),
47-
fields->Array.get(4),
48-
) {
49-
| (
50-
Some(cardNumberConfig),
51-
Some(cardExpiryMonthConfig),
52-
Some(cardExpiryYearConfig),
53-
Some(cardCvcConfig),
54-
Some(cardNetworkConfig),
55-
) => {
42+
let findField = (renderType: SuperpositionTypes.fieldType) =>
43+
fields->Array.find((f: SuperpositionTypes.fieldConfig) => f.fieldRenderType === renderType)
44+
let cardNumberConfig = findField(SuperpositionTypes.CardNumber)
45+
let cardExpiryMonthConfig = findField(SuperpositionTypes.CardExpiryMonth)
46+
let cardExpiryYearConfig = findField(SuperpositionTypes.CardExpiryYear)
47+
let cardCvcConfig = findField(SuperpositionTypes.Cvc)
48+
let cardNetworkConfig = findField(SuperpositionTypes.CardNetwork)
49+
50+
switch (cardNumberConfig, cardExpiryMonthConfig, cardExpiryYearConfig) {
51+
| (Some(cardNumberConfig), Some(cardExpiryMonthConfig), Some(cardExpiryYearConfig)) => {
5652
let (nativeProp, _) = React.useContext(NativePropContext.nativePropContext)
5753
let {eligibilityStatus} = React.useContext(DynamicFieldsContext.dynamicFieldsContext)
5854
let emitter = PaymentEvents.usePaymentEventEmitter()
@@ -73,31 +69,48 @@ let make = (
7369
let nullRef = React.useRef(Nullable.null)
7470

7571
let {input: cardNumberInput, meta: cardNumberMeta} = ReactFinalForm.useField(
76-
cardNumberConfig.outputPath,
72+
cardNumberConfig.confirmRequestWritePath,
7773
~config={
7874
validate: createFieldValidator(CardNumber),
7975
format: formatValue(CardNumber),
8076
},
8177
)
8278

8379
let {input: cardExpiryMonthInput, meta: _cardExpiryMonthMeta} = ReactFinalForm.useField(
84-
cardExpiryMonthConfig.outputPath,
80+
cardExpiryMonthConfig.confirmRequestWritePath,
8581
~config={validate: createFieldValidator(CardExpiry(expireDate))},
8682
)
8783

8884
let {input: cardExpiryYearInput, meta: cardExpiryYearMeta} = ReactFinalForm.useField(
89-
cardExpiryYearConfig.outputPath,
85+
cardExpiryYearConfig.confirmRequestWritePath,
9086
~config={validate: createFieldValidator(CardExpiry(expireDate))},
9187
)
9288

89+
let hasNetwork = cardNetworkConfig->Option.isSome
90+
let hasCvc = cardCvcConfig->Option.isSome
91+
let cardNetworkPath =
92+
cardNetworkConfig
93+
->Option.map(c => c.confirmRequestWritePath)
94+
->Option.getOr("__card_network_unbound")
95+
let cardCvcPath =
96+
cardCvcConfig
97+
->Option.map(c => c.confirmRequestWritePath)
98+
->Option.getOr("__card_cvc_unbound")
99+
93100
let {input: cardNetworkInput, meta: cardNetworkMeta} = ReactFinalForm.useField(
94-
cardNetworkConfig.outputPath,
95-
~config={validate: createFieldValidator(CardNetwork(enabledCardSchemes))},
101+
cardNetworkPath,
102+
~config={
103+
validate: ?(hasNetwork && enabledCardSchemes->Array.length > 0
104+
? Some(createFieldValidator(CardNetwork(enabledCardSchemes)))
105+
: None),
106+
},
96107
)
97108

98109
let {input: cardCvcInput, meta: cardCvcMeta} = ReactFinalForm.useField(
99-
cardCvcConfig.outputPath,
100-
~config={validate: createFieldValidator(CardCVC(cardNetworkInput.value->Option.getOr("")))},
110+
cardCvcPath,
111+
~config={
112+
validate: ?(hasCvc ? Some(createFieldValidator(CardCVC(cardNetworkInput.value->Option.getOr("")))) : None),
113+
},
101114
)
102115

103116
let (
@@ -338,10 +351,14 @@ let make = (
338351
(expireDate->String.length === 7 && checkCardExpiry(expireDate))}
339352
maxLength=Some(7)
340353
borderTopWidth=?{splitCardFields ? None : Some(borderWidth /. 2.)}
341-
borderRightWidth=?{splitCardFields ? None : Some(borderWidth /. 2.)}
354+
borderRightWidth=?{splitCardFields
355+
? None
356+
: Some(hasCvc ? borderWidth /. 2. : borderWidth)}
342357
borderTopLeftRadius=?{splitCardFields ? None : Some(0.)}
343358
borderTopRightRadius=?{splitCardFields ? None : Some(0.)}
344-
borderBottomRightRadius=?{splitCardFields ? None : Some(0.)}
359+
borderBottomRightRadius=?{splitCardFields
360+
? None
361+
: Some(hasCvc ? 0. : borderRadius)}
345362
textColor={((cardExpiryYearMeta.error->Option.isNone ||
346363
!cardExpiryYearMeta.touched ||
347364
cardExpiryYearMeta.active) && expireDate->String.length < 7) ||
@@ -376,6 +393,7 @@ let make = (
376393
}}
377394
</UIUtils.RenderIf>
378395
</View>
396+
<UIUtils.RenderIf condition={hasCvc}>
379397
<View style={s({flex: 1.})}>
380398
<CustomInput
381399
name={TestUtils.cvcInputTestId}
@@ -466,6 +484,7 @@ let make = (
466484
}}
467485
</UIUtils.RenderIf>
468486
</View>
487+
</UIUtils.RenderIf>
469488
</View>
470489
</View>
471490
<UIUtils.RenderIf condition={!splitCardFields}>

src/components/dynamic/CryptoElement.res

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,38 @@ let make = (
2929
}
3030

3131
let {gap} = ThemebasedStyle.useThemeBasedStyle()
32-
33-
switch (fields->Array.get(0), fields->Array.get(1)) {
32+
let localeObject = GetLocale.useGetLocalObj()
33+
let getLocalized = key => GetLocale.lookupLocaleString(localeObject, key)
34+
let currencyConfig =
35+
fields->Array.find((f: SuperpositionTypes.fieldConfig) =>
36+
f.fieldRenderType === SuperpositionTypes.CryptoCurrency
37+
)
38+
let networkConfig =
39+
fields->Array.find((f: SuperpositionTypes.fieldConfig) =>
40+
f.fieldRenderType === SuperpositionTypes.CryptoNetwork
41+
)
42+
switch (currencyConfig, networkConfig) {
3443
| (Some(currencyConfig), Some(networkConfig)) =>
3544
let {input: currencyInput, meta: currencyMeta} = ReactFinalForm.useField(
36-
currencyConfig.outputPath,
37-
~config={validate: createFieldValidator(Validation.Required)},
45+
currencyConfig.confirmRequestWritePath,
46+
~config={validate: createFieldValidator(Validation.Required(None))},
3847
)
3948

4049
let {input: networkInput, meta: networkMeta} = ReactFinalForm.useField(
41-
networkConfig.outputPath,
42-
~config={validate: createFieldValidator(Validation.Required)},
50+
networkConfig.confirmRequestWritePath,
51+
~config={validate: createFieldValidator(Validation.Required(None))},
4352
)
4453

54+
React.useEffect1(() => {
55+
let validNetworks = getNetworkArray(currencyInput.value)
56+
switch networkInput.value {
57+
| Some(network) if network !== "" && !(validNetworks->Array.includes(network)) =>
58+
networkInput.onChange("")
59+
| _ => ()
60+
}
61+
None
62+
}, [currencyInput.value->Option.getOr("")])
63+
4564
<>
4665
<React.Fragment>
4766
<View style={s({marginBottom: gap->dp})}>
@@ -53,11 +72,14 @@ let make = (
5372
<CustomPicker
5473
value=currencyInput.value
5574
setValue=handlePickerChange
56-
items={currencyConfig.options->Array.map(opt => {
75+
items={currencyConfig.dropdownOptions->Option.getOr([])->Array.map(opt => {
5776
SdkTypes.label: opt,
5877
value: opt,
5978
})}
60-
placeholderText={GetLocale.getLocalString(currencyConfig.displayName)}
79+
placeholderText={FieldLabelResolver.resolvePlaceholder(
80+
currencyConfig,
81+
getLocalized,
82+
)}
6183
isValid={currencyMeta.error->Option.isNone ||
6284
!currencyMeta.touched ||
6385
currencyMeta.active}
@@ -96,7 +118,7 @@ let make = (
96118
}}
97119
setValue=handlePickerChange
98120
items
99-
placeholderText={GetLocale.getLocalString(networkConfig.displayName)}
121+
placeholderText={FieldLabelResolver.resolvePlaceholder(networkConfig, getLocalized)}
100122
isValid={networkMeta.error->Option.isNone ||
101123
!networkMeta.touched ||
102124
networkMeta.active}

src/components/dynamic/DateElement.res

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ module DatePicker = {
6262
{label: "Nov", value: "11"},
6363
{label: "Dec", value: "12"},
6464
]
65+
let currentYear = Date.make()->Date.getFullYear
6566
let yearItems = []
6667
Belt.Range.forEach(0, 125, y => {
67-
let yearStr = (2025 - y)->Int.toString
68+
let yearStr = (currentYear - y)->Int.toString
6869
yearItems->Array.push({SdkTypes.label: yearStr, value: yearStr})
6970
})
7071
(dayItems, monthItems, yearItems)
@@ -140,15 +141,27 @@ let make = (
140141
~accessible=?,
141142
) => {
142143
let {gap} = ThemebasedStyle.useThemeBasedStyle()
143-
144+
let localeObject = GetLocale.useGetLocalObj()
145+
let getLocalized = key => GetLocale.lookupLocaleString(localeObject, key)
144146
fields
147+
->Array.filter((f: SuperpositionTypes.fieldConfig) =>
148+
switch f.fieldRenderType {
149+
| Date | DateOfBirth => true
150+
| _ => false
151+
}
152+
)
145153
->Array.map(field => {
146-
let placeholder = GetLocale.getLocalString(field.displayName)
154+
let placeholder = FieldLabelResolver.resolvePlaceholder(field, getLocalized)
155+
let validationRule = switch field.fieldRenderType {
156+
| DateOfBirth => Validation.DateOfBirth
157+
| _ => Validation.Required(None)
158+
}
147159

148-
<React.Fragment key={field.outputPath}>
160+
<React.Fragment key={field.confirmRequestWritePath}>
149161
<View style={s({marginBottom: gap->dp})}>
150162
<ReactFinalForm.Field
151-
name=field.outputPath validate=Some(createFieldValidator(Validation.Required))>
163+
name=field.confirmRequestWritePath
164+
validate=Some(createFieldValidator(validationRule))>
152165
{fieldProps => <DatePicker fieldProps placeholder accessible />}
153166
</ReactFinalForm.Field>
154167
</View>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
open ParentElement
2+
3+
type category =
4+
| Card
5+
| Email
6+
| Name
7+
| Phone
8+
| Crypto
9+
| Date
10+
| Generic
11+
12+
let classify = (field: SuperpositionTypes.fieldConfig): category =>
13+
switch field.fieldRenderType {
14+
| CardNumber | Cvc | CardExpiryMonth | CardExpiryYear | CardNetwork => Card
15+
| Email => Email
16+
| FirstName | LastName | CardHolderName => Name
17+
| Phone | PhoneCountryCode => Phone
18+
| CryptoCurrency | CryptoNetwork => Crypto
19+
| Date | DateOfBirth => Date
20+
| Generic | Dropdown | Country | State => Generic
21+
}
22+
23+
let toElement = (cat: category, fields): elementType =>
24+
switch cat {
25+
| Card => CARD(fields)
26+
| Email => EMAIL(fields)
27+
| Name => FULLNAME(fields)
28+
| Phone => PHONE(fields)
29+
| Crypto => CRYPTO(fields)
30+
| Date => DATE(fields)
31+
| Generic => GENERIC(fields)
32+
}
33+
34+
let isCombineCluster = (cat: category) =>
35+
switch cat {
36+
| Card | Email | Name | Phone | Crypto => true
37+
| Date | Generic => false
38+
}
39+
40+
let groupFields = (fields: array<SuperpositionTypes.fieldConfig>): array<elementType> => {
41+
let count = fields->Array.length
42+
let catAt = index => fields->Array.get(index)->Option.mapOr(Generic, classify)
43+
let firstIndexOfCat = cat => fields->Array.findIndex(f => classify(f) === cat)
44+
45+
let rec runEnd = (cat, index) =>
46+
index < count && catAt(index) === cat ? runEnd(cat, index + 1) : index
47+
48+
let rec walk = (start, acc) =>
49+
if start >= count {
50+
acc
51+
} else {
52+
let cat = catAt(start)
53+
if isCombineCluster(cat) {
54+
walk(
55+
start + 1,
56+
start === firstIndexOfCat(cat)
57+
? acc->Array.concat([toElement(cat, fields->Array.filter(f => classify(f) === cat))])
58+
: acc,
59+
)
60+
} else {
61+
let stop = runEnd(cat, start + 1)
62+
walk(stop, acc->Array.concat([toElement(cat, fields->Array.slice(~start, ~end=stop))]))
63+
}
64+
}
65+
66+
walk(0, [])
67+
}
68+
69+
let keyOf = (element: elementType): string =>
70+
switch element {
71+
| CARD(fs)
72+
| CRYPTO(fs)
73+
| FULLNAME(fs)
74+
| PHONE(fs)
75+
| EMAIL(fs)
76+
| DATE(fs)
77+
| GENERIC(fs) =>
78+
fs->Array.get(0)->Option.map(f => f.confirmRequestWritePath)->Option.getOr("empty-group")
79+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
let resolvePlaceholder = (
2+
field: SuperpositionTypes.fieldConfig,
3+
getLocalized: string => option<string>,
4+
) =>
5+
switch field.merchantProvidedPlaceholderText {
6+
| Some(text) if text !== "" => text
7+
| _ =>
8+
switch field.placeholderLocalizationKey {
9+
| Some(key) => getLocalized(key)->Option.getOr(field.defaultLabelText)
10+
| None => field.defaultLabelText
11+
}
12+
}

0 commit comments

Comments
 (0)