signal-desktop/ts/hooks/useInputMask.tsx
automated-signal 44da91ec9f
Init card form validation
Co-authored-by: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com>
2025-07-19 09:20:07 +10:00

70 lines
1.8 KiB
TypeScript

// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FormatterToken } from '@signalapp/minimask';
import {
minimask,
type Formatter,
createCreditCardExpirationFormatter,
} from '@signalapp/minimask';
import type { RefObject } from 'react';
import { useEffect } from 'react';
import creditCardType from 'credit-card-type';
import { strictAssert } from '../util/assert';
export function useInputMask(
inputRef: RefObject<HTMLInputElement>,
formatter: Formatter
): void {
useEffect(() => {
strictAssert(inputRef.current, 'Missing input ref');
const input = inputRef.current;
return minimask(input, formatter);
}, [inputRef, formatter]);
}
export const CC_NUMBER_FORMATTER: Formatter = input => {
const [cardType] = creditCardType(input);
const maxLength = cardType != null ? Math.max(...cardType.lengths) : 16;
const gaps = cardType != null ? cardType.gaps : [4, 8, 12];
const tokens: Array<FormatterToken> = [];
let digits = 0;
for (const [index, char] of input.split('').entries()) {
// skip non-digits
if (!/\d/.test(char)) {
continue;
}
// push digits
tokens.push({ char, index, mask: false });
digits += 1;
// insert spaces when needed
if (gaps.includes(digits)) {
tokens.push({ char: ' ', index, mask: true });
}
// ignore any additional chars
if (digits >= maxLength) {
break;
}
}
return tokens;
};
export const CC_EXP_FORMATTER = createCreditCardExpirationFormatter();
// Accept any number of digits, manage max length at the input
export const CC_CVC_FORMATTER: Formatter = input => {
const tokens: Array<FormatterToken> = [];
for (const [index, char] of input.split('').entries()) {
if (/\d/.test(char)) {
tokens.push({ char, index, mask: false });
}
}
return tokens;
};