diff --git a/src/entities/additional-service/ui/info-wrap/detail-info-wrap.tsx b/src/entities/additional-service/ui/info-wrap/detail-info-wrap.tsx index 57ceb75..cd98ec7 100644 --- a/src/entities/additional-service/ui/info-wrap/detail-info-wrap.tsx +++ b/src/entities/additional-service/ui/info-wrap/detail-info-wrap.tsx @@ -1,4 +1,5 @@ import moment from 'moment'; +import { useTranslation } from 'react-i18next'; import { AdditionalServiceCategory, DetailInfoSectionProps } from '../../model/types'; import 'react-slidedown/lib/slidedown.css'; @@ -6,29 +7,50 @@ export const DetailInfoWrap = ({ additionalServiceCategory, detailInfo }: DetailInfoSectionProps) => { + const { t } = useTranslation(); + + // 전화번호 마스킹 + const getMaskedPhoneNumber = (phone?: string) => { + if (!phone) return ''; + if (phone.length <= 7) return phone; + const visiblePart = phone.slice(0, -4); + return visiblePart + '****'; + }; + + // 이메일 마스킹 + const getMaskedEmail = (email?: string) => { + if (!email) return ''; + const atIndex = email.indexOf('@'); + if (atIndex === -1 || atIndex <= 2) return email; + const visiblePart = email.slice(0, 2); + const domainPart = email.slice(atIndex); + const maskedLength = atIndex - 2; + const masked = '*'.repeat(maskedLength); + return visiblePart + masked + domainPart; + }; return ( <>
-
상세 정보
+
{t('additionalService.infoWrap.detailInfo')}
@@ -83,16 +185,33 @@ export const LinkPaymentStep2 = ({
{ + // 백스페이스 처리 + if (e.key === 'Backspace') { + e.preventDefault(); + handleInputChange('phoneNumber', formData.phoneNumber.slice(0, -1)); + } + // 숫자 입력 처리 + else if (/^[0-9]$/.test(e.key)) { + e.preventDefault(); + if (formData.phoneNumber.length < 11) { + handleInputChange('phoneNumber', formData.phoneNumber + e.key); + } + } + }} onChange={(e) => { - const onlyNumbers = e.target.value.replace(/[^0-9]/g, ''); - handleInputChange('phoneNumber', onlyNumbers); + // Android 등에서 한글 키보드로 입력 시 대비 + const input = e.target.value; + const digitsOnly = input.replace(/\*/g, '').replace(/[^0-9]/g, ''); + if (digitsOnly.length <= 11) { + handleInputChange('phoneNumber', digitsOnly); + } }} className={formData.phoneNumber && !isValidPhoneNumber(formData.phoneNumber) ? 'error' : ''} inputMode="numeric" pattern="[0-9]*" - maxLength={11} + maxLength={15} />
diff --git a/src/entities/additional-service/ui/link-payment/filter/link-payment-history-filter.tsx b/src/entities/additional-service/ui/link-payment/filter/link-payment-history-filter.tsx index f98a014..e0a47da 100644 --- a/src/entities/additional-service/ui/link-payment/filter/link-payment-history-filter.tsx +++ b/src/entities/additional-service/ui/link-payment/filter/link-payment-history-filter.tsx @@ -128,7 +128,7 @@ export const LinkPaymentHistoryFilter = ({ selectValue={filterSearchCl} selectSetter={setFilterSearchCl} selectOptions={searchTypeOption} - inputValue={searchValue} + inputValue={filterSearchValue} inputSetter={setFilterSearchValue} > { const { navigate } = useNavigate(); const { t, i18n } = useTranslation(); + + // 이름 마스킹 + const getMaskedName = (name?: string) => { + if (!name) return ''; + const length = name.length; + if (length <= 1) return name; + if (length === 2) return name[0] + '*'; + if (length === 3) return name[0] + '*' + name[2]; + const firstChar = name[0]; + const lastChar = name[length - 1]; + const maskedLength = length - 2; + const masked = '*'.repeat(maskedLength); + return firstChar + masked + lastChar; + }; + + // 계좌번호 마스킹 (마지막 5자리 제외) + const getMaskedAccountNumber = (accountNo?: string) => { + if (!accountNo) return ''; + if (accountNo.length <= 5) return accountNo; + const visiblePart = accountNo.slice(-5); + const maskedLength = accountNo.length - 5; + const masked = '*'.repeat(maskedLength); + return masked + visiblePart; + }; + const getItemClass = () => { let rs = ''; if (paymentStatus === '') { @@ -351,18 +376,20 @@ export const ListItem = ({ } else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentHistory ) { - str = `${buyerName}`; + str = `${getMaskedName(buyerName)}`; } else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentWait) { - str = `${buyerName}(${receiverInfo})`; + str = `${getMaskedName(buyerName)}(${receiverInfo})`; } else if (additionalServiceCategory === AdditionalServiceCategory.Payout) { str = companyName; } - else if (additionalServiceCategory === AdditionalServiceCategory.FundAccountTransfer || - additionalServiceCategory === AdditionalServiceCategory.FundAccountResult + else if (additionalServiceCategory === AdditionalServiceCategory.FundAccountTransfer ) { str = `${accountName}(${accountNo})`; } + else if (additionalServiceCategory === AdditionalServiceCategory.FundAccountResult) { + str = `${getMaskedName(accountName)}(${getMaskedAccountNumber(accountNo)})`; + } else if (additionalServiceCategory === AdditionalServiceCategory.SMSPayment) { if (buyerPhoneLast4) { str = `${buyerName}(${buyerPhoneLast4})` @@ -372,10 +399,10 @@ export const ListItem = ({ } else if (additionalServiceCategory === AdditionalServiceCategory.Ars) { - str = `${buyerName}(${tid})`; + str = `${getMaskedName(buyerName)}(${tid})`; } else if (additionalServiceCategory === AdditionalServiceCategory.Alimtalk) { - str = `${receiverName}(${getAlimtalkSendTypeText(t)(sendType)})`; + str = `${getMaskedName(receiverName)}(${getAlimtalkSendTypeText(t)(sendType)})`; } return str; diff --git a/src/pages/additional-service/account-holder-search/account-holder-search-page.tsx b/src/pages/additional-service/account-holder-search/account-holder-search-page.tsx index bade272..eeb2fe5 100644 --- a/src/pages/additional-service/account-holder-search/account-holder-search-page.tsx +++ b/src/pages/additional-service/account-holder-search/account-holder-search-page.tsx @@ -213,9 +213,9 @@ export const AccountHolderSearchPage = () => { sortType ]); - // if (!hasAccess) { - // return ; - // } + if (!hasAccess) { + return ; + } return ( <> diff --git a/src/pages/additional-service/alimtalk/list-page.tsx b/src/pages/additional-service/alimtalk/list-page.tsx index f74383b..00a6f23 100644 --- a/src/pages/additional-service/alimtalk/list-page.tsx +++ b/src/pages/additional-service/alimtalk/list-page.tsx @@ -300,9 +300,9 @@ export const AlimtalkListPage = () => { }; }, []); - // if (!hasAccess) { - // return ; - // } + if (!hasAccess) { + return ; + } return ( <> diff --git a/src/pages/additional-service/ars/request-page.tsx b/src/pages/additional-service/ars/request-page.tsx index 82d1811..7513722 100644 --- a/src/pages/additional-service/ars/request-page.tsx +++ b/src/pages/additional-service/ars/request-page.tsx @@ -37,6 +37,7 @@ export const ArsRequestPage = () => { 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); @@ -110,6 +111,60 @@ export const ArsRequestPage = () => { ); }; + 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( @@ -215,8 +270,44 @@ export const ArsRequestPage = () => {
) => setBuyerName(e.target.value)} + value={isComposing ? buyerName : getMaskedName(buyerName)} + placeholder='홍*동' + onKeyDown={(e) => { + // 스페이스바 입력 차단 + 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} />
@@ -227,16 +318,35 @@ export const ArsRequestPage = () => {
{ + // 백스페이스 처리 + 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) => { - const onlyNumbers = e.target.value.replace(/[^0-9]/g, ''); - setPhoneNumber(onlyNumbers); + // 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={11} + maxLength={15} onFocus={handleInputFocus} />
@@ -247,9 +357,45 @@ export const ArsRequestPage = () => {
) => setEamil(e.target.value)} + value={getMaskedEmail(email)} + placeholder='te**@nicepay.co.kr' + onKeyDown={(e) => { + // 백스페이스 처리 + 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} /> diff --git a/src/pages/additional-service/face-auth/face-auth-page.tsx b/src/pages/additional-service/face-auth/face-auth-page.tsx index 6300a84..cb9fe79 100644 --- a/src/pages/additional-service/face-auth/face-auth-page.tsx +++ b/src/pages/additional-service/face-auth/face-auth-page.tsx @@ -256,9 +256,9 @@ export const FaceAuthPage = () => { }; }, []); - // if (!hasAccess) { - // return ; - // } + if (!hasAccess) { + return ; + } return ( <> diff --git a/src/pages/additional-service/fund-account/transfer-request-page.tsx b/src/pages/additional-service/fund-account/transfer-request-page.tsx index 32361ac..3e45059 100644 --- a/src/pages/additional-service/fund-account/transfer-request-page.tsx +++ b/src/pages/additional-service/fund-account/transfer-request-page.tsx @@ -32,6 +32,7 @@ export const FundAccountTransferRequestPage = () => { const [amount, setAmount] = useState(0); const [moid, setMoid] = useState(''); const [depositParameter, setDepositParameter] = useState(''); + const [isComposing, setIsComposing] = useState(false); const { handleInputFocus, keyboardAwarePadding } = useKeyboardAware(); const { mutateAsync: extensionFundAccountRegist } = useExtensionFundAccountTransferRegistMutation(); @@ -105,6 +106,28 @@ export const FundAccountTransferRequestPage = () => { ) } + // 계좌번호 마스킹 (마지막 5자리 제외) + const getMaskedAccountNumber = (accountNo: string) => { + if (accountNo.length <= 5) return accountNo; + const visiblePart = accountNo.slice(-5); + const maskedLength = accountNo.length - 5; + const masked = '*'.repeat(maskedLength); + return masked + visiblePart; + }; + + // 이름 마스킹 + const getMaskedName = (name: string) => { + const length = name.length; + if (length <= 1) return name; + if (length === 2) return name[0] + '*'; + if (length === 3) return name[0] + '*' + name[2]; + const firstChar = name[0]; + const lastChar = name[length - 1]; + const maskedLength = length - 2; + const masked = '*'.repeat(maskedLength); + return firstChar + masked + lastChar; + }; + return ( <>
@@ -150,16 +173,33 @@ export const FundAccountTransferRequestPage = () => {
{t('additionalService.fundAccount.accountNumber')}*
- { - const { value } = values; - return !value || value.length <= 14; + { + // 백스페이스 처리 + if (e.key === 'Backspace') { + e.preventDefault(); + setAccountNo(accountNo.slice(0, -1)); + } + // 숫자 입력 처리 + else if (/^[0-9]$/.test(e.key)) { + e.preventDefault(); + if (accountNo.length < 14) { + setAccountNo(accountNo + e.key); + } + } }} - onValueChange={(values) => setAccountNo(values.value)} + onChange={(e: ChangeEvent) => { + // Android 등에서 한글 키보드로 입력 시 대비 + const input = e.target.value; + const digitsOnly = input.replace(/\*/g, '').replace(/[^0-9]/g, ''); + if (digitsOnly.length <= 14) { + setAccountNo(digitsOnly); + } + }} + inputMode="numeric" + pattern="[0-9]*" onFocus={handleInputFocus} />
@@ -169,8 +209,43 @@ export const FundAccountTransferRequestPage = () => {
) => setAccountName(e.target.value)} + value={isComposing ? accountName : getMaskedName(accountName)} + onKeyDown={(e) => { + // 스페이스바 입력 차단 + if (e.key === ' ') { + e.preventDefault(); + } + // 백스페이스 처리 (composition 중이 아닐 때만) + else if (e.key === 'Backspace' && !isComposing) { + e.preventDefault(); + setAccountName(accountName.slice(0, -1)); + } + // 영문자 입력 처리 (composition 중이 아닐 때만) + else if (!isComposing && e.key.length === 1 && !e.ctrlKey && !e.metaKey) { + if (/^[a-zA-Z]$/.test(e.key)) { + e.preventDefault(); + setAccountName(accountName + 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, ''); + setAccountName(valueWithoutSpace); + }} + onChange={(e: ChangeEvent) => { + if (isComposing) { + // composition 중에는 스페이스 제거 + const valueWithoutSpace = e.target.value.replace(/\s/g, ''); + setAccountName(valueWithoutSpace); + } + }} onFocus={handleInputFocus} />
diff --git a/src/pages/additional-service/key-in-payment/key-in-payment-page.tsx b/src/pages/additional-service/key-in-payment/key-in-payment-page.tsx index 17833f3..9fcc337 100644 --- a/src/pages/additional-service/key-in-payment/key-in-payment-page.tsx +++ b/src/pages/additional-service/key-in-payment/key-in-payment-page.tsx @@ -196,9 +196,9 @@ export const KeyInPaymentPage = () => { sortType ]); - // if (!hasAccess) { - // return ; - // } + if (!hasAccess) { + return ; + } return ( diff --git a/src/pages/transaction/cash-receipt/list-page.tsx b/src/pages/transaction/cash-receipt/list-page.tsx index 7c93e92..d91fdea 100644 --- a/src/pages/transaction/cash-receipt/list-page.tsx +++ b/src/pages/transaction/cash-receipt/list-page.tsx @@ -236,7 +236,7 @@ export const CashReceiptListPage = () => { setTransactionType(val); }; const onClickToNavigate = () => { - if(checkGrant(menuId, 'W')){ + if(checkGrant(menuId, 'X')){ navigate(PATHS.transaction.cashReceipt.handWrittenIssuance); } else{