import { ChangeEvent, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PATHS } from '@/shared/constants/paths'; import { useLocation } from 'react-router'; import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { IMAGE_ROOT } from '@/shared/constants/common'; import { useExtensionArsApplyMutation } from '@/entities/additional-service/api/ars/use-extension-ars-apply-mutation'; import { HeaderType } from '@/entities/common/model/types'; import { useSetHeaderTitle, useSetHeaderType, useSetFooterMode, useSetOnBack } from '@/widgets/sub-layout/use-sub-layout'; import { ArsPaymentMethod, ExtensionArsApplyParams, ExtensionArsApplyResponse } from '@/entities/additional-service/model/ars/types'; import { ArsRequestSuccessPage } from './request-success-page'; import { useStore } from '@/shared/model/store'; import { snackBar } from '@/shared/lib'; import { NumericFormat, PatternFormat } from 'react-number-format'; import { showAlert } from '@/widgets/show-alert'; import { useKeyboardAware } from '@/shared/lib/hooks/use-keyboard-aware'; export const ArsRequestPage = () => { const { t } = useTranslation(); const { navigate } = useNavigate(); const location = useLocation(); const midOptions = useStore.getState().UserStore.selectOptionsMids const { mid: receivedMid } = location.state || {}; const { mutateAsync: arsApply } = useExtensionArsApplyMutation(); const [mid, setMid] = useState(receivedMid || ''); const [moid, setMoid] = useState(''); const [goodsName, setGoodsName] = useState(''); const [amount, setAmount] = useState(0); const [instmntMonth, setInstmntMonth] = useState('00'); const [buyerName, setBuyerName] = useState(''); const [isComposing, setIsComposing] = useState(false); const [phoneNumber, setPhoneNumber] = useState(''); const [email, setEamil] = useState(''); const [arsPaymentMethod, setArsPaymentMethod] = useState(ArsPaymentMethod.SMS); const [successPageOn, setSuccessPageOn] = useState(false); const [resultMessage, setResultMessage] = useState(''); const { handleInputFocus, keyboardAwarePadding } = useKeyboardAware(); useSetHeaderTitle(t('additionalService.ars.paymentRequest')); useSetHeaderType(HeaderType.LeftArrow); useSetFooterMode(false); useSetOnBack(() => { navigate(PATHS.additionalService.ars.list); }); const callArsApply = () => { let arsApplyParams: ExtensionArsApplyParams = { mid: mid, moid: moid, goodsName: goodsName, amount: amount, instmntMonth: instmntMonth, buyerName: buyerName, phoneNumber: phoneNumber, email: email, arsPaymentMethod: arsPaymentMethod, }; arsApply(arsApplyParams).then((rs: ExtensionArsApplyResponse) => { if(rs.status){ setSuccessPageOn(true); } else{ const errorMessage = rs.error?.message || t('additionalService.ars.requestFailed'); snackBar(`[${t('common.failed')}] ${errorMessage}`); } }).catch((e) => { const errorMsg = e?.response?.data?.message || e?.response?.data?.error?.message || t('additionalService.ars.requestFailed'); if (e.response?.data?.error?.root !== "SystemErrorCode") { snackBar(`[${t('common.failed')}] ${errorMsg}`); } else{ showAlert(`[${t('common.failed')}] ${errorMsg}`) } }); }; const onClickToRequest = () => { callArsApply(); }; const isValidPhoneNumber = (phone: string) => { // 한국 휴대폰 번호: 010, 011, 016, 017, 018, 019로 시작, 10-11자리 const phoneRegex = /^01[0|1|6|7|8|9][0-9]{7,8}$/; return phoneRegex.test(phone); }; const isValidEmail = (email: string) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; const isFormValid = () => { return ( mid.trim() !== '' && moid.trim() !== '' && goodsName.trim() !== '' && amount > 0 && buyerName.trim() !== '' && isValidPhoneNumber(phoneNumber) ); }; const getMaskedPhoneNumber = (phone: string) => { // 7자리 이하면 그대로 표시 if (phone.length <= 7) { return phone; } // 7자리 초과면 마지막 4자리 마스킹 const visiblePart = phone.slice(0, -4); return visiblePart + '****'; }; const getMaskedEmail = (email: string) => { const atIndex = email.indexOf('@'); // @가 없거나 앞부분이 2자리 이하면 그대로 표시 if (atIndex === -1 || atIndex <= 2) { return email; } // 앞 2자리만 보이고 나머지는 마스킹 const visiblePart = email.slice(0, 2); const domainPart = email.slice(atIndex); const maskedLength = atIndex - 2; const masked = '*'.repeat(maskedLength); return visiblePart + masked + domainPart; }; const getMaskedName = (name: string) => { const length = name.length; // 1글자면 그대로 표시 if (length <= 1) { return name; } // 2글자면 첫 글자만 표시 (한글: 홍*, 영문: j*) if (length === 2) { return name[0] + '*'; } // 3글자면 가운데 마스킹 (한글: 홍*동, 영문: j*n) if (length === 3) { return name[0] + '*' + name[2]; } // 4글자 이상이면 첫글자와 마지막글자만 표시, 나머지는 마스킹 (한글: 선**녀, 영문: j***n) const firstChar = name[0]; const lastChar = name[length - 1]; const maskedLength = length - 2; const masked = '*'.repeat(maskedLength); return firstChar + masked + lastChar; }; const getArsPaymentMethodBtns = () => { let rs = []; rs.push(
); return rs; }; return ( <>
{t('additionalService.ars.merchant')} *
{t('additionalService.ars.orderNumber')} *
) => setMoid(e.target.value)} onFocus={handleInputFocus} />
{t('additionalService.ars.productName')} *
) => setGoodsName(e.target.value)} onFocus={handleInputFocus} />
{t('additionalService.ars.amount')} *
{ const { floatValue } = values; setAmount(floatValue ?? 0); }} onFocus={handleInputFocus} >
{t('additionalService.ars.installmentPeriod')} *
{t('additionalService.ars.buyerName')} *
{ // 스페이스바 입력 차단 if (e.key === ' ') { e.preventDefault(); } // 백스페이스 처리 (composition 중이 아닐 때만) else if (e.key === 'Backspace' && !isComposing) { e.preventDefault(); setBuyerName(buyerName.slice(0, -1)); } // 영문자 입력 처리 (composition 중이 아닐 때만) else if (!isComposing && e.key.length === 1 && !e.ctrlKey && !e.metaKey) { if (/^[a-zA-Z]$/.test(e.key)) { e.preventDefault(); setBuyerName(buyerName + e.key); } // 한글이 아닌 다른 문자는 차단 else if (!/^[ㄱ-ㅎㅏ-ㅣ가-힣]$/.test(e.key)) { e.preventDefault(); } } }} onCompositionStart={() => setIsComposing(true)} onCompositionEnd={(e: any) => { setIsComposing(false); // 스페이스 제거 후 저장 const valueWithoutSpace = e.target.value.replace(/\s/g, ''); setBuyerName(valueWithoutSpace); }} onChange={(e: ChangeEvent) => { // composition 중에는 스페이스 제거 if (isComposing) { const valueWithoutSpace = e.target.value.replace(/\s/g, ''); setBuyerName(valueWithoutSpace); } }} onFocus={handleInputFocus} />
{t('additionalService.ars.phoneNumber')} *
{ // 백스페이스 처리 if (e.key === 'Backspace') { e.preventDefault(); setPhoneNumber(phoneNumber.slice(0, -1)); } // 숫자 입력 처리 else if (/^[0-9]$/.test(e.key)) { e.preventDefault(); if (phoneNumber.length < 11) { setPhoneNumber(phoneNumber + e.key); } } }} onChange={(e: ChangeEvent) => { // Android 등에서 한글 키보드로 입력 시 대비 const input = e.target.value; const digitsOnly = input.replace(/\*/g, '').replace(/[^0-9]/g, ''); if (digitsOnly.length <= 11) { setPhoneNumber(digitsOnly); } }} className={phoneNumber && !isValidPhoneNumber(phoneNumber) ? 'error' : ''} inputMode="numeric" pattern="[0-9]*" maxLength={15} onFocus={handleInputFocus} />
{t('additionalService.ars.email')}
{ // 백스페이스 처리 if (e.key === 'Backspace') { e.preventDefault(); setEamil(email.slice(0, -1)); } // 영문, 숫자, @, ., - 등 이메일에 허용되는 문자만 입력 가능 else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) { // 한글 및 특수문자 차단 (이메일에 사용 가능한 문자만 허용) if (/^[a-zA-Z0-9@._-]$/.test(e.key)) { e.preventDefault(); setEamil(email + e.key); } else { // 한글 등 허용되지 않는 문자는 입력 차단 e.preventDefault(); } } }} onChange={(e: ChangeEvent) => { // 복사/붙여넣기 등 특수 입력 대비 const input = e.target.value; // 한글 및 허용되지 않는 문자 제거 (영문, 숫자, @, ., -, _ 만 허용) const filteredInput = input.replace(/[^a-zA-Z0-9@._-]/g, ''); const atIndex = filteredInput.indexOf('@'); if (atIndex !== -1) { // @ 이후 부분은 그대로, @ 이전 부분은 * 제거 const beforeAt = filteredInput.slice(0, atIndex).replace(/\*/g, ''); const afterAt = filteredInput.slice(atIndex); setEamil(beforeAt + afterAt); } else { // @가 없으면 * 제거 setEamil(filteredInput.replace(/\*/g, '')); } }} className={email && !isValidEmail(email) ? 'error' : ''} onFocus={handleInputFocus} />
{t('additionalService.ars.paymentMethod')} *
{getArsPaymentMethodBtns()}
); };