mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
chore: initial Untitled UI Vite scaffold with FontAwesome Pro
This commit is contained in:
121
src/components/base/input/input-payment.tsx
Normal file
121
src/components/base/input/input-payment.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { useControlledState } from "@react-stately/utils";
|
||||
import { HintText } from "@/components/base/input/hint-text";
|
||||
import type { InputBaseProps } from "@/components/base/input/input";
|
||||
import { InputBase, TextField } from "@/components/base/input/input";
|
||||
import { Label } from "@/components/base/input/label";
|
||||
import { AmexIcon, DiscoverIcon, MastercardIcon, UnionPayIcon, VisaIcon } from "@/components/foundations/payment-icons";
|
||||
|
||||
const cardTypes = [
|
||||
{
|
||||
name: "Visa",
|
||||
pattern: /^4[0-9]{3,}$/, // Visa card numbers start with 4 and are 13 or 16 digits long
|
||||
card: "visa",
|
||||
icon: VisaIcon,
|
||||
},
|
||||
{
|
||||
name: "MasterCard",
|
||||
pattern: /^5[1-5][0-9]{2,}$/, // MasterCard numbers start with 51-55 and are 16 digits long
|
||||
card: "mastercard",
|
||||
icon: MastercardIcon,
|
||||
},
|
||||
{
|
||||
name: "American Express",
|
||||
pattern: /^3[47][0-9]{2,}$/, // American Express numbers start with 34 or 37 and are 15 digits long
|
||||
card: "amex",
|
||||
icon: AmexIcon,
|
||||
},
|
||||
{
|
||||
name: "Discover",
|
||||
pattern: /^6(?:011|5[0-9]{2}|4[4-9][0-9])[0-9]{12}$/, // Discover card numbers start with 6011 or 65 and are 16 digits long
|
||||
card: "discover",
|
||||
icon: DiscoverIcon,
|
||||
},
|
||||
{
|
||||
name: "UnionPay",
|
||||
pattern: /^(62|88)[0-9]{14,17}$/, // UnionPay card numbers start with 62 or 88 and are between 15-19 digits long
|
||||
card: "unionpay",
|
||||
icon: UnionPayIcon,
|
||||
},
|
||||
{
|
||||
name: "Unknown",
|
||||
pattern: /.*/, // Fallback pattern for unknown cards
|
||||
card: "unknown",
|
||||
icon: MastercardIcon,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Detect the card type based on the card number.
|
||||
* @param number The card number to detect the type for.
|
||||
* @returns The matching card type object.
|
||||
*/
|
||||
const detectCardType = (number: string) => {
|
||||
// Remove all spaces
|
||||
const sanitizedNumber = number.replace(/\D/g, "");
|
||||
|
||||
// Find the matching card type
|
||||
const card = cardTypes.find((cardType) => cardType.pattern.test(sanitizedNumber));
|
||||
|
||||
return card || cardTypes[cardTypes.length - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Format the card number in groups of 4 digits (i.e. 1234 5678 9012 3456).
|
||||
*/
|
||||
export const formatCardNumber = (number: string) => {
|
||||
// Remove non-numeric characters
|
||||
const cleaned = number.replace(/\D/g, "");
|
||||
|
||||
// Format the card number in groups of 4 digits
|
||||
const match = cleaned.match(/\d{1,4}/g);
|
||||
|
||||
if (match) {
|
||||
return match.join(" ");
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
};
|
||||
|
||||
interface PaymentInputProps extends Omit<InputBaseProps, "icon"> {}
|
||||
|
||||
export const PaymentInput = ({ onChange, value, defaultValue, className, maxLength = 19, label, hint, ...props }: PaymentInputProps) => {
|
||||
const [cardNumber, setCardNumber] = useControlledState(value, defaultValue || "", (value) => {
|
||||
// Remove all non-numeric characters
|
||||
value = value.replace(/\D/g, "");
|
||||
|
||||
onChange?.(value || "");
|
||||
});
|
||||
|
||||
const card = detectCardType(cardNumber);
|
||||
|
||||
return (
|
||||
<TextField
|
||||
aria-label={!label ? props?.placeholder : undefined}
|
||||
{...props}
|
||||
className={className}
|
||||
inputMode="numeric"
|
||||
maxLength={maxLength}
|
||||
value={formatCardNumber(cardNumber)}
|
||||
onChange={setCardNumber}
|
||||
>
|
||||
{({ isDisabled, isInvalid, isRequired }) => (
|
||||
<>
|
||||
{label && <Label isRequired={isRequired}>{label}</Label>}
|
||||
|
||||
<InputBase
|
||||
{...props}
|
||||
isDisabled={isDisabled}
|
||||
isInvalid={isInvalid}
|
||||
icon={card.icon}
|
||||
inputClassName="pl-13"
|
||||
iconClassName="left-2.5 h-6 w-8.5"
|
||||
/>
|
||||
|
||||
{hint && <HintText isInvalid={isInvalid}>{hint}</HintText>}
|
||||
</>
|
||||
)}
|
||||
</TextField>
|
||||
);
|
||||
};
|
||||
|
||||
PaymentInput.displayName = "PaymentInput";
|
||||
Reference in New Issue
Block a user