📢 해당 포스트는 스타트업 프로젝트를 진행하며, 직접 작성한 코드를 기록용으로 작성함을 명시합니다.
예약을 한 상품을 취소하도록 구현하였습니다.
'use client';
import PaymentDetail from '@/components/common/PaymentDetail';
import Checkbox from '@/components/common/Checkbox';
import Btn from '@/components/common/Btn';
import { useEffect, useState } from 'react';
import {
CancelButtonWrap,
CancelContentsSpanData,
CancelContentsSpanTitle,
CancelContentsWrap,
CancelPaymentDetailWrap,
CancelWholeWrap,
CheckFont,
CheckboxWrap,
GreyLine,
InfoCancelTitle,
InfoCancelWrap,
ModalStyle,
ModalWrap,
ReservationCancelTextTitle,
ReservationCancelWrap,
TotalPayText,
TotalPayWrap,
} from './styles';
import GreyRule from './greyrule';
import { CancelModal } from '@/app/reservationcancel/components/CancelModal';
import { CheckModal } from '@/app/reservationcancel/components/CheckModal';
import instance from '@/apis/instance';
const ReservationCancelUI = ({ id }: { id: number }) => {
const [confirmModalOpen, setConfirmModalOpen] = useState<boolean>(false);
const [checkModalOpen, setCheckModalOpen] = useState<boolean>(false);
const [priceRefund, setPriceRefund] = useState(null);
const [refundPercent, setRefundPercent] = useState();
const [checked, setChecked] = useState<boolean>(false);
const [submit, setSubmit] = useState<boolean>(false);
const [paymentDetail, setPaymentDetail] = useState(null);
const handleConfirmModalOpen = () => {
setConfirmModalOpen(true);
};
const handleConfirmModalClose = () => {
setConfirmModalOpen(false);
};
const handleCheckModalOpen = () => {
setCheckModalOpen(true);
};
const handleCheckModalClose = () => {
setCheckModalOpen(false);
};
const handleClick = () => {
setChecked(!checked);
};
const alertClick = () => {
if (checked) {
setSubmit(!submit);
handleConfirmModalOpen();
} else {
handleCheckModalOpen();
}
};
const getAccessTokenFromLocalStorage = () =>
localStorage.getItem('accessToken');
const RefundPrice = () => {
const accessToken = getAccessTokenFromLocalStorage();
instance
.get(`/api/v1/payment/refund/${id}/user/estimate`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
// console.log(response);
setPriceRefund(response.data.data);
console.log(priceRefund);
})
.catch((error) => {
console.log(error, '실패하였습니다');
});
};
const RefundPercent = () => {
const accessToken = getAccessTokenFromLocalStorage();
instance
.get(`/api/v1/payment/refund/${id}/user/estimate/percent`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
// console.log(response);
setRefundPercent(response.data.data.refundPercent);
console.log(refundPercent);
})
.catch((error) => {
console.log(error, '실패하였습니다');
});
};
const DetailPayment = () => {
const accessToken = getAccessTokenFromLocalStorage();
instance
.get(`/api/v1/payment/refund/${id}/page`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
// console.log(response);
setPaymentDetail(response.data.data);
console.log(paymentDetail);
})
.catch((error) => {
console.log(error, '실패하였습니다');
});
};
useEffect(() => {
RefundPrice();
RefundPercent();
DetailPayment();
}, []);
return (
<>
<ReservationCancelWrap>
<ReservationCancelTextTitle>예약 취소하기</ReservationCancelTextTitle>
<CancelPaymentDetailWrap>
<PaymentDetail
title={paymentDetail?.exhibitionTitle}
location={paymentDetail?.exhibitionLocation}
nickname={paymentDetail?.hostNickname}
host={paymentDetail?.hostNickname}
imgUrl={`https:${paymentDetail?.exhibitionPreImg}`}
// category={dummy.category}
date={paymentDetail?.exhibitionStockDatetime}
name={paymentDetail?.userName}
phoneNumber={paymentDetail?.userEmail}
job={paymentDetail?.userJobName}
company={paymentDetail?.userCompanyName}
/>
</CancelPaymentDetailWrap>
<InfoCancelWrap>
<InfoCancelTitle>환불 안내</InfoCancelTitle>
<CancelWholeWrap>
<CancelContentsWrap>
<CancelContentsSpanTitle>결제금액</CancelContentsSpanTitle>
<CancelContentsSpanTitle>취소 수수료</CancelContentsSpanTitle>
</CancelContentsWrap>
<CancelContentsWrap>
<CancelContentsSpanData>
{priceRefund?.paymentAmount}원
</CancelContentsSpanData>
<CancelContentsSpanData>
{priceRefund?.feeAmount}원
</CancelContentsSpanData>
</CancelContentsWrap>
</CancelWholeWrap>
<GreyLine />
<TotalPayWrap>
<TotalPayText>최종 환불 금액</TotalPayText>
<TotalPayText>{priceRefund?.refundAmount}원</TotalPayText>
</TotalPayWrap>
</InfoCancelWrap>
<GreyRule />
<CheckboxWrap>
<CheckFont>위 환불 금액 및 규정을 모두 확인하였습니다.</CheckFont>
<Checkbox checked={checked} onChange={handleClick} />
</CheckboxWrap>
<CancelButtonWrap>
<Btn
text="취소하기"
disabled={false}
size="middle"
state="orange"
onClick={alertClick}
/>
{submit && (
<CancelModal
isOpen={confirmModalOpen}
isClose={handleConfirmModalClose}
// paymentId={paymentId}
refundPercent={refundPercent}
id={id}
/>
)}
{checkModalOpen && (
<CheckModal
isOpen={checkModalOpen}
isClose={handleCheckModalClose}
/>
)}
</CancelButtonWrap>
</ReservationCancelWrap>
</>
);
};
export default ReservationCancelUI;
const getAccessTokenFromLocalStorage = () =>
localStorage.getItem('accessToken');
왜 ?
로컬스토리지에 저장된 accessToken으로 해당 회원이 로그인된 상태임을 알 수 있고,
따라서 회원에 맞는 데이터 (결제 데이터 등)을 가져올 수 있기 때문에
const DetailPayment = () => {
const accessToken = getAccessTokenFromLocalStorage();
instance
.get(`/api/v1/payment/refund/${id}/page`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
// console.log(response);
setPaymentDetail(response.data.data);
console.log(paymentDetail);
})
.catch((error) => {
console.log(error, '실패하였습니다');
});
};
${id}
를 보면, 동적라우팅 을 사용하여 구현하였다.
이전에 만들어 두었던 getAccessTokenFromLocalStorage
함수를 사용하여
accessToken 변수에 저장해주고
instance
를 사용하여 axios get을 해주었다.
헤더에 Authorization: Bearer ${accessToken}
넣어주고 , (Bearer)
const [paymentDetail, setPaymentDetail] = useState(null);
setPaymentDetail()
를 사용하여 paymentDetail 변수에 원하는 데이터 값을 넣어주었다.
아래와 같이
paymentDetail?
: paymentDetail이 있다면
paymentDetail?.exhibitionTitle
: paymentDetail의 exhibitionTitle 데이터를 가져온다.
<CancelPaymentDetailWrap>
<PaymentDetail
title={paymentDetail?.exhibitionTitle}
location={paymentDetail?.exhibitionLocation}
nickname={paymentDetail?.hostNickname}
host={paymentDetail?.hostNickname}
imgUrl={`https:${paymentDetail?.exhibitionPreImg}`}
// category={dummy.category}
date={paymentDetail?.exhibitionStockDatetime}
name={paymentDetail?.userName}
phoneNumber={paymentDetail?.userEmail}
job={paymentDetail?.userJobName}
company={paymentDetail?.userCompanyName}
/>
</CancelPaymentDetailWrap>
는 1번과 동일하게 처리해주면 된다.
useEffect(() => {
RefundPrice();
RefundPercent();
DetailPayment();
}, []);
useEffect
를 사용하여 컴포넌트가 처음 마운트 될 때
import { useEffect } from 'react';
import {
Overlay,
Wrapper,
Title,
CancelMessageText,
CancelMessageBox,
CheckBtnWrap,
} from '../index.styles';
import Btn from '@/components/common/Btn';
import { CheckModalProps } from '../index.types';
export const CheckModal = ({ isOpen, isClose }: CheckModalProps) => {
useEffect(() => {
if (isOpen) document.body.style.overflow = 'hidden';
else document.body.removeAttribute('style');
}, [isOpen]);
return (
<Overlay $isOpen={isOpen}>
<Wrapper>
<Title>예약 약관 동의</Title>
<CancelMessageBox>
<CancelMessageText>예약 확인을 체크해주세요.</CancelMessageText>
<CheckBtnWrap>
<Btn
text="확인"
state="orange"
disabled={false}
size="small"
onClick={isClose}
/>
</CheckBtnWrap>
</CancelMessageBox>
</Wrapper>
</Overlay>
);
};
import { useEffect, useState } from 'react';
import {
Overlay,
Wrapper,
Title,
CancelMessageText,
CancelMessageBox,
CancelBtnWrap,
} from '../index.styles';
import Btn from '@/components/common/Btn';
import { CancelModalProps } from '../index.types';
import instance from '@/apis/instance';
import { useRouter } from 'next/navigation';
import { CancelConfirmModal } from '../CancelConfirmModal';
export const CancelModal = ({
isOpen,
isClose,
refundPercent,
id,
}: CancelModalProps) => {
const [confirmModalOpen, setConfirmModalOpen] = useState<boolean>(false);
const router = useRouter();
const handleConfirmModalOpen = () => {
setConfirmModalOpen(true);
};
const handleConfirmModalClose = () => {
setConfirmModalOpen(false);
};
const getAccessTokenFromLocalStorage = () =>
localStorage.getItem('accessToken');
const CancelRequest = () => {
const accessToken = getAccessTokenFromLocalStorage();
instance
.post(
`/api/v1/payment/refund/${id}/user`,
{},
{
// 빈 객체를 요청 데이터로 전달
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
)
.then((response) => {
console.log('취소 성공', response);
router.push('/mypages');
})
.catch((error) => {
console.log(error, '실패하였습니다');
});
};
useEffect(() => {
if (isOpen) document.body.style.overflow = 'hidden';
else document.body.removeAttribute('style');
}, [isOpen]);
const handleConfirm = () => {
CancelRequest();
handleConfirmModalOpen();
isClose();
};
return (
<Overlay $isOpen={isOpen}>
<Wrapper>
<Title>예약 취소하기</Title>
<CancelMessageBox>
<CancelMessageText>
지금 예약을 취소하시면 {refundPercent}% 환불 가능합니다. <br></br>
예약 취소하시겠습니까?
</CancelMessageText>
<CancelBtnWrap>
<Btn
text="취소"
state="white"
disabled={false}
size="small"
onClick={isClose}
/>
<Btn
text="확인"
state="orange"
disabled={false}
size="small"
onClick={handleConfirm}
/>
{confirmModalOpen && (
<CancelConfirmModal
isOpen={confirmModalOpen}
isClose={handleConfirmModalClose}
/>
)}
</CancelBtnWrap>
</CancelMessageBox>
</Wrapper>
</Overlay>
);
};
확인
버튼을 누르면 handleConfirm
함수가 작동하며
const handleConfirm = () => {
CancelRequest();
handleConfirmModalOpen();
isClose();
};
이렇게 하면, 예약 취소 페이지 구현 완료이다 !
headers: {
Authorization: `Bearer ${accessToken}`,
},
와 같이 get을 사용할 때에도 헤더에 정보를 담다는 것.
//page.tsx
import ReservationCancelUI from '@/components/mypages/reservationcancel';
const ReservationCancelPage = ({
params: { id },
}: {
params: { id: number };
}) => <ReservationCancelUI id={id} />;
export default ReservationCancelPage;
//ReservationCancelUI
ReservationCancelUI 에선 ({ id }: { id: number }) 를 사용하면
const ReservationCancelUI = ({ id }: { id: number }) => {
...
}
params 를 사용하여 변수값을 넘겨준다는 것.
모달창을 따로 구현하였는데
재사용하며 사용할 수 있지 않았을까? 하는 아쉬운이 있다.