All files / app/payment/creditCard CreditCardNumberField.tsx

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 9953x   53x 53x   53x 53x 53x   53x           80x 107x             80x       80x               80x 80x 80x   53x 27x         53x 107x   107x                                 80x 9x 9x 9x 18x 9x     9x 1x     8x   8x     8x         8x     8x     8x   53x   53x  
import creditCardType from 'credit-card-type';
import { FieldProps } from 'formik';
import { max } from 'lodash';
import React, { createRef, memo, useCallback, useMemo, ChangeEventHandler, Fragment, FunctionComponent, PureComponent, ReactNode, RefObject } from 'react';
 
import { TranslatedString } from '../../locale';
import { FormField, TextInput } from '../../ui/form';
import { IconLock } from '../../ui/icon';
 
import formatCreditCardNumber from './formatCreditCardNumber';
 
export interface CreditCardNumberFieldProps {
    name: string;
}
 
const CreditCardNumberField: FunctionComponent<CreditCardNumberFieldProps> = ({ name }) => {
    const renderInput = useCallback(({ field, form }: FieldProps<string>) => (
        <CreditCardNumberInput
            field={ field }
            form={ form }
        />
    ), []);
 
    const labelContent = useMemo(() => (
        <TranslatedString id="payment.credit_card_number_label" />
    ), []);
 
    return <FormField
        additionalClassName="form-field--ccNumber"
        input={ renderInput }
        labelContent={ labelContent }
        name={ name }
    />;
};
 
class CreditCardNumberInput extends PureComponent<FieldProps<string>> {
    private inputRef: RefObject<HTMLInputElement> = createRef();
    private nextSelectionEnd: number = 0;
 
    componentDidUpdate(): void {
        Iif (this.inputRef.current && this.inputRef.current.selectionEnd !== this.nextSelectionEnd) {
            this.inputRef.current.setSelectionRange(this.nextSelectionEnd, this.nextSelectionEnd);
        }
    }
 
    render(): ReactNode {
        const { field } = this.props;
 
        return (
            <Fragment>
                <TextInput
                    { ...field }
                    additionalClassName="has-icon"
                    autoComplete="cc-number"
                    id={ field.name }
                    onChange={ this.handleChange }
                    ref={ this.inputRef }
                    type="tel"
                />
 
                <IconLock />
            </Fragment>
        );
    }
 
    private handleChange: ChangeEventHandler<HTMLInputElement> = event => {
        const separator = ' ';
        const { value = '' } = event.target;
        const { field, form } = this.props;
        const { name, value: previousValue = '' } = field;
        const selectionEnd = this.inputRef.current && this.inputRef.current.selectionEnd;
 
        // Only allow digits and spaces
        if (new RegExp(`[^\\d${separator}]`).test(value)) {
            return form.setFieldValue(name, previousValue);
        }
 
        const maxLength = max(
            creditCardType(value)
                .map(info => max(info.lengths))
        );
 
        const formattedValue = formatCreditCardNumber(
            value.replace(new RegExp(separator, 'g'), '').slice(0, maxLength),
            separator
        );
 
        Iif (selectionEnd === value.length && value.length < formattedValue.length) {
            this.nextSelectionEnd = formattedValue.length;
        } else {
            this.nextSelectionEnd = selectionEnd || 0;
        }
 
        form.setFieldValue(name, formattedValue);
    };
}
 
export default memo(CreditCardNumberField);