-
[리액트] 카드 정보 입력 UI & 카드번호, 카드유효기간, 생년월일 유효성검사Java Script & Type Script 2024. 6. 26. 19:41728x90
안녕하세요
참말로 오랜만에 돌아왔어용
신용카드 입력 UI를 만들어봤어요
카드번호, 유효기간, 생년월일 유효성검사식 그리고 UI 입니다
유효한 카드번호 입력시 master, visa, bc 등 메이저 카드사가 string으로 return되는 유틸함수도 아래에 있으니 참고해주세요. 제 UI에는 카드사가 나오진 않습니다. (다만 국내 카드 매입사 롯데, 신한,,, 등은 못가져옵니다 ㅜ_ㅜ 방법 아시는 분은 댓글주세요)
const [cardInfo, setCardInfo] = React.useState({ cardNumber: "", cardCompany: "", cardExpiration: "", cardPW: "", isCorporate: false, dateOfBirth: "", }); const [cardCheck, setCardCheck] = React.useState(""); const [expiryCheck, setExpiryCheck] = React.useState(""); const [birthCheck, setBirthDateCheck] = React.useState("");
필요한 state들을 만들어줍니다.
그리고 UI는 아래의 코드를 반복해서 활용할겁니다.
그대로 사용할 분은 없으실 것 같아 기존에 제가 만들어서 사용하는 스타일링 컴포넌트들도 섞여있어요
... <StCommon.Column $gap="4px"> <GrayRoundBox $error={cardCheck === "INVALID"}> <span>카드번호</span> <BInput placeholder="0000 0000 0000 0000" value={cardInfo.cardNumber} name="cardNumber" onChange={onChange} /> </GrayRoundBox> {cardCheck === "INVALID" && ( <StCommon.Row $gap="4px" $alignCenter> <MdOutlineErrorOutline size={16} color="#CD3232" /> <HintText>카드번호를 올바르게 입력해주세요.</HintText> </StCommon.Row> )} </StCommon.Column> ... const GrayRoundBox = styled.div<{ $error?: boolean }>` display: flex; flex-direction: column; gap: 9px; background-color: ${(props) => (props.$error ? "#fff" : "#f5f5f5")}; border: 1px solid ${(props) => (props.$error ? "#f5c6cb" : "transparent")}; padding: 14px; border-radius: 12px; transition: all 0.3s; span { color: #a7a7a7; font-weight: 400px; font-size: 13px; } `;
onChange함수는 모든 input에서 공통으로 사용할 수 있도록 만들었고, 각 input에게 부여한 name을 구분하여 필요한 검사를 진행하도록했어요.
const onChange = (e: React.ChangeEvent<HTMLElement>) => { const { name, value } = e.target as HTMLInputElement; if (name === "cardExpiration") { expiryOnChange(e as React.ChangeEvent<HTMLInputElement>); return; } if (name === "cardNumber") { ccOnChange(e as React.ChangeEvent<HTMLInputElement>); return; } if (name === "dateOfBirth") { dobChange(e as React.ChangeEvent<HTMLInputElement>); return; } if (name === "cardPW") { const formattedValue = value.replace(/[^0-9]/g, ""); if (formattedValue.length > 2) { return; } setCardInfo({ ...cardInfo, [name]: formattedValue }); return; } setCardInfo({ ...cardInfo, [name]: value }); };
(물론 더 나은 방식이 있을 것 같긴합니다....)
비밀번호는 앞 두자리만 입력하도록해서 따로 함수를 빼진 않고 onChange 내에서 검사를 진행했어요.
그럼 각 검사식도 보실까용
// 카드번호 검사 const ccOnChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { value } = e.target; const numericValue = value.replace(/\D/g, ""); if (numericValue.length > 16) return; // 하이픈 추가 로직 let formattedValue = numericValue; if (numericValue.length > 4) { formattedValue = numericValue.slice(0, 4) + "-" + numericValue.slice(4); } if (numericValue.length > 8) { formattedValue = formattedValue.slice(0, 9) + "-" + formattedValue.slice(9); } if (numericValue.length > 12) { formattedValue = formattedValue.slice(0, 14) + "-" + formattedValue.slice(14); } if (numericValue.length === 16) { const message = cardValidateCheck(numericValue); setCardCheck(message); } else { setCardCheck(""); } setCardInfo({ ...cardInfo, cardNumber: formattedValue }); return; };
cardValidateCheck는 아래의 util함수를 따로 저장해둬서 import해서 씁니다.
이거 옛날에 네이버블로그에서 유물처럼 찾은거라 출처를 못밝히네요..ㅠㅠㅠ
export const cardValidateCheck = (cardnumber: string) => { cardnumber = cardnumber.replace(/[ -]/g, ""); const match = /^(?:(94[0-9]{14})|(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/.exec( cardnumber ); if (match) { const types = ["BC", "Visa", "MasterCard", "Discover", "American Express", "Diners Club", "JCB"]; for (let i = 1; i < match.length; i++) { if (match[i]) { return types[i - 1]; } } return "Unknown card type"; } else { return "INVALID"; } };
// 유효기간 검사 const expiryOnChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { value } = e.target; let formattedValue = value.replace(/[^0-9]/g, ""); // 길이 제한 설정 if (formattedValue.length > 4) { formattedValue = formattedValue.slice(0, 4); } // 슬래시 추가 if (formattedValue.length > 2) { formattedValue = formattedValue.slice(0, 2) + "/" + formattedValue.slice(2); } // 유효성 검사 if (formattedValue.length === 5) { const message = validateExpiryDate(formattedValue); setExpiryCheck(message); } else { setExpiryCheck(""); } setCardInfo({ ...cardInfo, cardExpiration: formattedValue }); return; };
// 생년월일 검사 const dobChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { value } = e.target; // 숫자만 허용하고 나머지는 제거 let formattedValue = value.replace(/[^0-9]/g, ""); // 길이 제한 설정 (YYMMDD) if (formattedValue.length > 6) { formattedValue = formattedValue.slice(0, 6); } // 유효성 검사 if (formattedValue.length === 6) { const message = validateBirthDate(formattedValue); setBirthDateCheck(message); } else { setBirthDateCheck(""); } setCardInfo({ ...cardInfo, dateOfBirth: formattedValue }); return; };
그리고 은근 빡센 생년월일 검사 함수
export const validateExpiryDate = (expiryDate: string): string => { const [month, year] = expiryDate.split("/").map(Number); if (!month || !year) { return "ERROR"; //유효기간을 올바르게 입력해주세요. } if (month < 1 || month > 12) { return "ERROR"; //유효한 월을 입력해주세요 (01-12). } const currentDate = new Date(); const currentYear = currentDate.getFullYear() % 100; // YY 형식으로 현재 연도 얻기 const currentMonth = currentDate.getMonth() + 1; // 월은 0부터 시작하므로 +1 if (year < currentYear || (year === currentYear && month < currentMonth)) { return "ERROR"; //유효기간이 만료되었습니다. } return "PASS"; }; export const validateBirthDate = (birthDate: string): string => { if (birthDate.length !== 6) { return "ERROR"; //생년월일을 올바른 형식으로 입력해주세요 (YYMMDD). } const year = Number(birthDate.slice(0, 2)); const month = Number(birthDate.slice(2, 4)); const day = Number(birthDate.slice(4, 6)); if (month < 1 || month > 12) { return "ERROR"; //유효한 월을 입력해주세요 (01-12). } const maxDays = new Date(2000 + year, month, 0).getDate(); // 2000년대를 기준으로 일수 계산 if (day < 1 || day > maxDays) { return "ERROR"; //유효한 날짜를 입력해주세요. } const currentDate = new Date(); const fullYear = year < currentDate.getFullYear() % 100 ? 2000 + year : 1900 + year; const birthDateObj = new Date(fullYear, month - 1, day); if (birthDateObj > currentDate) { return "ERROR"; //미래의 날짜는 입력할 수 없습니다. } return "PASS"; // 유효한 경우 패스 };
저 주석들로 에러메시지까지 보여줄 수 있어요. 전 에러메시지는 보여주지 않고 에러표시만 할거라 ERROR, PASS로만 return했습니다. 필요한 분들은 활용하셔용
728x90'Java Script & Type Script' 카테고리의 다른 글
자바스크립트 클로저(Closure), 다시 한번 딱대세요. (0) 2025.02.08 [Next.js] App Router의 신기능, Private Folder & Route Group (Next.js 폴더구조 잡기), File (0) 2024.01.28 [React] 디자인패턴 Custom hook Pattern-UI와 비즈니스로직의 분리에 대하여 (0) 2024.01.20 [React] Tanstack react-query 데이터 리패칭의 네가지 방법 (1) 2023.12.28 [클린코드] 추상화, 관심사의 분리,의존성주입 (Java script version) (0) 2023.07.27