➕ 팀 리딩, 프로젝트 기획, 문서 관리, 일정 조율
예약하기 버튼 클릭, '확인' 클릭 시 저장된 호텔 목록 페이지로 이동
호텔 목록에 들어갈 카드를 구현했다.
HotelList 컴포넌트로부터에서 호텔 이름과 주소, 리뷰, 가격, 예약여부에 대한 데이터를 props로 받아온다.
// Card. tsx
import { Hotel } from 'types/types';
import { priceToString } from '../utils/priceToString';
import BookingButton from '../components/bookedList/BookingButton';
import Rating from './Rating';
import { useRecoilValue } from 'recoil';
import { HideCard } from 'store/global';
interface PropsType {
data: Hotel;
isBooked: boolean;
}
const IMAGE_URL =
'https://images.unsplash.com/photo-1535827841776-24afc1e255ac?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1335&q=80';
const BookedHotels: Hotel[] = [];
const Card = ({ data, isBooked }: PropsType) => {
const { hotel_name: hotelName, address, rating, review, price } = data;
return (
<div
key={hotelName}
className="flex justify-between w-full mt-5 mb-5 bg-white rounded-lg h-80 max-w-7xl border-slate-300 drop-shadow-md dark:bg-gray-500"
>
<div className="w-1/3 h-full">
<img
src={IMAGE_URL}
alt="hotel_image"
className="w-full h-full bg-cover rounded-l-lg"
/>
</div>
<section className="flex flex-col w-full ml-2 mr-6 mt-9 h-1/2">
... (생략)
<BookButton newHotel={data} isBooked={isBooked} />
</div>
</section>
</div>
);
};
export default Card;
예약 버튼 컴포넌트에서 예약 기능과(로컬스토리지에 저장) 예약 확인 모달을 띄우는 기능에 해당하는 로직을 구현했다.
handleClickBook
isExisting
에서 저장된 호텔인 지 확인한다. changeModal
함수에 true값(BOOK_HOTEL
)을 보낸다. BookedHotels
배열에 할당된 호텔 데이터를 저장한다.handleClickCancel
로컬스토리지에서 특정 데이터 삭제 로직
targetHotel
)을 매개변수로 받는다.targetHotel
을 제외한 나머지 호텔 데이터들이 할당된 newBookedHotels
배열을 만든다.newBookedHotels
배열을 저장한다.changeModal
함수에 false값 (CANCEL_HOTEL
)을 보낸다.changeModal
isBooked
의 값이 true일 경우 예약 버튼과 예약 페이지로 이동할 것인 지 확인하는 내용의 모달이 뜬다.isBooked
의 값이 false일 경우 예약취소 버튼과 예약 취소를 확인하는 내용의 모달이 뜬다.toggle
매서드를 호출합니다. 호출 시 useConfirm.tsx
의 isShown
의 상태값이 true로 바뀌고 모달창이 나타난다.다른 팀원분께서 모달을 위해 useConfirm 커스텀 훅과 Confirm (모달)에 해당하는 기능을 만들어두셨고 해당 코드를 사용하려고 하다보니 로직이 조금 복잡해졌다.
Confirm.tsx
컴포넌트는 아래 사진의 버튼 부분(빨간 박스)에 해당하는 기능으로 사용된다.
// BookButton.tsx
import React from 'react';
import { Hotel } from 'types/types';
import { useConfirm } from '../../hooks/useConfirm';
import ConfirmContent from '../result/ConfirmContent';
import { Confirm } from '../../common/Confirm';
import { useSetRecoilState } from 'recoil';
import { IsBookedButton } from 'store/global';
interface PropsType {
newHotel: Hotel;
isBooked: boolean;
}
const BookedHotels: Hotel[] = [];
const BOOK_HOTEL = true;
const CANCEL_HOTEL = false;
const BookButton = ({ newHotel, isBooked }: PropsType) => {
const setIsBooked = useSetRecoilState(IsBookedButton);
const { isShown, toggle } = useConfirm();
const modalContent = <ConfirmContent {...newHotel} />;
const changeModal = (isBooked: boolean) => {
if (isBooked) {
setIsBooked(BOOK_HOTEL);
toggle();
}
if (!isBooked) {
setIsBooked(CANCEL_HOTEL);
toggle();
}
};
const handleClickBook = (newHotel: Hotel) => {
const isExisting = BookedHotels.some(
(e) => e.hotel_name === newHotel.hotel_name
);
if (isExisting) {
alert('이미 저장된 호텔입니다.');
return;
}
if (!isExisting) {
BookedHotels.push(newHotel);
changeModal(BOOK_HOTEL);
}
localStorage.setItem('hotels', JSON.stringify(BookedHotels));
};
const handleClickCancel = React.useCallback((targetHotel: Hotel) => {
const localHotelData = JSON.parse(localStorage.getItem('hotels')!) ?? [];
const newBookedHotels = localHotelData.filter(
(hotel: Hotel) => hotel.hotel_name !== targetHotel.hotel_name
);
localStorage.setItem('hotels', JSON.stringify(newBookedHotels));
changeModal(CANCEL_HOTEL);
}, []);
return (
<>
<div>
{isBooked ? (
<button
className="self-end w-24 h-10 mt-2 text-base font-bold text-center text-white rounded bg-main hover:shadow-md"
onClick={() => handleClickCancel(newHotel)}
>
예약 취소
</button>
) : (
<button
className="self-end w-24 h-10 mt-2 text-base font-bold text-center text-white rounded bg-main hover:shadow-md"
onClick={() => handleClickBook(newHotel)}
>
예약하기
</button>
)}
</div>
<Confirm
isShown={isShown}
hide={toggle}
modalContent={modalContent}
headerText="예약 완료!"
/>
</>
);
};
export default BookButton;
예약 목록 확인 페이지 (로컬 스토리지에 저장된 목록 확인)
localHotelData
에 할당timer
) 주었다.// BookedList.tsx
import React from 'react';
import { Hotel } from 'types/types';
import BookedPc from './BookedPC';
import BookedMobile from './BookedMobile';
const BookedList = () => {
const [isLoading, setIsLoading] = React.useState(true);
const [localHotelData, setLocalHotelData] = React.useState<Hotel[]>([]);
React.useEffect(() => {
const localHotelData = JSON.parse(localStorage.getItem('hotels')!) ?? [];
const timer = setTimeout(() => {
setLocalHotelData(localHotelData as Hotel[]);
setIsLoading(false);
}, 500);
return () => {
clearTimeout(timer);
};
}, []);
return (
<>
<div className="hidden lg:block md:hidden">
<BookedPc hotel={localHotelData} isLoading={isLoading} />
</div>
<div className="md:block bolck lg:hidden">
<BookedMobile hotel={localHotelData} isLoading={isLoading} />
</div>
</>
);
};
export default BookedList;
제가 메인으로 맡아 구현하지는 않았지만 함께 사용했던 컴포넌트입니다.
: Confirm.tsx, ConfirmContent.tsx, useConfirm.tsx
그려보자면 이런 식 (간략한 그림)
마간 기한이 임박해 새로 모달을 구현할 시간이 부족했고 기존에 다른 팀원분계서 구현해둔 모달 기능을 사용해야했다. BookButton컴포넌트에서 예약 확인, 취소 상태에 대한 전역 상태를 추적하고 이 전역 상태값을 ConfirmContent.tsx
와 Confirm.tsx
에 반영한다. true
일 경우 호텔 예약 확인 모달, false
일 경우 예약 취소 확인 모달로 설정한다. 그리고 useConfirm.tsx
에서 모달 노출 상태값(isShown
)을 관리하며 toggle 매서드 호출을 통해 isShwon
값을 변경한다. isShown
이 tru
e일 경우 모달을 띄우고, false
일 경우 모달을 숨긴다.
처음 접해보는 모달 설정 방식라 조금 당황하였으나 마감기한이 길지 않아 동작 방식을 빠르게 파악하는 것에 집중했다. 예약 확인 상태, 예약 취소 상태 두 값을 각각 다른 모달에 전달해야한다고만 생각했었으나 두 상태 값을 각각 true와 false 로, 그리고 전역 상태값으로 설정했더니 모달을 설정하기 수월했다. 모달을 만들기 위한 다른 코드들이 필요 없고 전역 상태값과 커스텀 Hook, 그리고 모달 내용과 버튼에 해당하는 컴포넌트만 있으면 모달이 완성되니 의외로 나름 편한 방법이라는 생각도 들었다.