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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import moment from 'moment';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { IMAGE_ROOT } from '@/shared/constants/common';
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';
export const SmsPaymentPage = () => {
const { t } = useTranslation();
const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid;
@@ -70,7 +72,7 @@ export const SmsPaymentPage = () => {
onIntersect
});
useSetHeaderTitle('SMS 결제 통보');
useSetHeaderTitle(t('additionalService.sms.title'));
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
useSetOnBack(() => {