This commit is contained in:
focp212@naver.com
2025-11-03 16:18:26 +09:00
16 changed files with 303 additions and 142 deletions

View File

@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { PATHS } from '@/shared/constants/paths'; import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useSetOnBack } from '@/widgets/sub-layout/use-sub-layout'; import { useSetOnBack } from '@/widgets/sub-layout/use-sub-layout';
@@ -13,6 +14,7 @@ interface LinkPaymentStep1Props {
} }
export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Props) => { export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Props) => {
const { t } = useTranslation();
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const midOptionsWithoutGids = useStore.getState().UserStore.selectOptionsMidsWithoutGids; const midOptionsWithoutGids = useStore.getState().UserStore.selectOptionsMidsWithoutGids;
@@ -46,7 +48,7 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop
<> <>
<div className="issue-form"> <div className="issue-form">
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label"></div> <div className="issue-label">{t('filter.merchant')}</div>
<div className="issue-field"> <div className="issue-field">
<select <select
className="wid-100" className="wid-100"
@@ -66,7 +68,7 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop
</div> </div>
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label"> </div> <div className="issue-label">{t('additionalService.linkPayment.sendMethod')}</div>
<div className="issue-field"> <div className="issue-field">
<div className="chip-row"> <div className="chip-row">
<span <span
@@ -79,20 +81,20 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop
className={`keyword-tag flex-1 ${formData.sendMethod === 'EMAIL' ? 'active' : ''}`} className={`keyword-tag flex-1 ${formData.sendMethod === 'EMAIL' ? 'active' : ''}`}
onClick={() => handlePaymentMethodChange(LinkPaymentSendMethod.EMAIL)} onClick={() => handlePaymentMethodChange(LinkPaymentSendMethod.EMAIL)}
> >
{t('common.email')}
</span> </span>
<span <span
className={`keyword-tag flex-1 ${formData.sendMethod === 'KAKAO' ? 'active' : ''}`} className={`keyword-tag flex-1 ${formData.sendMethod === 'KAKAO' ? 'active' : ''}`}
onClick={() => handlePaymentMethodChange(LinkPaymentSendMethod.KAKAO)} onClick={() => handlePaymentMethodChange(LinkPaymentSendMethod.KAKAO)}
> >
{t('common.kakao')}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label"></div> <div className="issue-label">{t('transaction.fields.productName')}</div>
<div className="issue-field"> <div className="issue-field">
<input <input
type="text" type="text"
@@ -104,7 +106,7 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop
</div> </div>
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label"></div> <div className="issue-label">{t('additionalService.linkPayment.productPrice')}</div>
<div className="issue-field"> <div className="issue-field">
<NumericFormat <NumericFormat
value={formData.amount} value={formData.amount}
@@ -120,7 +122,7 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop
</div> </div>
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label"> </div> <div className="issue-label">{t('additionalService.linkPayment.productOrderNumber')}</div>
<div className="issue-field"> <div className="issue-field">
<input <input
type="text" type="text"
@@ -132,16 +134,16 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop
</div> </div>
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label"> </div> <div className="issue-label">{t('additionalService.linkPayment.paymentValidDate')}</div>
<div className="issue-field"> <div className="issue-field">
<div className="link-apply-date"> <div className="link-apply-date">
<SingleDatePicker <SingleDatePicker
date={formData.paymentLimitDate} date={formData.paymentLimitDate}
setDate={handleDateChange} setDate={handleDateChange}
placeholder="날짜 선택" placeholder={t('additionalService.linkPayment.selectDate')}
minDate={new Date()} minDate={new Date()}
/> />
<span></span> <span>{t('additionalService.linkPayment.until')}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { ProcessStep } from "@/entities/transaction/model/types"; import { ProcessStep } from "@/entities/transaction/model/types";
import { useSetOnBack } from "@/widgets/sub-layout/use-sub-layout"; import { useSetOnBack } from "@/widgets/sub-layout/use-sub-layout";
import { IdentityType, Language } from '@/entities/additional-service/model/types' import { IdentityType, Language } from '@/entities/additional-service/model/types'
@@ -15,6 +16,8 @@ export const LinkPaymentStep2 = ({
formData, formData,
setFormData setFormData
}: LinkPaymentStep2Props) => { }: LinkPaymentStep2Props) => {
const { t } = useTranslation();
useSetOnBack(() => { useSetOnBack(() => {
setProcessStep(ProcessStep.One); setProcessStep(ProcessStep.One);
}); });
@@ -49,7 +52,7 @@ export const LinkPaymentStep2 = ({
<> <>
<div className="issue-form"> <div className="issue-form">
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label wid-105"></div> <div className="issue-label wid-105">{t('transaction.fields.buyerName')}</div>
<div className="issue-field"> <div className="issue-field">
<input <input
type="text" type="text"
@@ -62,7 +65,7 @@ export const LinkPaymentStep2 = ({
{formData.sendMethod === 'EMAIL' && {formData.sendMethod === 'EMAIL' &&
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label wid-105"> </div> <div className="issue-label wid-105">{t('additionalService.linkPayment.buyerEmail')}</div>
<div className="issue-field"> <div className="issue-field">
<input <input
type="text" type="text"
@@ -76,7 +79,7 @@ export const LinkPaymentStep2 = ({
{(formData.sendMethod === 'SMS' || formData.sendMethod === 'KAKAO') && {(formData.sendMethod === 'SMS' || formData.sendMethod === 'KAKAO') &&
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label wid-105"><br /> </div> <div className="issue-label wid-105" dangerouslySetInnerHTML={{ __html: t('additionalService.linkPayment.buyerPhoneNumber') }}></div>
<div className="issue-field"> <div className="issue-field">
<input <input
type="tel" type="tel"
@@ -97,7 +100,7 @@ export const LinkPaymentStep2 = ({
<div className="issue-row gap-10 beetween"> <div className="issue-row gap-10 beetween">
<div className="issue-label wid-105"> </div> <div className="issue-label wid-105">{t('additionalService.linkPayment.buyerInfoVerification')}</div>
<label className="settings-switch"> <label className="settings-switch">
<input type="checkbox" checked={formData.isIdentity} onChange={(e) => handleIdentityToggle(e.target.checked)} /> <input type="checkbox" checked={formData.isIdentity} onChange={(e) => handleIdentityToggle(e.target.checked)} />
<span className="slider"></span> <span className="slider"></span>
@@ -112,14 +115,14 @@ export const LinkPaymentStep2 = ({
onClick={() => formData.isIdentity && handleIdendityTypeChange(IdentityType.INDIVIDUAL)} onClick={() => formData.isIdentity && handleIdendityTypeChange(IdentityType.INDIVIDUAL)}
style={!formData.isIdentity ? { opacity: 0.5, cursor: 'not-allowed' } : {}} style={!formData.isIdentity ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
> >
{t('additionalService.linkPayment.individual')}
</span> </span>
<span <span
className={`keyword-tag flex-1 ${formData.identityType === IdentityType.CORPORATE ? 'active' : ''} ${!formData.isIdentity ? 'disabled' : ''}`} className={`keyword-tag flex-1 ${formData.identityType === IdentityType.CORPORATE ? 'active' : ''} ${!formData.isIdentity ? 'disabled' : ''}`}
onClick={() => formData.isIdentity && handleIdendityTypeChange(IdentityType.CORPORATE)} onClick={() => formData.isIdentity && handleIdendityTypeChange(IdentityType.CORPORATE)}
style={!formData.isIdentity ? { opacity: 0.5, cursor: 'not-allowed' } : {}} style={!formData.isIdentity ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
> >
{t('additionalService.linkPayment.corporate')}
</span> </span>
</div> </div>
</div> </div>
@@ -131,7 +134,7 @@ export const LinkPaymentStep2 = ({
{formData.identityType === IdentityType.CORPORATE ? ( {formData.identityType === IdentityType.CORPORATE ? (
<PatternFormat <PatternFormat
format="##########" format="##########"
placeholder="사업자번호 10자리" placeholder={t('additionalService.linkPayment.businessNumber10Digits')}
value={formData.identityValue} value={formData.identityValue}
valueIsNumericString valueIsNumericString
onValueChange={(values) => { onValueChange={(values) => {
@@ -143,7 +146,7 @@ export const LinkPaymentStep2 = ({
) : ( ) : (
<PatternFormat <PatternFormat
format="######" format="######"
placeholder="생년월일 6자리" placeholder={t('additionalService.linkPayment.birthDate6Digits')}
value={formData.identityValue} value={formData.identityValue}
valueIsNumericString valueIsNumericString
onValueChange={(values) => { onValueChange={(values) => {
@@ -157,39 +160,39 @@ export const LinkPaymentStep2 = ({
</div> </div>
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label wid-105"></div> <div className="issue-label wid-105">{t('additionalService.linkPayment.language')}</div>
<div className="issue-field"> <div className="issue-field">
<div className="chip-row"> <div className="chip-row">
<span <span
className={`keyword-tag flex-1 ${formData.language === Language.KR ? 'active' : ''}`} className={`keyword-tag flex-1 ${formData.language === Language.KR ? 'active' : ''}`}
onClick={() => handleLanguageType(Language.KR)} onClick={() => handleLanguageType(Language.KR)}
> >
{t('additionalService.linkPayment.korean')}
</span> </span>
<span <span
className={`keyword-tag flex-1 ${formData.language === Language.EN ? 'active' : ''}`} className={`keyword-tag flex-1 ${formData.language === Language.EN ? 'active' : ''}`}
onClick={() => handleLanguageType(Language.EN)} onClick={() => handleLanguageType(Language.EN)}
> >
{t('additionalService.linkPayment.english')}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div className="issue-row gap-10"> <div className="issue-row gap-10">
<div className="issue-label wid-105"></div> <div className="issue-label wid-105">{t('additionalService.linkPayment.linkContent')}</div>
<div className="issue-field"> <div className="issue-field">
<div className="chip-row"> <div className="chip-row">
<span <span
className={`keyword-tag flex-1 ${formData.linkContentType === LinkContentType.BASIC ? 'active' : ''}`} className={`keyword-tag flex-1 ${formData.linkContentType === LinkContentType.BASIC ? 'active' : ''}`}
onClick={() => handleLinkContent(LinkContentType.BASIC)} onClick={() => handleLinkContent(LinkContentType.BASIC)}
> >
{t('additionalService.linkPayment.basic')}
</span> </span>
<span <span
className={`keyword-tag flex-1 ${formData.linkContentType === LinkContentType.ADDITIONAL ? 'active' : ''}`} className={`keyword-tag flex-1 ${formData.linkContentType === LinkContentType.ADDITIONAL ? 'active' : ''}`}
onClick={() => handleLinkContent(LinkContentType.ADDITIONAL)} onClick={() => handleLinkContent(LinkContentType.ADDITIONAL)}
> >
{t('additionalService.linkPayment.additional')}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
import moment from 'moment'; import moment from 'moment';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { IMAGE_ROOT } from '@/shared/constants/common'; import { IMAGE_ROOT } from '@/shared/constants/common';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { ChangeEvent, useState } from 'react'; import { ChangeEvent, useState } from 'react';
@@ -32,7 +33,7 @@ export const LinkPaymentWaitSendFilter = ({
setSendMethod, setSendMethod,
setProcessStatus setProcessStatus
}: LinkPaymentWaitFilterProps) => { }: LinkPaymentWaitFilterProps) => {
const { t } = useTranslation();
const [filterMid, setFilterMid] = useState<string>(mid); const [filterMid, setFilterMid] = useState<string>(mid);
const [filterSearchType, setFilterSearchType] = useState<LinkPaymentSearchCl>(searchCl); const [filterSearchType, setFilterSearchType] = useState<LinkPaymentSearchCl>(searchCl);
@@ -58,21 +59,21 @@ export const LinkPaymentWaitSendFilter = ({
}; };
let searchTypeOption = [ let searchTypeOption = [
{ name: '휴대폰번호', value: LinkPaymentSearchCl.PHONE }, { name: t('additionalService.linkPay.phoneNumber'), value: LinkPaymentSearchCl.PHONE },
{ name: '이메일', value: LinkPaymentSearchCl.EMAIL }, { name: t('common.email'), value: LinkPaymentSearchCl.EMAIL },
]; ];
let sendMethodOption = [ let sendMethodOption = [
{ name: '전체', value: LinkPaymentSendMethod.ALL }, { name: t('additionalService.linkPay.all'), value: LinkPaymentSendMethod.ALL },
{ name: 'SMS', value: LinkPaymentSendMethod.SMS }, { name: 'SMS', value: LinkPaymentSendMethod.SMS },
{ name: '이메일', value: LinkPaymentSendMethod.EMAIL }, { name: t('common.email'), value: LinkPaymentSendMethod.EMAIL },
{ name: '카카오', value: LinkPaymentSendMethod.KAKAO }, { name: t('common.kakao'), value: LinkPaymentSendMethod.KAKAO },
]; ];
let processStatusOption = [ let processStatusOption = [
{ name: '전체', value: LinkPaymentProcessStatus.ALL }, { name: t('additionalService.linkPay.all'), value: LinkPaymentProcessStatus.ALL },
{ name: '발송요청', value: LinkPaymentProcessStatus.SEND_REQUEST }, { name: t('additionalService.linkPay.sendRequest'), value: LinkPaymentProcessStatus.SEND_REQUEST },
{ name: '발송취소', value: LinkPaymentProcessStatus.SEND_CANCEL }, { name: t('additionalService.linkPay.sendCancel'), value: LinkPaymentProcessStatus.SEND_CANCEL },
]; ];
useEffect(() => { useEffect(() => {
@@ -90,7 +91,7 @@ export const LinkPaymentWaitSendFilter = ({
> >
<div className="full-menu-container"> <div className="full-menu-container">
<div className="full-menu-header"> <div className="full-menu-header">
<div className="full-menu-title center"></div> <div className="full-menu-title center">{t('filter.filter')}</div>
<div className="full-menu-actions"> <div className="full-menu-actions">
<FullMenuClose <FullMenuClose
addClass='full-menu-close' addClass='full-menu-close'
@@ -101,13 +102,13 @@ export const LinkPaymentWaitSendFilter = ({
<div className="option-list pt-16 pb-86"> <div className="option-list pt-16 pb-86">
<FilterSelectMid <FilterSelectMid
title='가맹점' title={t('filter.merchant')}
selectSetter={setFilterMid} selectSetter={setFilterMid}
showType={'GID'} showType={'GID'}
></FilterSelectMid> ></FilterSelectMid>
<FilterSelectInput <FilterSelectInput
title='휴대폰번호/이메일' title={t('additionalService.linkPay.phoneNumberEmail')}
selectValue={filterSearchType} selectValue={filterSearchType}
selectSetter={setFilterSearchType} selectSetter={setFilterSearchType}
selectOptions={searchTypeOption} selectOptions={searchTypeOption}
@@ -122,14 +123,14 @@ export const LinkPaymentWaitSendFilter = ({
></FilterCalendar> ></FilterCalendar>
<FilterButtonGroups <FilterButtonGroups
title='발송수단' title={t('additionalService.linkPayment.sendMethodFilter')}
activeValue={filterSendMethod} activeValue={filterSendMethod}
btnGroups={sendMethodOption} btnGroups={sendMethodOption}
setter={setFilterSendMethod} setter={setFilterSendMethod}
></FilterButtonGroups> ></FilterButtonGroups>
<FilterButtonGroups <FilterButtonGroups
title='진행상태' title={t('additionalService.linkPayment.processStatus')}
activeValue={filterProcessStatus} activeValue={filterProcessStatus}
btnGroups={processStatusOption} btnGroups={processStatusOption}
setter={setFilterProcessStatus} setter={setFilterProcessStatus}
@@ -139,7 +140,7 @@ export const LinkPaymentWaitSendFilter = ({
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={() => onClickToSetFilter()} onClick={() => onClickToSetFilter()}
></button> >{t('filter.apply')}</button>
</div> </div>
</div> </div>
</motion.div> </motion.div>

View File

@@ -1,4 +1,5 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import { IMAGE_ROOT } from '@/shared/constants/common'; import { IMAGE_ROOT } from '@/shared/constants/common';
import { SmsPaymentDetailResendProps } from '../../../additional-service/model/sms-payment/types'; import { SmsPaymentDetailResendProps } from '../../../additional-service/model/sms-payment/types';
import { useExtensionSmsResendMutation } from '../../api/sms-payment/use-extension-sms-resend-mutation'; import { useExtensionSmsResendMutation } from '../../api/sms-payment/use-extension-sms-resend-mutation';
@@ -12,7 +13,8 @@ export const SmsPaymentDetailResend = ({
mid, mid,
tid tid
}: SmsPaymentDetailResendProps) => { }: SmsPaymentDetailResendProps) => {
const { t } = useTranslation();
const variants = { const variants = {
hidden: { y: '100%' }, hidden: { y: '100%' },
visible: { y: '0%' }, visible: { y: '0%' },
@@ -32,13 +34,13 @@ export const SmsPaymentDetailResend = ({
sendMessage: smsDetailData.sendMessage sendMessage: smsDetailData.sendMessage
}).then((rs) => { }).then((rs) => {
if (rs.status) { if (rs.status) {
snackBar("SMS 발송을 성공하였습니다.") snackBar(t('additionalService.sms.sendSuccess'))
} else { } else {
snackBar(`[실패] ${rs.error?.message}`) snackBar(t('additionalService.sms.sendFailed', { message: rs.error?.message }))
} }
setBottomSmsPaymentDetailResendOn(false); setBottomSmsPaymentDetailResendOn(false);
}).catch((error) => { }).catch((error) => {
snackBar(`[실패] ${error?.response?.data?.message || error?.response?.data?.error?.message}` || '[실패] 신청을 실패하였습니다.') snackBar(t('additionalService.sms.sendFailed', { message: error?.response?.data?.message || error?.response?.data?.error?.message }) || t('additionalService.sms.sendFailedGeneric'))
}); });
} }
@@ -61,22 +63,22 @@ export const SmsPaymentDetailResend = ({
> >
<div className="bottomsheet-header"> <div className="bottomsheet-header">
<div className="bottomsheet-title"> <div className="bottomsheet-title">
<h2>SMS & </h2> <h2>{t('additionalService.sms.smsDetailAndResend')}</h2>
<button <button
className="close-btn" className="close-btn"
type="button" type="button"
> >
<img <img
src={ IMAGE_ROOT + '/ico_close.svg' } src={ IMAGE_ROOT + '/ico_close.svg' }
alt="닫기" alt={t('common.close')}
onClick={ () => onClickToClose() } onClick={ () => onClickToClose() }
/> />
</button> </button>
</div> </div>
</div> </div>
<div className="resend-info"> <div className="resend-info">
<div className="resend-row">() : {smsDetailData?.senderName || '-'}({smsDetailData?.senderNumber || '-'})</div> <div className="resend-row">{t('additionalService.sms.sender')} : {smsDetailData?.senderName || '-'}({smsDetailData?.senderNumber || '-'})</div>
<div className="resend-row">() : {smsDetailData?.receiverName || '-'}({smsDetailData?.receiverNumber || '-'})</div> <div className="resend-row">{t('additionalService.sms.receiver')} : {smsDetailData?.receiverName || '-'}({smsDetailData?.receiverNumber || '-'})</div>
</div> </div>
<div className="resend-box"> <div className="resend-box">
<p className="resend-text">{smsDetailData?.sendMessage || '-'}</p> <p className="resend-text">{smsDetailData?.sendMessage || '-'}</p>
@@ -88,7 +90,7 @@ export const SmsPaymentDetailResend = ({
onClick={onClickResend} onClick={onClickResend}
disabled={!smsDetailData?.sendMessage} disabled={!smsDetailData?.sendMessage}
> >
{t('common.request')}
</button> </button>
</div> </div>
</motion.div> </motion.div>

View File

@@ -1,4 +1,5 @@
import moment from 'moment'; import moment from 'moment';
import { useTranslation } from 'react-i18next';
import { IMAGE_ROOT } from '@/shared/constants/common'; import { IMAGE_ROOT } from '@/shared/constants/common';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useState } from 'react'; import { useState } from 'react';
@@ -28,6 +29,7 @@ export const SmsPaymentFilter = ({
setToDate, setToDate,
setSmsCl setSmsCl
}: SmsPaymentFilterProps) => { }: SmsPaymentFilterProps) => {
const { t } = useTranslation();
const [filterMid, setFilterMid] = useState<string>(mid); const [filterMid, setFilterMid] = useState<string>(mid);
const [filterSearchCl, setFilterSearchCl] = useState<SmsPaymentSearchCl>(searchCl); const [filterSearchCl, setFilterSearchCl] = useState<SmsPaymentSearchCl>(searchCl);
@@ -52,14 +54,14 @@ export const SmsPaymentFilter = ({
]; ];
let searchTypeOption = [ let searchTypeOption = [
{ name: '주문자', value: SmsPaymentSearchCl.BUYER_NAME }, { name: t('transaction.fields.buyerName'), value: SmsPaymentSearchCl.BUYER_NAME },
{ name: '수신번호', value: SmsPaymentSearchCl.RECEIVE_PHONE_NUMBER }, { name: t('additionalService.sms.receivePhoneNumber'), value: SmsPaymentSearchCl.RECEIVE_PHONE_NUMBER },
] ]
let smsTypeOption = [ let smsTypeOption = [
{ name: '전체', value: SmsCl.ALL }, { name: t('additionalService.sms.all'), value: SmsCl.ALL },
{ name: '가상계좌\n요청', value: SmsCl.VACCOUNT_REQ }, { name: t('additionalService.sms.virtualAccountReqShort'), value: SmsCl.VACCOUNT_REQ },
{ name: '가상계좌\n요청+입금', value: SmsCl.VACCOUNT_REQ_DEPOSIT }, { name: t('additionalService.sms.virtualAccountReqDepositShort'), value: SmsCl.VACCOUNT_REQ_DEPOSIT },
] ]
const onClickToClose = () => { const onClickToClose = () => {
@@ -78,7 +80,7 @@ export const SmsPaymentFilter = ({
> >
<div className="full-menu-container"> <div className="full-menu-container">
<div className="full-menu-header"> <div className="full-menu-header">
<div className="full-menu-title center"></div> <div className="full-menu-title center">{t('filter.filter')}</div>
<div className="full-menu-actions"> <div className="full-menu-actions">
<FullMenuClose <FullMenuClose
addClass='full-menu-close' addClass='full-menu-close'
@@ -89,13 +91,13 @@ export const SmsPaymentFilter = ({
<div className="option-list pt-16 pb-86"> <div className="option-list pt-16 pb-86">
<FilterSelectMid <FilterSelectMid
title='가맹점' title={t('filter.merchant')}
selectSetter={setFilterMid} selectSetter={setFilterMid}
showType={'GID'} showType={'GID'}
></FilterSelectMid> ></FilterSelectMid>
<FilterSelectInput <FilterSelectInput
title='주문자,수신번호' title={t('additionalService.sms.buyerReceiveNumber')}
selectValue={searchCl} selectValue={searchCl}
selectSetter={setSearchCl} selectSetter={setSearchCl}
selectOptions={searchTypeOption} selectOptions={searchTypeOption}
@@ -109,7 +111,7 @@ export const SmsPaymentFilter = ({
setEndDate={setFilterToDate} setEndDate={setFilterToDate}
></FilterCalendar> ></FilterCalendar>
<FilterButtonGroups <FilterButtonGroups
title='조회결과' title={t('additionalService.sms.queryResult')}
activeValue={filterSmsCl} activeValue={filterSmsCl}
btnGroups={smsTypeOption} btnGroups={smsTypeOption}
setter={setFilterSmsCl} setter={setFilterSmsCl}
@@ -119,7 +121,7 @@ export const SmsPaymentFilter = ({
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={() => onClickToSetFilter()} onClick={() => onClickToSetFilter()}
></button> >{t('filter.apply')}</button>
</div> </div>
</div> </div>
</motion.div> </motion.div>

View File

@@ -868,14 +868,28 @@
"agreeOrReject": "Agree/Reject Settlement" "agreeOrReject": "Agree/Reject Settlement"
}, },
"ars": { "ars": {
"title": "Credit Card ARS Payment",
"paymentComplete": "Payment Complete", "paymentComplete": "Payment Complete",
"unpaid": "Unpaid", "unpaid": "Unpaid",
"pendingPayment": "Pending Payment", "pendingPayment": "Pending Payment",
"paymentSuccess": "Payment Success", "paymentSuccess": "Payment Success",
"expired": "Expired", "expired": "Expired",
"canceled": "Canceled" "canceled": "Canceled",
"paymentRequest": "Payment Request",
"merchant": "Merchant",
"orderNumber": "Order Number",
"productName": "Product Name",
"amount": "Amount",
"installmentPeriod": "Installment Period",
"lumpSum": "Lump Sum",
"buyerName": "Buyer Name",
"phoneNumber": "Phone Number",
"email": "Email",
"paymentMethod": "Payment Method",
"requestFailed": "Request failed."
}, },
"sms": { "sms": {
"title": "SMS Payment Notification",
"virtualAccountRequest": "Virtual Account Request", "virtualAccountRequest": "Virtual Account Request",
"virtualAccountRequestDeposit": "Virtual Account Request + Deposit", "virtualAccountRequestDeposit": "Virtual Account Request + Deposit",
"smsDetailAndResend": "SMS Details & Resend", "smsDetailAndResend": "SMS Details & Resend",
@@ -883,7 +897,13 @@
"receiver": "Receiver (Number)", "receiver": "Receiver (Number)",
"sendSuccess": "SMS sent successfully.", "sendSuccess": "SMS sent successfully.",
"sendFailed": "[Failed] {{message}}", "sendFailed": "[Failed] {{message}}",
"sendFailedGeneric": "[Failed] Request failed." "sendFailedGeneric": "[Failed] Request failed.",
"all": "All",
"virtualAccountReqShort": "Virtual Account\nRequest",
"virtualAccountReqDepositShort": "Virtual Account\nRequest+Deposit",
"buyerReceiveNumber": "Buyer,Receive Number",
"receivePhoneNumber": "Receive Phone Number",
"queryResult": "Query Result"
}, },
"linkPay": { "linkPay": {
"sendRequest": "Send Request", "sendRequest": "Send Request",
@@ -1023,7 +1043,55 @@
"applyTitle": "Link Payment Request", "applyTitle": "Link Payment Request",
"messagePreview": "Message Preview", "messagePreview": "Message Preview",
"separateApprovalTitle": "Separate Approval Detail", "separateApprovalTitle": "Separate Approval Detail",
"resendSuccess": "Resend successful.", "previous": "Previous",
"next": "Next",
"requestProcessingError": "Unable to process request.",
"requestError": "An error occurred during request",
"confirmSendMessage": "Please confirm\nthe message to be sent",
"paymentRequestComplete": "Payment request has been completed.",
"merchantName": "Merchant Name",
"customerGreeting": "Hello, {buyerName}!",
"paymentGuideMessage": "NICEPAYMENTS Co., Ltd. is notifying you\nof the payment details.\nYou can check the details and proceed with payment by accessing the URL below.",
"separateApproval": "Link Payment_Separate Approval",
"extendPeriodNotice": "※ Extension Period: Up to 7 days, can be extended 3 times in total",
"linkBreakNotice": "※ Link Break: Function to close payment before expiration date, cannot be undone once broken",
"transactionAmount": "Transaction Amount",
"paymentStatus": "Payment Status",
"validityPeriod": "Validity Period",
"extendCount": "Extension Count",
"extendPeriod": "Extension Period",
"unset": "Not Set",
"extendPeriodAction": "Extend Period",
"linkBreakAction": "Break Link",
"noItemsSelected": "No items selected.",
"pleaseSelectExtendPeriod": "Please select extension period for all selected items.",
"allRequestSuccess": "All requests successful.",
"extendPeriodFailed": "Failed to extend validity period. Please check individual status.",
"extendPeriodFailedGeneric": "Failed to extend validity period.",
"linkBreakFailed": "Failed to break link. Please check individual status.",
"linkBreakFailedGeneric": "Failed to break link.",
"pleaseRetry": "Please try again",
"sendMethod": "Send Method",
"productPrice": "Product Price",
"productOrderNumber": "Product Order Number",
"paymentValidDate": "Payment Valid Date",
"until": "Until",
"selectDate": "Select Date",
"buyerEmail": "Buyer Email",
"buyerPhoneNumber": "Buyer\nPhone Number",
"buyerInfoVerification": "Buyer Info Verification",
"individual": "Individual",
"corporate": "Corporate",
"businessNumber10Digits": "Business Number (10 digits)",
"birthDate6Digits": "Birth Date (6 digits)",
"language": "Language",
"korean": "Korean",
"english": "English",
"linkContent": "Link Content",
"basic": "Basic",
"additional": "Additional",
"sendMethodFilter": "Send Method",
"processStatus": "Process Status",
"resendFailed": "Resend failed.", "resendFailed": "Resend failed.",
"resendError": "An error occurred during resend.", "resendError": "An error occurred during resend.",
"resendConfirm": "Do you want to resend?", "resendConfirm": "Do you want to resend?",

View File

@@ -868,14 +868,28 @@
"agreeOrReject": "정산 동의/거절" "agreeOrReject": "정산 동의/거절"
}, },
"ars": { "ars": {
"title": "신용카드 ARS 결제",
"paymentComplete": "결제완료", "paymentComplete": "결제완료",
"unpaid": "미결제", "unpaid": "미결제",
"pendingPayment": "결제대기", "pendingPayment": "결제대기",
"paymentSuccess": "결제성공", "paymentSuccess": "결제성공",
"expired": "기간만료", "expired": "기간만료",
"canceled": "취소완료" "canceled": "취소완료",
"paymentRequest": "결제 신청",
"merchant": "가맹점",
"orderNumber": "주문번호",
"productName": "상품명",
"amount": "금액",
"installmentPeriod": "할부기간",
"lumpSum": "일시불",
"buyerName": "구매자명",
"phoneNumber": "휴대폰 번호",
"email": "이메일",
"paymentMethod": "결제 방식",
"requestFailed": "신청을 실패하였습니다."
}, },
"sms": { "sms": {
"title": "SMS 결제 통보",
"virtualAccountRequest": "가상계좌 요청", "virtualAccountRequest": "가상계좌 요청",
"virtualAccountRequestDeposit": "가상계좌 요청 + 입금", "virtualAccountRequestDeposit": "가상계좌 요청 + 입금",
"smsDetailAndResend": "SMS 상세 & 재발송", "smsDetailAndResend": "SMS 상세 & 재발송",
@@ -883,7 +897,13 @@
"receiver": "수신자(번호)", "receiver": "수신자(번호)",
"sendSuccess": "SMS 발송을 성공하였습니다.", "sendSuccess": "SMS 발송을 성공하였습니다.",
"sendFailed": "[실패] {{message}}", "sendFailed": "[실패] {{message}}",
"sendFailedGeneric": "[실패] 신청을 실패하였습니다." "sendFailedGeneric": "[실패] 신청을 실패하였습니다.",
"all": "전체",
"virtualAccountReqShort": "가상계좌\n요청",
"virtualAccountReqDepositShort": "가상계좌\n요청+입금",
"buyerReceiveNumber": "주문자,수신번호",
"receivePhoneNumber": "수신번호",
"queryResult": "조회결과"
}, },
"linkPay": { "linkPay": {
"sendRequest": "발송요청", "sendRequest": "발송요청",
@@ -1023,7 +1043,55 @@
"applyTitle": "링크결제 신청", "applyTitle": "링크결제 신청",
"messagePreview": "메시지 미리보기", "messagePreview": "메시지 미리보기",
"separateApprovalTitle": "분리승인 상세", "separateApprovalTitle": "분리승인 상세",
"resendSuccess": "재발송을 성공하였습니다.", "previous": "이전",
"next": "다음",
"requestProcessingError": "요청을 처리할 수 없습니다.",
"requestError": "요청 중 오류가 발생했습니다",
"confirmSendMessage": "발송 메시지를\n최종 확인하세요",
"paymentRequestComplete": "결제 신청이 완료되었습니다.",
"merchantName": "가맹점 상호",
"customerGreeting": "{buyerName} 고객님, 안녕하세요?",
"paymentGuideMessage": "나이스페이먼츠 주식회사에서\n결제하실 내역 안내드립니다.\n아래 URL로 접속하시면 상세 내역 확인과 결제 진행이 가능합니다.",
"separateApproval": "링크결제_분리승인",
"extendPeriodNotice": "※ 연장 기간: 최대 7일, 총 3번 연장 가능",
"linkBreakNotice": "※ 링크 중단: 유효기간 전, 결제를 마감하는 기능, 링크중단 시 원복 불가",
"transactionAmount": "거래금액",
"paymentStatus": "결제상태",
"validityPeriod": "유효기간",
"extendCount": "연장횟수",
"extendPeriod": "연장기간",
"unset": "미설정",
"extendPeriodAction": "기간연장",
"linkBreakAction": "링크중단",
"noItemsSelected": "선택된 항목이 없습니다.",
"pleaseSelectExtendPeriod": "모든 선택된 항목의 연장 기간을 선택해주세요.",
"allRequestSuccess": "전체요청 성공했습니다.",
"extendPeriodFailed": "유효기간 연장에 실패했습니다. 개별 상태를 확인해주세요.",
"extendPeriodFailedGeneric": "유효기간 연장에 실패했습니다.",
"linkBreakFailed": "링크중단 요청에 실패했습니다. 개별 상태를 확인해주세요.",
"linkBreakFailedGeneric": "링크중단 요청에 실패했습니다.",
"pleaseRetry": "다시 시도해 주세요",
"sendMethod": "발송 수단",
"productPrice": "상품가격",
"productOrderNumber": "상품 주문번호",
"paymentValidDate": "결제 유효일",
"until": "까지",
"selectDate": "날짜 선택",
"buyerEmail": "구매자 이메일",
"buyerPhoneNumber": "구매자\n휴대폰 번호",
"buyerInfoVerification": "구매자 정보 대조",
"individual": "개인",
"corporate": "법인",
"businessNumber10Digits": "사업자번호 10자리",
"birthDate6Digits": "생년월일 6자리",
"language": "언어",
"korean": "국문",
"english": "영문",
"linkContent": "링크내용",
"basic": "기본",
"additional": "추가",
"sendMethodFilter": "발송수단",
"processStatus": "진행상태",
"resendFailed": "재발송을 실패하였습니다.", "resendFailed": "재발송을 실패하였습니다.",
"resendError": "재발송 중 오류가 발생했습니다.", "resendError": "재발송 중 오류가 발생했습니다.",
"resendConfirm": "재발송 하시겠습니까?", "resendConfirm": "재발송 하시겠습니까?",

View File

@@ -69,7 +69,7 @@ export const ArsListPage = () => {
onIntersect onIntersect
}); });
useSetHeaderTitle('신용카드 ARS 결제'); useSetHeaderTitle(t('additionalService.ars.title'));
useSetHeaderType(HeaderType.LeftArrow); useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false); useSetFooterMode(false);
useSetOnBack(() => { useSetOnBack(() => {
@@ -248,23 +248,23 @@ export const ArsListPage = () => {
/> />
<button <button
className="filter-btn" className="filter-btn"
aria-label="필터" aria-label={t('filter.filter')}
onClick={() => onClickToOpenFilter()} onClick={() => onClickToOpenFilter()}
> >
<img <img
src={IMAGE_ROOT + '/ico_setting.svg'} src={IMAGE_ROOT + '/ico_setting.svg'}
alt="검색옵션" alt={t('common.searchOptions')}
/> />
</button> </button>
</div> </div>
<button <button
className="download-btn" className="download-btn"
aria-label="다운로드" aria-label={t('common.download')}
onClick={() => onClickToOpenEmailBottomSheet()} onClick={() => onClickToOpenEmailBottomSheet()}
> >
<img <img
src={IMAGE_ROOT + '/ico_download.svg'} src={IMAGE_ROOT + '/ico_download.svg'}
alt="다운로드" alt={t('common.download')}
/> />
</button> </button>
</div> </div>
@@ -300,7 +300,7 @@ export const ArsListPage = () => {
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={() => onClickToNavigate()} onClick={() => onClickToNavigate()}
> </button> >{t('additionalService.ars.paymentRequest')}</button>
</div> </div>
</main> </main>
<ArsFilter <ArsFilter

View File

@@ -1,4 +1,5 @@
import { ChangeEvent, useState } from 'react'; import { ChangeEvent, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PATHS } from '@/shared/constants/paths'; import { PATHS } from '@/shared/constants/paths';
import { useLocation } from 'react-router'; import { useLocation } from 'react-router';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
@@ -18,6 +19,7 @@ import { snackBar } from '@/shared/lib';
import { NumericFormat, PatternFormat } from 'react-number-format'; import { NumericFormat, PatternFormat } from 'react-number-format';
export const ArsRequestPage = () => { export const ArsRequestPage = () => {
const { t } = useTranslation();
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const location = useLocation(); const location = useLocation();
@@ -39,7 +41,7 @@ export const ArsRequestPage = () => {
const [successPageOn, setSuccessPageOn] = useState<boolean>(false); const [successPageOn, setSuccessPageOn] = useState<boolean>(false);
const [resultMessage, setResultMessage] = useState<string>(''); const [resultMessage, setResultMessage] = useState<string>('');
useSetHeaderTitle('결제 신청'); useSetHeaderTitle(t('additionalService.ars.paymentRequest'));
useSetHeaderType(HeaderType.LeftArrow); useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false); useSetFooterMode(false);
useSetOnBack(() => { useSetOnBack(() => {
@@ -63,12 +65,13 @@ export const ArsRequestPage = () => {
if (rs.status) { if (rs.status) {
setSuccessPageOn(true); setSuccessPageOn(true);
} else { } else {
const errorMessage = rs.error?.message || '신청을 실패하였습니다.'; const errorMessage = rs.error?.message || t('additionalService.ars.requestFailed');
snackBar(`[실패] ${errorMessage}`); snackBar(`[${t('common.failed')}] ${errorMessage}`);
} }
}) })
.catch((error) => { .catch((error) => {
snackBar(`[실패] ${error?.response?.data?.message || error?.response?.data?.error?.message}` || '[실패] 신청을 실패하였습니다.') const errorMsg = error?.response?.data?.message || error?.response?.data?.error?.message || t('additionalService.ars.requestFailed');
snackBar(`[${t('common.failed')}] ${errorMsg}`);
}) })
}; };
@@ -127,7 +130,7 @@ export const ArsRequestPage = () => {
<div className="option-list"> <div className="option-list">
<div className="billing-form gap-16"> <div className="billing-form gap-16">
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"> <span>*</span></div> <div className="billing-label">{t('additionalService.ars.merchant')} <span>*</span></div>
<div className="billing-field"> <div className="billing-field">
<select <select
value={mid} value={mid}
@@ -146,7 +149,7 @@ export const ArsRequestPage = () => {
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"> <span>*</span></div> <div className="billing-label">{t('additionalService.ars.orderNumber')} <span>*</span></div>
<div className="billing-field"> <div className="billing-field">
<input <input
type="text" type="text"
@@ -157,7 +160,7 @@ export const ArsRequestPage = () => {
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"> <span>*</span></div> <div className="billing-label">{t('additionalService.ars.productName')} <span>*</span></div>
<div className="billing-field"> <div className="billing-field">
<input <input
type="text" type="text"
@@ -168,7 +171,7 @@ export const ArsRequestPage = () => {
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"> <span>*</span></div> <div className="billing-label">{t('additionalService.ars.amount')} <span>*</span></div>
<div className="billing-field"> <div className="billing-field">
<NumericFormat <NumericFormat
value={amount} value={amount}
@@ -184,20 +187,20 @@ export const ArsRequestPage = () => {
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"> <span>*</span></div> <div className="billing-label">{t('additionalService.ars.installmentPeriod')} <span>*</span></div>
<div className="billing-field"> <div className="billing-field">
<select <select
disabled disabled
value={instmntMonth} value={instmntMonth}
onChange={(e: ChangeEvent<HTMLSelectElement>) => setInstmntMonth(e.target.value)} onChange={(e: ChangeEvent<HTMLSelectElement>) => setInstmntMonth(e.target.value)}
> >
<option value="00"></option> <option value="00">{t('additionalService.ars.lumpSum')}</option>
</select> </select>
</div> </div>
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"> <span>*</span></div> <div className="billing-label">{t('additionalService.ars.buyerName')} <span>*</span></div>
<div className="billing-field"> <div className="billing-field">
<input <input
type="text" type="text"
@@ -208,7 +211,7 @@ export const ArsRequestPage = () => {
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"> <span>*</span></div> <div className="billing-label">{t('additionalService.ars.phoneNumber')} <span>*</span></div>
<div className="billing-field"> <div className="billing-field">
<input <input
type="tel" type="tel"
@@ -227,7 +230,7 @@ export const ArsRequestPage = () => {
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"></div> <div className="billing-label">{t('additionalService.ars.email')}</div>
<div className="billing-field"> <div className="billing-field">
<input <input
type="text" type="text"
@@ -241,7 +244,7 @@ export const ArsRequestPage = () => {
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"> <span>*</span></div> <div className="billing-label">{t('additionalService.ars.paymentMethod')} <span>*</span></div>
<div className="billing-field"> <div className="billing-field">
{getArsPaymentMethodBtns()} {getArsPaymentMethodBtns()}
</div> </div>
@@ -253,7 +256,7 @@ export const ArsRequestPage = () => {
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={() => onClickToRequest()} onClick={() => onClickToRequest()}
disabled={!isFormValid()} disabled={!isFormValid()}
> </button> >{t('additionalService.ars.paymentRequest')}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { HeaderType } from '@/entities/common/model/types'; import { HeaderType } from '@/entities/common/model/types';
import { useSetFooterMode, useSetHeaderTitle, useSetHeaderType } from '@/widgets/sub-layout/use-sub-layout'; import { useSetFooterMode, useSetHeaderTitle, useSetHeaderType } from '@/widgets/sub-layout/use-sub-layout';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
@@ -9,13 +10,14 @@ import { ExtensionLinkPayRequestParams, ExtensionLinkPayRequestResponse, LinkPay
import { snackBar } from '@/shared/lib'; import { snackBar } from '@/shared/lib';
export const LinkPaymentApplyConfirmPage = () => { export const LinkPaymentApplyConfirmPage = () => {
const { t } = useTranslation();
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const location = useLocation(); const location = useLocation();
const formData: LinkPaymentFormData = location.state?.formData; const formData: LinkPaymentFormData = location.state?.formData;
const { mutateAsync: linkPayRequest } = useExtensionLinkPayRequestMutation(); const { mutateAsync: linkPayRequest } = useExtensionLinkPayRequestMutation();
useSetHeaderTitle('메시지 미리보기'); useSetHeaderTitle(t('additionalService.linkPayment.messagePreview'));
useSetHeaderType(HeaderType.LeftArrow); useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false); useSetFooterMode(false);
@@ -53,16 +55,16 @@ export const LinkPaymentApplyConfirmPage = () => {
navigate(PATHS.additionalService.linkPayment.confirmSuccess); navigate(PATHS.additionalService.linkPayment.confirmSuccess);
} else { } else {
// 일반 에러 메시지 // 일반 에러 메시지
const errorMessage = rs.error?.message || '요청을 처리할 수 없습니다.'; const errorMessage = rs.error?.message || t('additionalService.linkPayment.requestProcessingError');
snackBar(`[실패] ${errorMessage}`); snackBar(`[${t('common.failed')}] ${errorMessage}`);
} }
}) })
.catch((error) => { .catch((error) => {
// 네트워크 에러 등 예외 상황 // 네트워크 에러 등 예외 상황
const errorMessage = error?.response?.data?.error?.message || const errorMessage = error?.response?.data?.error?.message ||
error?.message || error?.message ||
'요청 중 오류가 발생했습니다'; t('additionalService.linkPayment.requestError');
snackBar(`[실패] ${errorMessage}`); snackBar(`[${t('common.failed')}] ${errorMessage}`);
}); });
}; };
@@ -76,20 +78,18 @@ export const LinkPaymentApplyConfirmPage = () => {
<div className="sub-wrap"> <div className="sub-wrap">
<div className="preview-body"> <div className="preview-body">
<div className="attention-icon" aria-hidden="true"> <div className="attention-icon" aria-hidden="true">
<img src={IMAGE_ROOT + '/ico_alert.svg'} alt="주의" /> <img src={IMAGE_ROOT + '/ico_alert.svg'} alt={t('common.confirm')} />
</div> </div>
<h1 className="preview-title"> <br /> </h1> <h1 className="preview-title">{t('additionalService.linkPayment.confirmSendMessage')}</h1>
<div className="preview-result"> <div className="preview-result">
<p className="preview-text"> <p className="preview-text">
{formData.buyerName} , ?<br /> {t('additionalService.linkPayment.customerGreeting', { buyerName: formData.buyerName })}<br />
<br /> {t('additionalService.linkPayment.paymentGuideMessage')}<br /><br />
.<br />
URL로 .<br /><br />
!$&#123;pay_url&#125;<br /><br /> !$&#123;pay_url&#125;<br /><br />
<b> <b>
상호 : 나이스페이먼 <br /> {t('additionalService.linkPayment.merchantName')} : <br />
: {formData.goodsName}<br /> {t('transaction.fields.productName')} : {formData.goodsName}<br />
: {formData.amount.toLocaleString()} {t('transaction.fields.amount')} : {formData.amount.toLocaleString()}{t('common.currencyUnit')}
</b> </b>
</p> </p>
</div> </div>
@@ -100,11 +100,11 @@ export const LinkPaymentApplyConfirmPage = () => {
<button <button
className="btn-50 btn-darkgray flex-1" className="btn-50 btn-darkgray flex-1"
onClick={() => onClickToBack()} onClick={() => onClickToBack()}
></button> >{t('additionalService.linkPayment.previous')}</button>
<button <button
className="btn-50 btn-blue flex-3" className="btn-50 btn-blue flex-3"
onClick={() => onClickToConfirm()} onClick={() => onClickToConfirm()}
> </button> >{t('additionalService.linkPay.paymentRequest')}</button>
</div> </div>
</main> </main>
</> </>

View File

@@ -1,4 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LinkPaymentStep1 } from '@/entities/additional-service/ui/link-payment/apply/link-payment-step1'; import { LinkPaymentStep1 } from '@/entities/additional-service/ui/link-payment/apply/link-payment-step1';
import { LinkPaymentStep2 } from '@/entities/additional-service/ui/link-payment/apply/link-payment-step2'; import { LinkPaymentStep2 } from '@/entities/additional-service/ui/link-payment/apply/link-payment-step2';
import { HeaderType } from '@/entities/common/model/types'; import { HeaderType } from '@/entities/common/model/types';
@@ -14,6 +15,7 @@ import moment from 'moment';
export const LinkPaymentApplyPage = () => { export const LinkPaymentApplyPage = () => {
const { t } = useTranslation();
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const midOptionsWithoutGids = useStore.getState().UserStore.selectOptionsMidsWithoutGids; const midOptionsWithoutGids = useStore.getState().UserStore.selectOptionsMidsWithoutGids;
@@ -43,7 +45,7 @@ export const LinkPaymentApplyPage = () => {
linkContentType: LinkContentType.BASIC linkContentType: LinkContentType.BASIC
}); });
useSetHeaderTitle('링크결제 신청'); useSetHeaderTitle(t('additionalService.linkPayment.applyTitle'));
useSetHeaderType(HeaderType.LeftArrow); useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false); useSetFooterMode(false);
@@ -144,7 +146,7 @@ export const LinkPaymentApplyPage = () => {
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={() => onClickToChangeTab()} onClick={() => onClickToChangeTab()}
disabled={!isStep1Valid()} disabled={!isStep1Valid()}
></button> >{t('additionalService.linkPayment.next')}</button>
</div> </div>
} }
{(processStep === ProcessStep.Two) && {(processStep === ProcessStep.Two) &&
@@ -152,12 +154,12 @@ export const LinkPaymentApplyPage = () => {
<button <button
className="btn-50 btn-darkgray flex-1" className="btn-50 btn-darkgray flex-1"
onClick={() => onClickToBack()} onClick={() => onClickToBack()}
></button> >{t('additionalService.linkPayment.previous')}</button>
<button <button
className="btn-50 btn-blue flex-3" className="btn-50 btn-blue flex-3"
onClick={() => onClickToChangeTab()} onClick={() => onClickToChangeTab()}
disabled={!isStep2Valid()} disabled={!isStep2Valid()}
> </button> >{t('additionalService.linkPay.paymentRequest')}</button>
</div> </div>
} }
</div> </div>

View File

@@ -1,12 +1,14 @@
import { useTranslation } from 'react-i18next';
import { HeaderType } from '@/entities/common/model/types'; import { HeaderType } from '@/entities/common/model/types';
import { useSetFooterMode, useSetHeaderTitle, useSetHeaderType } from '@/widgets/sub-layout/use-sub-layout'; import { useSetFooterMode, useSetHeaderTitle, useSetHeaderType } from '@/widgets/sub-layout/use-sub-layout';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { PATHS } from "@/shared/constants/paths"; import { PATHS } from "@/shared/constants/paths";
export const LinkPaymentApplySuccessPage = () => { export const LinkPaymentApplySuccessPage = () => {
const { t } = useTranslation();
const { navigate } = useNavigate(); const { navigate } = useNavigate();
useSetHeaderTitle('링크결제 신청'); useSetHeaderTitle(t('additionalService.linkPayment.applyTitle'));
useSetHeaderType(HeaderType.LeftArrow); useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false); useSetFooterMode(false);
@@ -20,15 +22,15 @@ export const LinkPaymentApplySuccessPage = () => {
<div className="success-body"> <div className="success-body">
<div className="success-icon" aria-hidden="true"></div> <div className="success-icon" aria-hidden="true"></div>
<h1 className="success-title"> <h1 className="success-title">
<span></span><br/> <span>{t('additionalService.linkPayment.title')}</span><br/>
<span> .</span> <span>{t('additionalService.linkPayment.paymentRequestComplete')}</span>
</h1> </h1>
</div> </div>
<div className="apply-row"> <div className="apply-row">
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={() => onClickToHome()} onClick={() => onClickToHome()}
></button> >{t('common.confirm')}</button>
</div> </div>
</div> </div>
</> </>

View File

@@ -1,4 +1,5 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { PATHS } from "@/shared/constants/paths"; import { PATHS } from "@/shared/constants/paths";
import { import {
@@ -25,6 +26,7 @@ export const LinkPaymentApplyFailPage = ({
errorMessage, errorMessage,
onClose onClose
}: LinkPaymentApplyFailPageProps) => { }: LinkPaymentApplyFailPageProps) => {
const { t } = useTranslation();
const onClickToClose = () => { const onClickToClose = () => {
setPageOn(false); setPageOn(false);
@@ -51,7 +53,7 @@ export const LinkPaymentApplyFailPage = ({
aria-hidden="true" aria-hidden="true"
></div> ></div>
<h1 className="success-title"> <h1 className="success-title">
<span>_분리승인</span> <span>{t('additionalService.linkPayment.separateApproval')}</span>
</h1> </h1>
{resultMessage && ( {resultMessage && (
@@ -62,8 +64,8 @@ export const LinkPaymentApplyFailPage = ({
<div className="success-result"> <div className="success-result">
<p className="result-text align-left position_label"> <p className="result-text align-left position_label">
<span> :</span> <span>{t('common.result')} :</span>
<span>{errorMessage || '다시 시도해 주세요'}</span> <span>{errorMessage || t('additionalService.linkPayment.pleaseRetry')}</span>
</p> </p>
</div> </div>
</div> </div>
@@ -71,7 +73,7 @@ export const LinkPaymentApplyFailPage = ({
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={onClickToClose} onClick={onClickToClose}
></button> >{t('common.confirm')}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,5 @@
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PATHS } from '@/shared/constants/paths'; import { PATHS } from '@/shared/constants/paths';
import { useLocation } from 'react-router'; import { useLocation } from 'react-router';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
@@ -20,6 +21,7 @@ import { useStore } from '@/shared/model/store';
import moment from 'moment'; import moment from 'moment';
export const LinkPaymentSeparateApprovalPage = () => { export const LinkPaymentSeparateApprovalPage = () => {
const { t } = useTranslation();
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const location = useLocation(); const location = useLocation();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
@@ -43,7 +45,7 @@ export const LinkPaymentSeparateApprovalPage = () => {
const { mutateAsync: callItems } = useExtensionLinkPaySeparateDetail(); const { mutateAsync: callItems } = useExtensionLinkPaySeparateDetail();
const { mutateAsync: linkPaySeparateAction } = useExtensionLinkPaySeparateAction(); const { mutateAsync: linkPaySeparateAction } = useExtensionLinkPaySeparateAction();
useSetHeaderTitle('분리승인 상세'); useSetHeaderTitle(t('additionalService.linkPayment.separateApprovalTitle'));
useSetHeaderType(HeaderType.RightClose); useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => { useSetOnBack(() => {
navigate(PATHS.additionalService.linkPayment.shippingHistory); navigate(PATHS.additionalService.linkPayment.shippingHistory);
@@ -189,7 +191,7 @@ export const LinkPaymentSeparateApprovalPage = () => {
// 선택된 항목이 없으면 리턴 // 선택된 항목이 없으면 리턴
if (selectedItems.length === 0) { if (selectedItems.length === 0) {
setErrorMessage('선택된 항목이 없습니다.'); setErrorMessage(t('additionalService.linkPayment.noItemsSelected'));
setFailPageOn(true); setFailPageOn(true);
return; return;
} }
@@ -203,7 +205,7 @@ export const LinkPaymentSeparateApprovalPage = () => {
const allHaveExtendPeriod = selectedSubItems.every(id => extendPeriods[id]); const allHaveExtendPeriod = selectedSubItems.every(id => extendPeriods[id]);
if (!allHaveExtendPeriod) { if (!allHaveExtendPeriod) {
setErrorMessage('모든 선택된 항목의 연장 기간을 선택해주세요.'); setErrorMessage(t('additionalService.linkPayment.pleaseSelectExtendPeriod'));
setFailPageOn(true); setFailPageOn(true);
return; return;
} }
@@ -237,16 +239,16 @@ export const LinkPaymentSeparateApprovalPage = () => {
setTotalCount(response.totalCount); setTotalCount(response.totalCount);
if (response.success) { if (response.success) {
setResultMessage(`전체요청 성공했습니다.`); setResultMessage(t('additionalService.linkPayment.allRequestSuccess'));
setSuccessPageOn(true); setSuccessPageOn(true);
} else { } else {
setResultMessage(`유효기간 연장에 실패했습니다. 개별 상태를 확인해주세요.`); setResultMessage(t('additionalService.linkPayment.extendPeriodFailed'));
setErrorMessage(''); setErrorMessage('');
setFailPageOn(true); setFailPageOn(true);
} }
}).catch((error) => { }).catch((error) => {
console.error('기간연장 실패:', error); console.error('기간연장 실패:', error);
setResultMessage('유효기간 연장에 실패했습니다.'); setResultMessage(t('additionalService.linkPayment.extendPeriodFailedGeneric'));
setFailPageOn(true); setFailPageOn(true);
}); });
}; };
@@ -257,7 +259,7 @@ export const LinkPaymentSeparateApprovalPage = () => {
// 선택된 항목이 없으면 리턴 // 선택된 항목이 없으면 리턴
if (selectedItems.length === 0) { if (selectedItems.length === 0) {
setErrorMessage('선택된 항목이 없습니다.'); setErrorMessage(t('additionalService.linkPayment.noItemsSelected'));
setFailPageOn(true); setFailPageOn(true);
return; return;
} }
@@ -287,16 +289,16 @@ export const LinkPaymentSeparateApprovalPage = () => {
setFailCount(response.failCount); setFailCount(response.failCount);
setTotalCount(response.totalCount); setTotalCount(response.totalCount);
if (response.success) { if (response.success) {
setResultMessage(`전체요청 성공했습니다.`); setResultMessage(t('additionalService.linkPayment.allRequestSuccess'));
setSuccessPageOn(true); setSuccessPageOn(true);
} else { } else {
setResultMessage('링크중단 요청에 실패했습니다. 개별 상태를 확인해주세요.'); setResultMessage(t('additionalService.linkPayment.linkBreakFailed'));
setErrorMessage(''); setErrorMessage('');
setFailPageOn(true); setFailPageOn(true);
} }
}).catch((error) => { }).catch((error) => {
console.error('링크중단 실패:', error); console.error('링크중단 실패:', error);
setResultMessage('링크중단 요청에 실패했습니다.'); setResultMessage(t('additionalService.linkPayment.linkBreakFailedGeneric'));
setFailPageOn(true); setFailPageOn(true);
}); });
}; };
@@ -334,8 +336,8 @@ export const LinkPaymentSeparateApprovalPage = () => {
<div className="tab-pane sub active"> <div className="tab-pane sub active">
<div className="separate-approval-section"> <div className="separate-approval-section">
<div className="approval-notice-box"> <div className="approval-notice-box">
<p> 기간: 최대 7, 3 </p> <p>{t('additionalService.linkPayment.extendPeriodNotice')}</p>
<p> 중단: 유효기간 , , </p> <p>{t('additionalService.linkPayment.linkBreakNotice')}</p>
</div> </div>
<div className="approval-cards-wrapper"> <div className="approval-cards-wrapper">
@@ -360,17 +362,17 @@ export const LinkPaymentSeparateApprovalPage = () => {
<div className="card-body"> <div className="card-body">
<ul className="info-list"> <ul className="info-list">
<li> <li>
<span className="label"> :</span> <span className="label"> {t('additionalService.linkPayment.transactionAmount')}:</span>
<span className="value">{item.amount.toLocaleString()}</span> <span className="value">{item.amount.toLocaleString()}</span>
</li> </li>
<li> <li>
<span className="label"> :</span> <span className="label"> {t('additionalService.linkPayment.paymentStatus')}:</span>
<span className="value">{item.paymentStatusName}</span> <span className="value">{item.paymentStatusName}</span>
</li> </li>
{item.type !== LinkPaymentSeparateType.MAIN && ( {item.type !== LinkPaymentSeparateType.MAIN && (
<> <>
<li> <li>
<span className="label"> :</span> <span className="label"> {t('additionalService.linkPayment.validityPeriod')}:</span>
<span className="value"> <span className="value">
{item.paymentLimitDate {item.paymentLimitDate
? moment(item.paymentLimitDate, 'YYYYMMDD').format('YYYY/MM/DD') ? moment(item.paymentLimitDate, 'YYYYMMDD').format('YYYY/MM/DD')
@@ -379,7 +381,7 @@ export const LinkPaymentSeparateApprovalPage = () => {
</span> </span>
</li> </li>
<li> <li>
<span className="label"> :</span> <span className="label"> {t('additionalService.linkPayment.extendCount')}:</span>
<span className="value">{item.paymentLimitCount}</span> <span className="value">{item.paymentLimitCount}</span>
</li> </li>
</> </>
@@ -388,7 +390,7 @@ export const LinkPaymentSeparateApprovalPage = () => {
</div> </div>
<div className="card-footer"> <div className="card-footer">
<div className="period-selector"> <div className="period-selector">
<label></label> <label>{t('additionalService.linkPayment.extendPeriod')}</label>
<select <select
value={extendPeriods[itemId] || ''} value={extendPeriods[itemId] || ''}
onChange={(e) => handleExtendPeriodChange(itemId, e.target.value)} onChange={(e) => handleExtendPeriodChange(itemId, e.target.value)}
@@ -399,7 +401,7 @@ export const LinkPaymentSeparateApprovalPage = () => {
? '-' ? '-'
: !canExtendPeriod(item) : !canExtendPeriod(item)
? '-' ? '-'
: '미설정'} : t('additionalService.linkPayment.unset')}
</option> </option>
{canExtendPeriod(item) && [1, 2, 3, 4, 5, 6, 7].map(days => { {canExtendPeriod(item) && [1, 2, 3, 4, 5, 6, 7].map(days => {
const baseDate = moment(item.paymentLimitDate, 'YYYYMMDD'); const baseDate = moment(item.paymentLimitDate, 'YYYYMMDD');
@@ -420,13 +422,13 @@ export const LinkPaymentSeparateApprovalPage = () => {
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={onClickToValidityPeriod} onClick={onClickToValidityPeriod}
disabled={!isExtendButtonEnabled()} disabled={!isExtendButtonEnabled()}
> >{t('additionalService.linkPayment.extendPeriodAction')}
</button> </button>
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={onClickToSendLink} onClick={onClickToSendLink}
disabled={!isLinkBreadkEnabled()} disabled={!isLinkBreadkEnabled()}
> >{t('additionalService.linkPayment.linkBreakAction')}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,5 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import { import {
FilterMotionDuration, FilterMotionDuration,
FilterMotionStyle, FilterMotionStyle,
@@ -24,6 +25,7 @@ export const LinkPaymentApplySuccessPage = ({
totalCount, totalCount,
onClose onClose
}: LinkPaymentApplySuccessPageProps) => { }: LinkPaymentApplySuccessPageProps) => {
const { t } = useTranslation();
const onClickToClose = () => { const onClickToClose = () => {
setPageOn(false); setPageOn(false);
@@ -50,13 +52,13 @@ export const LinkPaymentApplySuccessPage = ({
aria-hidden="true" aria-hidden="true"
></div> ></div>
<h1 className="success-title"> <h1 className="success-title">
<span>_분리승인</span> <span>{t('additionalService.linkPayment.separateApproval')}</span>
</h1> </h1>
<div className="success-result"> <div className="success-result">
<p className="result-text align-left position_label"> <p className="result-text align-left position_label">
<span> :</span> <span>{t('common.result')} :</span>
<span>{resultMessage || '전체요청 성공했습니다.'}</span> <span>{resultMessage || t('additionalService.linkPayment.allRequestSuccess')}</span>
</p> </p>
</div> </div>
</div> </div>
@@ -64,7 +66,7 @@ export const LinkPaymentApplySuccessPage = ({
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={onClickToClose} onClick={onClickToClose}
></button> >{t('common.confirm')}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
import moment from 'moment'; import moment from 'moment';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { IMAGE_ROOT } from '@/shared/constants/common'; import { IMAGE_ROOT } from '@/shared/constants/common';
import { SmsPaymentDetailResend } from '@/entities/additional-service/ui/sms-payment/sms-payment-detail-resend'; import { SmsPaymentDetailResend } from '@/entities/additional-service/ui/sms-payment/sms-payment-detail-resend';
@@ -25,6 +26,7 @@ import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access
import useIntersectionObserver from '@/widgets/intersection-observer'; import useIntersectionObserver from '@/widgets/intersection-observer';
export const SmsPaymentPage = () => { export const SmsPaymentPage = () => {
const { t } = useTranslation();
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
@@ -70,7 +72,7 @@ export const SmsPaymentPage = () => {
onIntersect onIntersect
}); });
useSetHeaderTitle('SMS 결제 통보'); useSetHeaderTitle(t('additionalService.sms.title'));
useSetHeaderType(HeaderType.LeftArrow); useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false); useSetFooterMode(false);
useSetOnBack(() => { useSetOnBack(() => {