mirror of
https://github.com/jlengrand/adyen-web.git
synced 2026-03-10 08:01:22 +00:00
Fix/remove this keyword from functional comps (#1968)
* Removing this keyword from functional comps * Removing this keyword from functional comps - expanding to parent UIElements (Ach, Doku, Econtext, Boleto) * Removing some mocks from e2e tests now we have appropriate test card * Implementing suggestions to improve interfaces and prevent repetition
This commit is contained in:
@@ -1,63 +1,24 @@
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.resolve('../../', '.env') });
|
||||
|
||||
import { Selector, RequestMock } from 'testcafe';
|
||||
import { Selector } from 'testcafe';
|
||||
import { start, getIframeSelector, getIsValid } from '../../../../utils/commonUtils';
|
||||
import cu from '../../../utils/cardUtils';
|
||||
import { BASE_URL, CARDS_URL } from '../../../../pages';
|
||||
import { BIN_LOOKUP_VERSION, UNKNOWN_BIN_CARD } from '../../../utils/constants';
|
||||
import { CARDS_URL } from '../../../../pages';
|
||||
import { BCMC_CARD } from '../../../utils/constants';
|
||||
|
||||
const cvcSpan = Selector('.card-field .adyen-checkout__field__cvc');
|
||||
const optionalCVCSpan = Selector('.card-field .adyen-checkout__field__cvc--optional');
|
||||
const cvcLabel = Selector('.card-field .adyen-checkout__label__text');
|
||||
const brandingIcon = Selector('.card-field .adyen-checkout__card__cardNumber__brandIcon');
|
||||
|
||||
const requestURL = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`;
|
||||
|
||||
/**
|
||||
* NOTE - we are mocking the response until such time as we have a genuine card,
|
||||
* that's not in our local RegEx, that returns the properties we want to test
|
||||
*/
|
||||
const mockedResponse = {
|
||||
brands: [
|
||||
{
|
||||
brand: 'bcmc', // keep as a recognised card brand (bcmc) until we have a genuine card - to avoid logo loading errors
|
||||
cvcPolicy: 'hidden',
|
||||
enableLuhnCheck: true,
|
||||
showExpiryDate: true,
|
||||
supported: true
|
||||
}
|
||||
],
|
||||
issuingCountryCode: 'US',
|
||||
requestId: null
|
||||
};
|
||||
|
||||
const mock = RequestMock()
|
||||
.onRequestTo(request => {
|
||||
return request.url === requestURL && request.method === 'post';
|
||||
})
|
||||
.respond(
|
||||
(req, res) => {
|
||||
const body = JSON.parse(req.body);
|
||||
mockedResponse.requestId = body.requestId;
|
||||
res.setBody(mockedResponse);
|
||||
},
|
||||
200,
|
||||
{
|
||||
'Access-Control-Allow-Origin': BASE_URL
|
||||
}
|
||||
);
|
||||
|
||||
const TEST_SPEED = 1;
|
||||
|
||||
const iframeSelector = getIframeSelector('.card-field iframe');
|
||||
|
||||
const cardUtils = cu(iframeSelector);
|
||||
|
||||
fixture`Testing a card, as detected by a mock/binLookup, for a response that should indicate hidden cvc)`
|
||||
.page(CARDS_URL)
|
||||
.clientScripts('binLookup.mocks.clientScripts.js')
|
||||
.requestHooks(mock);
|
||||
fixture`Testing a card for a response that should indicate hidden cvc)`.page(CARDS_URL).clientScripts('binLookup.mocks.clientScripts.js');
|
||||
|
||||
test('Test card has hidden cvc field ' + 'then complete date and see card is valid ' + ' then delete card number and see card reset', async t => {
|
||||
// Start, allow time for iframes to load
|
||||
@@ -81,7 +42,7 @@ test('Test card has hidden cvc field ' + 'then complete date and see card is val
|
||||
.notOk();
|
||||
|
||||
// Unknown card
|
||||
await cardUtils.fillCardNumber(t, UNKNOWN_BIN_CARD);
|
||||
await cardUtils.fillCardNumber(t, BCMC_CARD);
|
||||
|
||||
await t
|
||||
// bcmc card icon
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
window.cardConfig = {
|
||||
type: 'scheme',
|
||||
brands: ['mc', 'visa', 'amex', 'bcmc']
|
||||
brands: ['mc', 'visa', 'amex', 'bcmc', 'synchrony_plcc']
|
||||
};
|
||||
|
||||
@@ -1,62 +1,23 @@
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.resolve('../../', '.env') });
|
||||
|
||||
import { Selector, RequestMock } from 'testcafe';
|
||||
import { Selector } from 'testcafe';
|
||||
import { start, getIframeSelector, getIsValid } from '../../../../utils/commonUtils';
|
||||
import cu from '../../../utils/cardUtils';
|
||||
import { BASE_URL, CARDS_URL } from '../../../../pages';
|
||||
import { BIN_LOOKUP_VERSION, TEST_CVC_VALUE, UNKNOWN_BIN_CARD } from '../../../utils/constants';
|
||||
import { CARDS_URL } from '../../../../pages';
|
||||
import { SYNCHRONY_PLCC_NO_LUHN, TEST_CVC_VALUE } from '../../../utils/constants';
|
||||
|
||||
const brandingIcon = Selector('.card-field .adyen-checkout__card__cardNumber__brandIcon');
|
||||
|
||||
const dateSpan = Selector('.card-field .adyen-checkout__card__exp-date__input');
|
||||
|
||||
const requestURL = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`;
|
||||
|
||||
/**
|
||||
* NOTE - we are mocking the response until such time as we have a genuine card,
|
||||
* that's not in our local RegEx, that returns the properties we want to test
|
||||
*/
|
||||
const mockedResponse = {
|
||||
brands: [
|
||||
{
|
||||
brand: 'bcmc', // keep as a recognised card brand (bcmc) until we have a genuine plcc - to avoid logo loading errors
|
||||
cvcPolicy: 'required',
|
||||
enableLuhnCheck: true,
|
||||
showExpiryDate: false,
|
||||
supported: true
|
||||
}
|
||||
],
|
||||
issuingCountryCode: 'US',
|
||||
requestId: null
|
||||
};
|
||||
|
||||
const mock = RequestMock()
|
||||
.onRequestTo(request => {
|
||||
return request.url === requestURL && request.method === 'post';
|
||||
})
|
||||
.respond(
|
||||
(req, res) => {
|
||||
const body = JSON.parse(req.body);
|
||||
mockedResponse.requestId = body.requestId;
|
||||
res.setBody(mockedResponse);
|
||||
},
|
||||
200,
|
||||
{
|
||||
'Access-Control-Allow-Origin': BASE_URL
|
||||
}
|
||||
);
|
||||
|
||||
const TEST_SPEED = 1;
|
||||
|
||||
const iframeSelector = getIframeSelector('.card-field iframe');
|
||||
|
||||
const cardUtils = cu(iframeSelector);
|
||||
|
||||
fixture`Testing a PLCC, as detected by a mock/binLookup, for a response that should indicate hidden expiryDate field)`
|
||||
.page(CARDS_URL)
|
||||
.clientScripts('plcc.clientScripts.js')
|
||||
.requestHooks(mock);
|
||||
fixture`Testing a PLCC, for a response that should indicate hidden expiryDate field)`.page(CARDS_URL).clientScripts('plcc.clientScripts.js');
|
||||
|
||||
test('Test plcc card hides date field ' + 'then complete date and see card is valid ' + ' then delete card number and see card reset', async t => {
|
||||
// Start, allow time for iframes to load
|
||||
@@ -66,13 +27,9 @@ test('Test plcc card hides date field ' + 'then complete date and see card is va
|
||||
await t.expect(brandingIcon.getAttribute('src')).contains('nocard.svg');
|
||||
|
||||
// Unknown card
|
||||
await cardUtils.fillCardNumber(t, UNKNOWN_BIN_CARD);
|
||||
await cardUtils.fillCardNumber(t, SYNCHRONY_PLCC_NO_LUHN);
|
||||
|
||||
await t
|
||||
// bcmc card icon
|
||||
.expect(brandingIcon.getAttribute('src'))
|
||||
.contains('bcmc.svg')
|
||||
|
||||
// hidden date field
|
||||
.expect(dateSpan.filterHidden().exists)
|
||||
.ok();
|
||||
|
||||
@@ -12,8 +12,8 @@ export const AMEX_CARD = '370000000000002';
|
||||
|
||||
export const DUAL_BRANDED_CARD_EXCLUDED = '4001230000000004'; // dual branded visa/star
|
||||
|
||||
export const SYNCHRONY_PLCC_NO_LUHN = '6044100018023838';
|
||||
export const SYNCHRONY_PLCC_WITH_LUHN = '6044141000018769';
|
||||
export const SYNCHRONY_PLCC_NO_LUHN = '6044100018023838'; // also, no date
|
||||
export const SYNCHRONY_PLCC_WITH_LUHN = '6044141000018769'; // also, no date
|
||||
|
||||
export const FAILS_LUHN_CARD = '4111111111111112';
|
||||
|
||||
|
||||
@@ -79,9 +79,7 @@ export class AchElement extends UIElement<AchElementProps> {
|
||||
/>
|
||||
) : (
|
||||
<AchInput
|
||||
ref={ref => {
|
||||
this.componentRef = ref;
|
||||
}}
|
||||
setComponentRef={this.setComponentRef}
|
||||
{...this.props}
|
||||
onChange={this.setState}
|
||||
onSubmit={this.submit}
|
||||
|
||||
@@ -14,6 +14,7 @@ import styles from './AchInput.module.scss';
|
||||
import './AchInput.scss';
|
||||
import { ACHInputDataState, ACHInputProps, ACHInputStateError, ACHInputStateValid } from './types';
|
||||
import StoreDetails from '../../../internal/StoreDetails';
|
||||
import { ComponentMethodsRef } from '../../../types';
|
||||
|
||||
function validateHolderName(holderName, holderNameRequired = false) {
|
||||
if (holderNameRequired) {
|
||||
@@ -84,14 +85,20 @@ function AchInput(props: ACHInputProps) {
|
||||
// Refs
|
||||
const sfp = useRef(null);
|
||||
const billingAddressRef = useRef(null);
|
||||
const setAddressRef = ref => {
|
||||
billingAddressRef.current = ref;
|
||||
};
|
||||
|
||||
const [status, setStatus] = useState('ready');
|
||||
|
||||
this.setStatus = newStatus => {
|
||||
setStatus(newStatus);
|
||||
};
|
||||
/** An object by which to expose 'public' members to the parent UIElement */
|
||||
const achRef = useRef<ComponentMethodsRef>({});
|
||||
// Just call once
|
||||
if (!Object.keys(achRef.current).length) {
|
||||
props.setComponentRef?.(achRef.current);
|
||||
}
|
||||
|
||||
this.showValidation = () => {
|
||||
achRef.current.showValidation = () => {
|
||||
// Validate SecuredFields
|
||||
sfp.current.showValidation();
|
||||
|
||||
@@ -104,6 +111,8 @@ function AchInput(props: ACHInputProps) {
|
||||
if (billingAddressRef.current) billingAddressRef.current.showValidation();
|
||||
};
|
||||
|
||||
achRef.current.setStatus = setStatus;
|
||||
|
||||
useEffect(() => {
|
||||
this.setFocusOn = sfp.current.setFocusOn;
|
||||
this.updateStyles = sfp.current.updateStyles;
|
||||
@@ -172,7 +181,7 @@ function AchInput(props: ACHInputProps) {
|
||||
onChange={handleAddress}
|
||||
allowedCountries={props.billingAddressAllowedCountries}
|
||||
requiredFields={props.billingAddressRequiredFields}
|
||||
ref={billingAddressRef}
|
||||
setComponentRef={setAddressRef}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -58,4 +58,5 @@ export interface ACHInputProps {
|
||||
styles?: StylesObject;
|
||||
type?: string;
|
||||
forceCompat?: boolean;
|
||||
setComponentRef?: (ref) => void;
|
||||
}
|
||||
|
||||
@@ -16,11 +16,10 @@ export class AddressElement extends UIElement {
|
||||
return (
|
||||
<CoreProvider i18n={this.props.i18n} loadingContext={this.props.loadingContext}>
|
||||
<Address
|
||||
ref={ref => {
|
||||
this.componentRef = ref;
|
||||
}}
|
||||
setComponentRef={this.setComponentRef}
|
||||
{...this.props}
|
||||
onChange={this.setState}
|
||||
{...(process.env.NODE_ENV !== 'production' && { payButton: this.payButton })}
|
||||
/>
|
||||
</CoreProvider>
|
||||
);
|
||||
|
||||
@@ -40,7 +40,13 @@ export class BoletoElement extends UIElement {
|
||||
{this.props.reference ? (
|
||||
<BoletoVoucherResult ref={this.handleRef} icon={this.icon} {...this.props} />
|
||||
) : (
|
||||
<BoletoInput ref={this.handleRef} {...this.props} onChange={this.setState} onSubmit={this.submit} payButton={this.payButton} />
|
||||
<BoletoInput
|
||||
setComponentRef={this.handleRef}
|
||||
{...this.props}
|
||||
onChange={this.setState}
|
||||
onSubmit={this.submit}
|
||||
payButton={this.payButton}
|
||||
/>
|
||||
)}
|
||||
</CoreProvider>
|
||||
);
|
||||
|
||||
@@ -8,18 +8,21 @@ import useCoreContext from '../../../../core/Context/useCoreContext';
|
||||
import { BoletoInputDataState } from '../../types';
|
||||
import useForm from '../../../../utils/useForm';
|
||||
import { BrazilPersonalDetail } from '../../../internal/SocialSecurityNumberBrazil/BrazilPersonalDetail';
|
||||
import { ComponentMethodsRef } from '../../../types';
|
||||
|
||||
function BoletoInput(props) {
|
||||
const { i18n } = useCoreContext();
|
||||
const addressRef = useRef(null);
|
||||
const { handleChangeFor, triggerValidation, setSchema, setData, setValid, setErrors, data, valid, errors, isValid } = useForm<
|
||||
BoletoInputDataState
|
||||
>({
|
||||
schema: ['firstName', 'lastName', 'socialSecurityNumber', 'billingAddress', 'shopperEmail'],
|
||||
defaultData: props.data,
|
||||
rules: boletoValidationRules,
|
||||
formatters: boletoFormatters
|
||||
});
|
||||
const setAddressRef = ref => {
|
||||
addressRef.current = ref;
|
||||
};
|
||||
const { handleChangeFor, triggerValidation, setSchema, setData, setValid, setErrors, data, valid, errors, isValid } =
|
||||
useForm<BoletoInputDataState>({
|
||||
schema: ['firstName', 'lastName', 'socialSecurityNumber', 'billingAddress', 'shopperEmail'],
|
||||
defaultData: props.data,
|
||||
rules: boletoValidationRules,
|
||||
formatters: boletoFormatters
|
||||
});
|
||||
|
||||
// Email field toggle
|
||||
const [showingEmail, setShowingEmail] = useState<boolean>(false);
|
||||
@@ -42,15 +45,23 @@ function BoletoInput(props) {
|
||||
};
|
||||
|
||||
const [status, setStatus] = useState('ready');
|
||||
this.setStatus = setStatus;
|
||||
|
||||
this.showValidation = () => {
|
||||
/** An object by which to expose 'public' members to the parent UIElement */
|
||||
const boletoRef = useRef<ComponentMethodsRef>({});
|
||||
// Just call once
|
||||
if (!Object.keys(boletoRef.current).length) {
|
||||
props.setComponentRef?.(boletoRef.current);
|
||||
}
|
||||
|
||||
boletoRef.current.showValidation = () => {
|
||||
triggerValidation();
|
||||
if (props.billingAddressRequired) {
|
||||
addressRef.current.showValidation();
|
||||
}
|
||||
};
|
||||
|
||||
boletoRef.current.setStatus = setStatus;
|
||||
|
||||
useEffect(() => {
|
||||
const billingAddressValid = props.billingAddressRequired ? Boolean(valid.billingAddress) : true;
|
||||
props.onChange({ data, valid, errors, isValid: isValid && billingAddressValid });
|
||||
@@ -71,7 +82,7 @@ function BoletoInput(props) {
|
||||
data={{ ...props.data.billingAddress, country: 'BR' }}
|
||||
onChange={handleAddress}
|
||||
requiredFields={['country', 'street', 'houseNumberOrName', 'postalCode', 'city', 'stateOrProvince']}
|
||||
ref={addressRef}
|
||||
setComponentRef={setAddressRef}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -50,10 +50,6 @@ export class CardElement extends UIElement<CardElementProps> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public setComponentRef = ref => {
|
||||
this.componentRef = ref;
|
||||
};
|
||||
|
||||
private setClickToPayRef = ref => {
|
||||
this.clickToPayRef = ref;
|
||||
};
|
||||
|
||||
@@ -27,11 +27,15 @@ import { getPartialAddressValidationRules } from '../../../internal/Address/vali
|
||||
|
||||
const CardInput: FunctionalComponent<CardInputProps> = props => {
|
||||
const sfp = useRef(null);
|
||||
const billingAddressRef = useRef(null);
|
||||
const isValidating = useRef(false);
|
||||
|
||||
const billingAddressRef = useRef(null);
|
||||
const setAddressRef = ref => {
|
||||
billingAddressRef.current = ref;
|
||||
};
|
||||
|
||||
const cardInputRef = useRef<CardInputRef>({});
|
||||
// Just call once
|
||||
// Just call once to create the object by which we expose the members expected by the parent Card comp
|
||||
if (!Object.keys(cardInputRef.current).length) {
|
||||
props.setComponentRef(cardInputRef.current);
|
||||
}
|
||||
@@ -430,7 +434,7 @@ const CardInput: FunctionalComponent<CardInputProps> = props => {
|
||||
// For Store details
|
||||
handleOnStoreDetails={setStorePaymentMethod}
|
||||
// For Address
|
||||
billingAddressRef={billingAddressRef}
|
||||
setAddressRef={setAddressRef}
|
||||
billingAddress={billingAddress}
|
||||
billingAddressValidationRules={partialAddressSchema && getPartialAddressValidationRules(partialAddressCountry.current)}
|
||||
partialAddressSchema={partialAddressSchema}
|
||||
|
||||
@@ -49,7 +49,7 @@ export const CardFieldsWrapper = ({
|
||||
// Address
|
||||
billingAddress,
|
||||
handleAddress,
|
||||
billingAddressRef,
|
||||
setAddressRef,
|
||||
partialAddressSchema,
|
||||
// For this comp (props passed through from CardInput)
|
||||
amount,
|
||||
@@ -171,7 +171,7 @@ export const CardFieldsWrapper = ({
|
||||
onChange={handleAddress}
|
||||
allowedCountries={billingAddressAllowedCountries}
|
||||
requiredFields={billingAddressRequiredFields}
|
||||
ref={billingAddressRef}
|
||||
setComponentRef={setAddressRef}
|
||||
validationRules={billingAddressValidationRules}
|
||||
specifications={partialAddressSchema}
|
||||
iOSFocusedField={iOSFocusedField}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ValidationRuleResult } from '../../../../utils/Validator/ValidationRule
|
||||
import Specifications from '../../../internal/Address/Specifications';
|
||||
import { AddressSchema, StringObject } from '../../../internal/Address/types';
|
||||
import { CbObjOnError, StylesObject } from '../../../internal/SecuredFields/lib/types';
|
||||
import { ComponentMethodsRef } from '../../../types';
|
||||
|
||||
export interface CardInputValidState {
|
||||
holderName?: boolean;
|
||||
@@ -131,12 +132,10 @@ export interface CardInputState {
|
||||
showSocialSecurityNumber?: boolean;
|
||||
}
|
||||
|
||||
export interface CardInputRef {
|
||||
export interface CardInputRef extends ComponentMethodsRef {
|
||||
sfp?: any;
|
||||
setFocusOn?: (who) => void;
|
||||
showValidation?: (who) => void;
|
||||
processBinLookupResponse?: (binLookupResponse: BinLookupResponse, isReset: boolean) => void;
|
||||
setStatus?: any;
|
||||
updateStyles?: (stylesObj: StylesObject) => void;
|
||||
handleUnsupportedCard?: (errObj: CbObjOnError) => boolean;
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ export class DokuElement extends UIElement {
|
||||
/>
|
||||
) : (
|
||||
<DokuInput
|
||||
ref={ref => {
|
||||
this.componentRef = ref;
|
||||
}}
|
||||
setComponentRef={this.setComponentRef}
|
||||
{...this.props}
|
||||
onChange={this.setState}
|
||||
onSubmit={this.submit}
|
||||
|
||||
@@ -2,18 +2,31 @@ import { h } from 'preact';
|
||||
import { useRef, useState } from 'preact/hooks';
|
||||
import PersonalDetails from '../../../internal/PersonalDetails/PersonalDetails';
|
||||
import useCoreContext from '../../../../core/Context/useCoreContext';
|
||||
import { ComponentMethodsRef } from '../../../types';
|
||||
|
||||
export default function DokuInput(props) {
|
||||
const personalDetailsRef = useRef(null);
|
||||
const setPersonalDetailsRef = ref => {
|
||||
personalDetailsRef.current = ref;
|
||||
};
|
||||
|
||||
const { i18n } = useCoreContext();
|
||||
|
||||
const [status, setStatus] = useState('ready');
|
||||
this.setStatus = setStatus;
|
||||
|
||||
this.showValidation = () => {
|
||||
if (personalDetailsRef.current) personalDetailsRef.current.showValidation();
|
||||
/** An object by which to expose 'public' members to the parent UIElement */
|
||||
const dokuRef = useRef<ComponentMethodsRef>({});
|
||||
// Just call once
|
||||
if (!Object.keys(dokuRef.current).length) {
|
||||
props.setComponentRef?.(dokuRef.current);
|
||||
}
|
||||
|
||||
dokuRef.current.showValidation = () => {
|
||||
personalDetailsRef.current?.showValidation();
|
||||
};
|
||||
|
||||
dokuRef.current.setStatus = setStatus;
|
||||
|
||||
return (
|
||||
<div className="adyen-checkout__doku-input__field">
|
||||
<PersonalDetails
|
||||
@@ -21,7 +34,7 @@ export default function DokuInput(props) {
|
||||
requiredFields={['firstName', 'lastName', 'shopperEmail']}
|
||||
onChange={props.onChange}
|
||||
namePrefix="doku"
|
||||
ref={personalDetailsRef}
|
||||
setComponentRef={setPersonalDetailsRef}
|
||||
/>
|
||||
|
||||
{props.showPayButton && props.payButton({ status, label: i18n.get('confirmPurchase') })}
|
||||
|
||||
@@ -55,9 +55,7 @@ export class EcontextElement extends UIElement<EcontextElementProps> {
|
||||
/>
|
||||
) : (
|
||||
<EcontextInput
|
||||
ref={ref => {
|
||||
this.componentRef = ref;
|
||||
}}
|
||||
setComponentRef={this.setComponentRef}
|
||||
{...this.props}
|
||||
onChange={this.setState}
|
||||
onSubmit={this.submit}
|
||||
|
||||
@@ -5,6 +5,7 @@ import useCoreContext from '../../../../core/Context/useCoreContext';
|
||||
import { econtextValidationRules } from '../../validate';
|
||||
import { PersonalDetailsSchema } from '../../../../types';
|
||||
import './EcontextInput.scss';
|
||||
import { ComponentMethodsRef } from '../../../types';
|
||||
|
||||
interface EcontextInputProps {
|
||||
personalDetailsRequired?: boolean;
|
||||
@@ -16,17 +17,28 @@ interface EcontextInputProps {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export default function EcontextInput({ personalDetailsRequired = true, data, onChange, showPayButton, payButton }: EcontextInputProps) {
|
||||
export default function EcontextInput({ personalDetailsRequired = true, data, onChange, showPayButton, payButton, ...props }: EcontextInputProps) {
|
||||
const personalDetailsRef = useRef(null);
|
||||
const setPersonalDetailsRef = ref => {
|
||||
personalDetailsRef.current = ref;
|
||||
};
|
||||
const { i18n } = useCoreContext();
|
||||
|
||||
const [status, setStatus] = useState('ready');
|
||||
this.setStatus = setStatus;
|
||||
|
||||
this.showValidation = () => {
|
||||
if (personalDetailsRef.current) personalDetailsRef.current.showValidation();
|
||||
/** An object by which to expose 'public' members to the parent UIElement */
|
||||
const econtextRef = useRef<ComponentMethodsRef>({});
|
||||
// Just call once
|
||||
if (!Object.keys(econtextRef.current).length) {
|
||||
props.setComponentRef?.(econtextRef.current);
|
||||
}
|
||||
|
||||
econtextRef.current.showValidation = () => {
|
||||
personalDetailsRef.current?.showValidation();
|
||||
};
|
||||
|
||||
econtextRef.current.setStatus = setStatus;
|
||||
|
||||
return (
|
||||
<div className="adyen-checkout__econtext-input__field">
|
||||
{!!personalDetailsRequired && (
|
||||
@@ -35,7 +47,7 @@ export default function EcontextInput({ personalDetailsRequired = true, data, on
|
||||
requiredFields={['firstName', 'lastName', 'telephoneNumber', 'shopperEmail']}
|
||||
onChange={onChange}
|
||||
namePrefix="econtext"
|
||||
ref={personalDetailsRef}
|
||||
setComponentRef={setPersonalDetailsRef}
|
||||
validationRules={econtextValidationRules}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -16,11 +16,10 @@ export class PersonalDetailsElement extends UIElement {
|
||||
return (
|
||||
<CoreProvider i18n={this.props.i18n} loadingContext={this.props.loadingContext}>
|
||||
<PersonalDetails
|
||||
ref={ref => {
|
||||
this.componentRef = ref;
|
||||
}}
|
||||
setComponentRef={this.setComponentRef}
|
||||
{...this.props}
|
||||
onChange={this.setState}
|
||||
{...(process.env.NODE_ENV !== 'production' && { payButton: this.payButton })}
|
||||
/>
|
||||
</CoreProvider>
|
||||
);
|
||||
|
||||
@@ -130,10 +130,7 @@ export class UIElement<P extends UIElementProps = any> extends BaseElement<P> im
|
||||
}
|
||||
|
||||
private submitAdditionalDetails(data): Promise<void> {
|
||||
return this._parentInstance.session
|
||||
.submitDetails(data)
|
||||
.then(this.handleResponse)
|
||||
.catch(this.handleError);
|
||||
return this._parentInstance.session.submitDetails(data).then(this.handleResponse).catch(this.handleError);
|
||||
}
|
||||
|
||||
protected handleError = (error: AdyenCheckoutError): void => {
|
||||
@@ -228,6 +225,10 @@ export class UIElement<P extends UIElementProps = any> extends BaseElement<P> im
|
||||
return this.elementRef._parentInstance.update(options);
|
||||
}
|
||||
|
||||
public setComponentRef = ref => {
|
||||
this.componentRef = ref;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current validation status of the element
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@ import CoreProvider from '../../../core/Context/CoreProvider';
|
||||
import { OpenInvoiceProps } from '../../internal/OpenInvoice/types';
|
||||
import { AddressSpecifications } from '../../internal/Address/types';
|
||||
|
||||
export interface OpenInvoiceContainerProps extends Partial<OpenInvoiceProps>{
|
||||
export interface OpenInvoiceContainerProps extends Partial<OpenInvoiceProps> {
|
||||
consentCheckboxLabel?: h.JSX.Element;
|
||||
billingAddressRequiredFields?: string[];
|
||||
billingAddressSpecification?: AddressSpecifications;
|
||||
@@ -87,9 +87,7 @@ export default class OpenInvoiceContainer extends UIElement<OpenInvoiceContainer
|
||||
return (
|
||||
<CoreProvider i18n={this.props.i18n} loadingContext={this.props.loadingContext}>
|
||||
<OpenInvoice
|
||||
ref={ref => {
|
||||
this.componentRef = ref;
|
||||
}}
|
||||
setComponentRef={this.setComponentRef}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
onChange={this.setState}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { h } from 'preact';
|
||||
import { useEffect, useMemo } from 'preact/hooks';
|
||||
import { Fragment, h } from 'preact';
|
||||
import { useEffect, useMemo, useRef } from 'preact/hooks';
|
||||
import Fieldset from '../FormFields/Fieldset';
|
||||
import ReadOnlyAddress from './components/ReadOnlyAddress';
|
||||
import { getAddressValidationRules } from './validate';
|
||||
@@ -11,9 +11,21 @@ import useForm from '../../../utils/useForm';
|
||||
import Specifications from './Specifications';
|
||||
import { ADDRESS_SCHEMA, FALLBACK_VALUE } from './constants';
|
||||
import { getMaxLengthByFieldAndCountry } from '../../../utils/validator-utils';
|
||||
import useCoreContext from '../../../core/Context/useCoreContext';
|
||||
import { ComponentMethodsRef } from '../../types';
|
||||
|
||||
export default function Address(props: AddressProps) {
|
||||
const { i18n } = useCoreContext();
|
||||
|
||||
const { label = '', requiredFields, visibility, iOSFocusedField = null } = props;
|
||||
|
||||
/** An object by which to expose 'public' members to the parent UIElement */
|
||||
const addressRef = useRef<ComponentMethodsRef>({});
|
||||
// Just call once
|
||||
if (!Object.keys(addressRef.current).length) {
|
||||
props.setComponentRef?.(addressRef.current);
|
||||
}
|
||||
|
||||
const specifications = useMemo(() => new Specifications(props.specifications), [props.specifications]);
|
||||
|
||||
const requiredFieldsSchema = specifications.getAddressSchemaForCountryFlat(props.countryCode).filter(field => requiredFields.includes(field));
|
||||
@@ -25,6 +37,11 @@ export default function Address(props: AddressProps) {
|
||||
formatters: addressFormatters
|
||||
});
|
||||
|
||||
// Expose method expected by (parent) Address.tsx
|
||||
addressRef.current.showValidation = () => {
|
||||
triggerValidation();
|
||||
};
|
||||
|
||||
/**
|
||||
* For iOS: iOSFocusedField is the name of the element calling for other elements to be disabled
|
||||
* - so if it is set (meaning we are in iOS *and* an input has been focussed) only enable the field that corresponds to this element
|
||||
@@ -81,8 +98,6 @@ export default function Address(props: AddressProps) {
|
||||
props.onChange({ data: processedData, valid, errors, isValid });
|
||||
}, [data, valid, errors, isValid]);
|
||||
|
||||
this.showValidation = triggerValidation;
|
||||
|
||||
if (visibility === 'hidden') return null;
|
||||
if (visibility === 'readOnly') return <ReadOnlyAddress data={data} label={label} />;
|
||||
|
||||
@@ -118,9 +133,13 @@ export default function Address(props: AddressProps) {
|
||||
const addressSchema = specifications.getAddressSchemaForCountry(data.country);
|
||||
|
||||
return (
|
||||
<Fieldset classNameModifiers={[label]} label={label}>
|
||||
{addressSchema.map(field => (field instanceof Array ? getWrapper(field) : getComponent(field, {})))}
|
||||
</Fieldset>
|
||||
<Fragment>
|
||||
<Fieldset classNameModifiers={[label]} label={label}>
|
||||
{addressSchema.map(field => (field instanceof Array ? getWrapper(field) : getComponent(field, {})))}
|
||||
</Fieldset>
|
||||
{/* Needed to easily test when showValidation is called */}
|
||||
{process.env.NODE_ENV !== 'production' && props.showPayButton && props.payButton({ label: i18n.get('continue') })}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ export interface AddressProps {
|
||||
visibility?: string;
|
||||
overrideSchema?: AddressSpecifications;
|
||||
iOSFocusedField?: string;
|
||||
payButton?: (obj) => {};
|
||||
showPayButton?: boolean;
|
||||
setComponentRef?: (ref) => void;
|
||||
}
|
||||
|
||||
export interface AddressStateError {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { h } from 'preact';
|
||||
import { useEffect } from 'preact/hooks';
|
||||
import { useEffect, useRef } from 'preact/hooks';
|
||||
import Fieldset from '../FormFields/Fieldset';
|
||||
import Field from '../FormFields/Field';
|
||||
import ReadOnlyCompanyDetails from './ReadOnlyCompanyDetails';
|
||||
@@ -9,6 +9,7 @@ import useCoreContext from '../../../core/Context/useCoreContext';
|
||||
import { getFormattedData } from './utils';
|
||||
import { CompanyDetailsSchema, CompanyDetailsProps } from './types';
|
||||
import useForm from '../../../utils/useForm';
|
||||
import { ComponentMethodsRef } from '../../types';
|
||||
|
||||
const companyDetailsSchema = ['name', 'registrationNumber'];
|
||||
|
||||
@@ -21,22 +22,34 @@ export default function CompanyDetails(props: CompanyDetailsProps) {
|
||||
defaultData: props.data
|
||||
});
|
||||
|
||||
/** An object by which to expose 'public' members to the parent UIElement */
|
||||
const companyDetailsRef = useRef<ComponentMethodsRef>({});
|
||||
// Just call once
|
||||
if (!Object.keys(companyDetailsRef.current).length) {
|
||||
props.setComponentRef?.(companyDetailsRef.current);
|
||||
}
|
||||
|
||||
// Expose method expected by (parent) Address.tsx
|
||||
companyDetailsRef.current.showValidation = () => {
|
||||
triggerValidation();
|
||||
};
|
||||
|
||||
const generateFieldName = (name: string): string => `${namePrefix ? `${namePrefix}.` : ''}${name}`;
|
||||
|
||||
const eventHandler = (mode: string): Function => (e: Event): void => {
|
||||
const { name } = e.target as HTMLInputElement;
|
||||
const key = name.split(`${namePrefix}.`).pop();
|
||||
const eventHandler =
|
||||
(mode: string): Function =>
|
||||
(e: Event): void => {
|
||||
const { name } = e.target as HTMLInputElement;
|
||||
const key = name.split(`${namePrefix}.`).pop();
|
||||
|
||||
handleChangeFor(key, mode)(e);
|
||||
};
|
||||
handleChangeFor(key, mode)(e);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const formattedData = getFormattedData(data);
|
||||
props.onChange({ data: formattedData, valid, errors, isValid });
|
||||
}, [data, valid, errors, isValid]);
|
||||
|
||||
this.showValidation = triggerValidation;
|
||||
|
||||
if (visibility === 'hidden') return null;
|
||||
if (visibility === 'readOnly') return <ReadOnlyCompanyDetails {...props} data={data} />;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface CompanyDetailsProps {
|
||||
readonly?: boolean;
|
||||
ref?: any;
|
||||
validationRules?: ValidatorRules;
|
||||
setComponentRef?: (ref) => void;
|
||||
}
|
||||
|
||||
export interface ReadOnlyCompanyDetailsProps {
|
||||
|
||||
@@ -5,6 +5,11 @@ import { mock } from 'jest-mock-extended';
|
||||
import { OpenInvoiceProps } from './types';
|
||||
import { FieldsetVisibility } from '../../../types';
|
||||
|
||||
let componentRef;
|
||||
const setComponentRef = ref => {
|
||||
componentRef = ref;
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
data: { personalDetails: {}, billingAddress: {}, deliveryAddress: {} },
|
||||
@@ -12,7 +17,8 @@ const defaultProps = {
|
||||
personalDetails: 'editable' as FieldsetVisibility,
|
||||
billingAddress: 'editable' as FieldsetVisibility,
|
||||
deliveryAddress: 'editable' as FieldsetVisibility
|
||||
}
|
||||
},
|
||||
setComponentRef: setComponentRef
|
||||
};
|
||||
|
||||
describe('OpenInvoice', () => {
|
||||
@@ -77,7 +83,7 @@ describe('OpenInvoice', () => {
|
||||
const payButton = jest.fn();
|
||||
const wrapper = getWrapper({ showPayButton: true, payButton });
|
||||
const status = 'loading';
|
||||
wrapper.instance().setStatus(status);
|
||||
componentRef.setStatus(status);
|
||||
wrapper.update();
|
||||
expect(payButton).toHaveBeenCalledWith(expect.objectContaining({ status }));
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { h, createRef } from 'preact';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { h } from 'preact';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import useCoreContext from '../../../core/Context/useCoreContext';
|
||||
import CompanyDetails from '../CompanyDetails';
|
||||
import PersonalDetails from '../PersonalDetails';
|
||||
@@ -17,16 +17,30 @@ import {
|
||||
} from './types';
|
||||
import './OpenInvoice.scss';
|
||||
import IbanInput from '../IbanInput';
|
||||
import { ComponentMethodsRef } from '../../types';
|
||||
|
||||
export default function OpenInvoice(props: OpenInvoiceProps) {
|
||||
const { countryCode, visibility } = props;
|
||||
const { i18n } = useCoreContext();
|
||||
|
||||
/** An object by which to expose 'public' members to the parent UIElement */
|
||||
const openInvoiceRef = useRef<ComponentMethodsRef>({});
|
||||
// Just call once
|
||||
if (!Object.keys(openInvoiceRef.current).length) {
|
||||
props.setComponentRef?.(openInvoiceRef.current);
|
||||
}
|
||||
|
||||
const initialActiveFieldsets: OpenInvoiceActiveFieldsets = getInitialActiveFieldsets(visibility, props.data);
|
||||
const [activeFieldsets, setActiveFieldsets] = useState<OpenInvoiceActiveFieldsets>(initialActiveFieldsets);
|
||||
const fieldsetsRefs: OpenInvoiceFieldsetsRefs = fieldsetsSchema.reduce((acc, fieldset) => {
|
||||
acc[fieldset] = createRef();
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const { current: fieldsetsRefs } = useRef<OpenInvoiceFieldsetsRefs>(
|
||||
fieldsetsSchema.reduce((acc, fieldset) => {
|
||||
acc[fieldset] = ref => {
|
||||
fieldsetsRefs[fieldset].current = ref;
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const checkFieldsets = () => Object.keys(activeFieldsets).every(fieldset => !activeFieldsets[fieldset] || !!valid[fieldset]);
|
||||
const hasConsentCheckbox = !!props.consentCheckboxLabel;
|
||||
@@ -41,7 +55,18 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
|
||||
const [valid, setValid] = useState<OpenInvoiceStateValid>({});
|
||||
const [status, setStatus] = useState('ready');
|
||||
|
||||
this.setStatus = setStatus;
|
||||
// Expose methods expected by parent
|
||||
openInvoiceRef.current.showValidation = () => {
|
||||
fieldsetsSchema.forEach(fieldset => {
|
||||
if (fieldsetsRefs[fieldset].current) fieldsetsRefs[fieldset].current.showValidation();
|
||||
});
|
||||
|
||||
setErrors({
|
||||
...(hasConsentCheckbox && { consentCheckbox: !data.consentCheckbox })
|
||||
});
|
||||
};
|
||||
|
||||
openInvoiceRef.current.setStatus = setStatus;
|
||||
|
||||
useEffect(() => {
|
||||
const fieldsetsAreValid: boolean = checkFieldsets();
|
||||
@@ -72,16 +97,6 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
|
||||
setErrors(prevErrors => ({ ...prevErrors, consentCheckbox: !checked }));
|
||||
};
|
||||
|
||||
this.showValidation = () => {
|
||||
fieldsetsSchema.forEach(fieldset => {
|
||||
if (fieldsetsRefs[fieldset].current) fieldsetsRefs[fieldset].current.showValidation();
|
||||
});
|
||||
|
||||
setErrors({
|
||||
...(hasConsentCheckbox && { consentCheckbox: !data.consentCheckbox })
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="adyen-checkout__open-invoice">
|
||||
{activeFieldsets.companyDetails && (
|
||||
@@ -89,7 +104,7 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
|
||||
data={props.data.companyDetails}
|
||||
label="companyDetails"
|
||||
onChange={handleFieldset('companyDetails')}
|
||||
ref={fieldsetsRefs.companyDetails}
|
||||
setComponentRef={fieldsetsRefs.companyDetails}
|
||||
visibility={visibility.companyDetails}
|
||||
/>
|
||||
)}
|
||||
@@ -100,7 +115,7 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
|
||||
requiredFields={props.personalDetailsRequiredFields}
|
||||
label="personalDetails"
|
||||
onChange={handleFieldset('personalDetails')}
|
||||
ref={fieldsetsRefs.personalDetails}
|
||||
setComponentRef={fieldsetsRefs.personalDetails}
|
||||
visibility={visibility.personalDetails}
|
||||
/>
|
||||
)}
|
||||
@@ -124,7 +139,7 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
|
||||
data={data.billingAddress}
|
||||
label="billingAddress"
|
||||
onChange={handleFieldset('billingAddress')}
|
||||
ref={fieldsetsRefs.billingAddress}
|
||||
setComponentRef={fieldsetsRefs.billingAddress}
|
||||
visibility={visibility.billingAddress}
|
||||
/>
|
||||
)}
|
||||
@@ -146,7 +161,7 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
|
||||
data={data.deliveryAddress}
|
||||
label="deliveryAddress"
|
||||
onChange={handleFieldset('deliveryAddress')}
|
||||
ref={fieldsetsRefs.deliveryAddress}
|
||||
setComponentRef={fieldsetsRefs.deliveryAddress}
|
||||
visibility={visibility.deliveryAddress}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AddressData, FieldsetVisibility, PersonalDetailsSchema } from '../../../types';
|
||||
import { CompanyDetailsSchema } from '../CompanyDetails/types';
|
||||
import { AddressSpecifications } from '../Address/types';
|
||||
import {UIElementProps} from "../../types";
|
||||
import UIElement from "../../UIElement";
|
||||
import { UIElementProps } from '../../types';
|
||||
import UIElement from '../../UIElement';
|
||||
|
||||
export interface OpenInvoiceVisibility {
|
||||
companyDetails?: FieldsetVisibility;
|
||||
@@ -13,12 +13,12 @@ export interface OpenInvoiceVisibility {
|
||||
}
|
||||
|
||||
export interface BankDetailsSchema {
|
||||
countryCode?: string,
|
||||
ibanNumber?: any,
|
||||
ownerName?: string
|
||||
countryCode?: string;
|
||||
ibanNumber?: any;
|
||||
ownerName?: string;
|
||||
}
|
||||
|
||||
export interface OpenInvoiceProps extends UIElementProps{
|
||||
export interface OpenInvoiceProps extends UIElementProps {
|
||||
allowedCountries?: string[];
|
||||
consentCheckboxLabel: any;
|
||||
countryCode?: string;
|
||||
@@ -27,7 +27,7 @@ export interface OpenInvoiceProps extends UIElementProps{
|
||||
personalDetails?: PersonalDetailsSchema;
|
||||
billingAddress?: AddressData;
|
||||
deliveryAddress?: AddressData;
|
||||
bankAccount?: BankDetailsSchema
|
||||
bankAccount?: BankDetailsSchema;
|
||||
};
|
||||
onChange: (state: any, element?: UIElement) => void;
|
||||
payButton: any;
|
||||
@@ -36,6 +36,7 @@ export interface OpenInvoiceProps extends UIElementProps{
|
||||
personalDetailsRequiredFields?: string[];
|
||||
billingAddressRequiredFields?: string[];
|
||||
billingAddressSpecification?: AddressSpecifications;
|
||||
setComponentRef?: (ref) => void;
|
||||
}
|
||||
|
||||
export interface OpenInvoiceStateData {
|
||||
@@ -43,7 +44,7 @@ export interface OpenInvoiceStateData {
|
||||
personalDetails?: PersonalDetailsSchema;
|
||||
billingAddress?: AddressData;
|
||||
deliveryAddress?: AddressData;
|
||||
bankAccount?: BankDetailsSchema
|
||||
bankAccount?: BankDetailsSchema;
|
||||
consentCheckbox?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { h } from 'preact';
|
||||
import { useEffect, useMemo } from 'preact/hooks';
|
||||
import { Fragment, h } from 'preact';
|
||||
import { useEffect, useMemo, useRef } from 'preact/hooks';
|
||||
import Fieldset from '../FormFields/Fieldset';
|
||||
import Field from '../FormFields/Field';
|
||||
import ReadOnlyPersonalDetails from './ReadOnlyPersonalDetails';
|
||||
@@ -12,6 +12,7 @@ import { PersonalDetailsSchema } from '../../../types';
|
||||
import { getFormattedData } from './utils';
|
||||
import useForm from '../../../utils/useForm';
|
||||
import './PersonalDetails.scss';
|
||||
import { ComponentMethodsRef } from '../../types';
|
||||
|
||||
const personalDetailsSchema = ['firstName', 'lastName', 'gender', 'dateOfBirth', 'shopperEmail', 'telephoneNumber'];
|
||||
|
||||
@@ -19,6 +20,14 @@ export default function PersonalDetails(props: PersonalDetailsProps) {
|
||||
const { label = '', namePrefix, placeholders, requiredFields, visibility } = props;
|
||||
|
||||
const { i18n } = useCoreContext();
|
||||
|
||||
/** An object by which to expose 'public' members to the parent UIElement */
|
||||
const personalDetailsRef = useRef<ComponentMethodsRef>({});
|
||||
// Just call once
|
||||
if (!Object.keys(personalDetailsRef.current).length) {
|
||||
props.setComponentRef?.(personalDetailsRef.current);
|
||||
}
|
||||
|
||||
const isDateInputSupported = useMemo(checkDateInputSupport, []);
|
||||
const { handleChangeFor, triggerValidation, data, valid, errors, isValid } = useForm<PersonalDetailsSchema>({
|
||||
schema: requiredFields,
|
||||
@@ -27,13 +36,20 @@ export default function PersonalDetails(props: PersonalDetailsProps) {
|
||||
defaultData: props.data
|
||||
});
|
||||
|
||||
const eventHandler = (mode: string): Function => (e: Event): void => {
|
||||
const { name } = e.target as HTMLInputElement;
|
||||
const key = name.split(`${namePrefix}.`).pop();
|
||||
|
||||
handleChangeFor(key, mode)(e);
|
||||
// Expose method expected by (parent) PersonalDetails.tsx
|
||||
personalDetailsRef.current.showValidation = () => {
|
||||
triggerValidation();
|
||||
};
|
||||
|
||||
const eventHandler =
|
||||
(mode: string): Function =>
|
||||
(e: Event): void => {
|
||||
const { name } = e.target as HTMLInputElement;
|
||||
const key = name.split(`${namePrefix}.`).pop();
|
||||
|
||||
handleChangeFor(key, mode)(e);
|
||||
};
|
||||
|
||||
const generateFieldName = (name: string): string => `${namePrefix ? `${namePrefix}.` : ''}${name}`;
|
||||
const getErrorMessage = error => (error && error.errorMessage ? i18n.get(error.errorMessage) : !!error);
|
||||
|
||||
@@ -42,136 +58,138 @@ export default function PersonalDetails(props: PersonalDetailsProps) {
|
||||
props.onChange({ data: formattedData, valid, errors, isValid });
|
||||
}, [data, valid, errors, isValid]);
|
||||
|
||||
this.showValidation = triggerValidation;
|
||||
|
||||
if (visibility === 'hidden') return null;
|
||||
if (visibility === 'readOnly') return <ReadOnlyPersonalDetails {...props} data={data} />;
|
||||
|
||||
return (
|
||||
<Fieldset classNameModifiers={['personalDetails']} label={label}>
|
||||
{requiredFields.includes('firstName') && (
|
||||
<Field
|
||||
label={i18n.get('firstName')}
|
||||
classNameModifiers={['col-50', 'firstName']}
|
||||
errorMessage={getErrorMessage(errors.firstName)}
|
||||
name={'firstName'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('text', {
|
||||
name: generateFieldName('firstName'),
|
||||
value: data.firstName,
|
||||
classNameModifiers: ['firstName'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.firstName,
|
||||
spellCheck: false,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
<Fragment>
|
||||
<Fieldset classNameModifiers={['personalDetails']} label={label}>
|
||||
{requiredFields.includes('firstName') && (
|
||||
<Field
|
||||
label={i18n.get('firstName')}
|
||||
classNameModifiers={['col-50', 'firstName']}
|
||||
errorMessage={getErrorMessage(errors.firstName)}
|
||||
name={'firstName'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('text', {
|
||||
name: generateFieldName('firstName'),
|
||||
value: data.firstName,
|
||||
classNameModifiers: ['firstName'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.firstName,
|
||||
spellCheck: false,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{requiredFields.includes('lastName') && (
|
||||
<Field
|
||||
label={i18n.get('lastName')}
|
||||
classNameModifiers={['col-50', 'lastName']}
|
||||
errorMessage={getErrorMessage(errors.lastName)}
|
||||
name={'lastName'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('text', {
|
||||
name: generateFieldName('lastName'),
|
||||
value: data.lastName,
|
||||
classNameModifiers: ['lastName'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.lastName,
|
||||
spellCheck: false,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
{requiredFields.includes('lastName') && (
|
||||
<Field
|
||||
label={i18n.get('lastName')}
|
||||
classNameModifiers={['col-50', 'lastName']}
|
||||
errorMessage={getErrorMessage(errors.lastName)}
|
||||
name={'lastName'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('text', {
|
||||
name: generateFieldName('lastName'),
|
||||
value: data.lastName,
|
||||
classNameModifiers: ['lastName'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.lastName,
|
||||
spellCheck: false,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{requiredFields.includes('gender') && (
|
||||
<Field errorMessage={!!errors.gender} classNameModifiers={['gender']} name={'gender'} useLabelElement={false}>
|
||||
{renderFormField('radio', {
|
||||
i18n,
|
||||
name: generateFieldName('gender'),
|
||||
value: data.gender,
|
||||
items: [
|
||||
{ id: 'MALE', name: 'male' },
|
||||
{ id: 'FEMALE', name: 'female' }
|
||||
],
|
||||
classNameModifiers: ['gender'],
|
||||
onInput: eventHandler('input'),
|
||||
onChange: eventHandler('blur'),
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
{requiredFields.includes('gender') && (
|
||||
<Field errorMessage={!!errors.gender} classNameModifiers={['gender']} name={'gender'} useLabelElement={false}>
|
||||
{renderFormField('radio', {
|
||||
i18n,
|
||||
name: generateFieldName('gender'),
|
||||
value: data.gender,
|
||||
items: [
|
||||
{ id: 'MALE', name: 'male' },
|
||||
{ id: 'FEMALE', name: 'female' }
|
||||
],
|
||||
classNameModifiers: ['gender'],
|
||||
onInput: eventHandler('input'),
|
||||
onChange: eventHandler('blur'),
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{requiredFields.includes('dateOfBirth') && (
|
||||
<Field
|
||||
label={i18n.get('dateOfBirth')}
|
||||
classNameModifiers={['col-50', 'lastName']}
|
||||
errorMessage={getErrorMessage(errors.dateOfBirth)}
|
||||
helper={isDateInputSupported ? null : i18n.get('dateOfBirth.format')}
|
||||
name={'dateOfBirth'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('date', {
|
||||
name: generateFieldName('dateOfBirth'),
|
||||
value: data.dateOfBirth,
|
||||
classNameModifiers: ['dateOfBirth'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.dateOfBirth,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
{requiredFields.includes('dateOfBirth') && (
|
||||
<Field
|
||||
label={i18n.get('dateOfBirth')}
|
||||
classNameModifiers={['col-50', 'lastName']}
|
||||
errorMessage={getErrorMessage(errors.dateOfBirth)}
|
||||
helper={isDateInputSupported ? null : i18n.get('dateOfBirth.format')}
|
||||
name={'dateOfBirth'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('date', {
|
||||
name: generateFieldName('dateOfBirth'),
|
||||
value: data.dateOfBirth,
|
||||
classNameModifiers: ['dateOfBirth'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.dateOfBirth,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{requiredFields.includes('shopperEmail') && (
|
||||
<Field
|
||||
label={i18n.get('shopperEmail')}
|
||||
classNameModifiers={['shopperEmail']}
|
||||
errorMessage={getErrorMessage(errors.shopperEmail)}
|
||||
dir={'ltr'}
|
||||
name={'emailAddress'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('emailAddress', {
|
||||
name: generateFieldName('shopperEmail'),
|
||||
value: data.shopperEmail,
|
||||
classNameModifiers: ['shopperEmail'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.shopperEmail,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
{requiredFields.includes('shopperEmail') && (
|
||||
<Field
|
||||
label={i18n.get('shopperEmail')}
|
||||
classNameModifiers={['shopperEmail']}
|
||||
errorMessage={getErrorMessage(errors.shopperEmail)}
|
||||
dir={'ltr'}
|
||||
name={'emailAddress'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('emailAddress', {
|
||||
name: generateFieldName('shopperEmail'),
|
||||
value: data.shopperEmail,
|
||||
classNameModifiers: ['shopperEmail'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.shopperEmail,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{requiredFields.includes('telephoneNumber') && (
|
||||
<Field
|
||||
label={i18n.get('telephoneNumber')}
|
||||
classNameModifiers={['telephoneNumber']}
|
||||
errorMessage={getErrorMessage(errors.telephoneNumber)}
|
||||
dir={'ltr'}
|
||||
name={'telephoneNumber'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('tel', {
|
||||
name: generateFieldName('telephoneNumber'),
|
||||
value: data.telephoneNumber,
|
||||
classNameModifiers: ['telephoneNumber'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.telephoneNumber,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
</Fieldset>
|
||||
{requiredFields.includes('telephoneNumber') && (
|
||||
<Field
|
||||
label={i18n.get('telephoneNumber')}
|
||||
classNameModifiers={['telephoneNumber']}
|
||||
errorMessage={getErrorMessage(errors.telephoneNumber)}
|
||||
dir={'ltr'}
|
||||
name={'telephoneNumber'}
|
||||
i18n={i18n}
|
||||
>
|
||||
{renderFormField('tel', {
|
||||
name: generateFieldName('telephoneNumber'),
|
||||
value: data.telephoneNumber,
|
||||
classNameModifiers: ['telephoneNumber'],
|
||||
onInput: eventHandler('input'),
|
||||
onBlur: eventHandler('blur'),
|
||||
placeholder: placeholders.telephoneNumber,
|
||||
required: true
|
||||
})}
|
||||
</Field>
|
||||
)}
|
||||
</Fieldset>
|
||||
{/* Needed to easily test when showValidation is called */}
|
||||
{process.env.NODE_ENV !== 'production' && props.showPayButton && props.payButton({ label: i18n.get('continue') })}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ export interface PersonalDetailsProps {
|
||||
readonly?: boolean;
|
||||
ref?: any;
|
||||
validationRules?: ValidatorRules;
|
||||
setComponentRef?: (ref) => void;
|
||||
payButton?: (obj) => {};
|
||||
showPayButton?: boolean;
|
||||
}
|
||||
|
||||
export interface PersonalDetailsStateError {
|
||||
|
||||
@@ -131,3 +131,9 @@ export interface UIElementProps extends BaseElementProps {
|
||||
/** @internal */
|
||||
i18n?: Language;
|
||||
}
|
||||
|
||||
// An interface for the members exposed by a component to its parent UIElement
|
||||
export interface ComponentMethodsRef {
|
||||
showValidation?: () => void;
|
||||
setStatus?(status: UIElementStatus): void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user