"use client" import * as React from "react" import { cn } from "../../lib/utils" import { Input } from "@/components/ui" import { CreditCard } from "lucide-react" // Card type detection const getCardType = (number: string): string => { const patterns = { visa: /^4/, mastercard: /^5[1-5]/, amex: /^3[47]/, discover: /^6(?:011|5)/, diners: /^3(?:0[0-5]|[68])/, jcb: /^35/, } for (const [type, pattern] of Object.entries(patterns)) { if (pattern.test(number)) return type } return "unknown" } // Format card number with spaces const formatCardNumber = (value: string, cardType: string): string => { const cleaned = value.replace(/\s+/g, "") const groups = cardType === "amex" ? [4, 6, 5] : [4, 4, 4, 4] let formatted = "" let position = 0 for (const group of groups) { if (position >= cleaned.length) break if (formatted) formatted += " " formatted += cleaned.slice(position, position + group) position += group } return formatted } // Card Number Input export interface CardNumberInputProps extends Omit, "onChange" | "value"> { value?: string onChange?: (value: string, cardType: string) => void showIcon?: boolean } export const CardNumberInput = React.forwardRef( ({ className, value = "", onChange, showIcon = true, size, ...props }, ref) => { const [cardType, setCardType] = React.useState("unknown") const handleChange = (e: React.ChangeEvent) => { const rawValue = e.target.value.replace(/\D/g, "") const detectedType = getCardType(rawValue) setCardType(detectedType) const maxLength = detectedType === "amex" ? 15 : 16 const truncated = rawValue.slice(0, maxLength) const formatted = formatCardNumber(truncated, detectedType) onChange?.(truncated, detectedType) // Update the input value e.target.value = formatted } return (
{showIcon && ( )}
) } ) CardNumberInput.displayName = "CardNumberInput" // Card Expiry Input export interface CardExpiryInputProps extends Omit, "onChange" | "value"> { value?: string onChange?: (value: string) => void } export const CardExpiryInput = React.forwardRef( ({ className, value = "", onChange, size, ...props }, ref) => { const handleChange = (e: React.ChangeEvent) => { let rawValue = e.target.value.replace(/\D/g, "") if (rawValue.length >= 2) { const month = parseInt(rawValue.slice(0, 2)) if (month > 12) { rawValue = "12" + rawValue.slice(2) } else if (month === 0) { rawValue = "01" + rawValue.slice(2) } } rawValue = rawValue.slice(0, 4) let formatted = rawValue if (rawValue.length >= 2) { formatted = rawValue.slice(0, 2) + "/" + rawValue.slice(2) } onChange?.(rawValue) e.target.value = formatted } const formattedValue = value.length >= 2 ? value.slice(0, 2) + "/" + value.slice(2) : value return ( ) } ) CardExpiryInput.displayName = "CardExpiryInput" // Card CVC Input export interface CardCVCInputProps extends Omit, "onChange" | "value"> { value?: string onChange?: (value: string) => void cardType?: string } export const CardCVCInput = React.forwardRef( ({ className, value = "", onChange, cardType = "unknown", size, ...props }, ref) => { const maxLength = cardType === "amex" ? 4 : 3 const handleChange = (e: React.ChangeEvent) => { const rawValue = e.target.value.replace(/\D/g, "").slice(0, maxLength) onChange?.(rawValue) } return ( ) } ) CardCVCInput.displayName = "CardCVCInput" // Card Zip Input export interface CardZipInputProps extends Omit, "onChange" | "value"> { value?: string onChange?: (value: string) => void format?: "US" | "CA" | "UK" | "other" } export const CardZipInput = React.forwardRef( ({ className, value = "", onChange, format = "US", size, ...props }, ref) => { const handleChange = (e: React.ChangeEvent) => { let rawValue = e.target.value switch (format) { case "US": rawValue = rawValue.replace(/\D/g, "").slice(0, 5) break case "CA": rawValue = rawValue.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 6) if (rawValue.length >= 3) { rawValue = rawValue.slice(0, 3) + " " + rawValue.slice(3) } break case "UK": rawValue = rawValue.toUpperCase().replace(/[^A-Z0-9\s]/g, "").slice(0, 8) break default: rawValue = rawValue.slice(0, 10) } onChange?.(rawValue.replace(/\s/g, "")) e.target.value = rawValue } const getPlaceholder = () => { switch (format) { case "US": return "12345" case "CA": return "K1A 0B1" case "UK": return "SW1A 1AA" default: return "Postal Code" } } return ( ) } ) CardZipInput.displayName = "CardZipInput"