팝업창

Minhyuk Song·2024년 12월 13일
0

쇼핑몰 기능 탐방

목록 보기
6/6

요구사항

  1. 페이지 진입 시 팝업창이 떠야 한다.
    ㄴ 다른 DOM 요소에 영향을 안 주기 위해 리액트 포탈로 모달 형식으로 뜨게 하자.
  2. 팝업창 닫기 버튼을 추가해야 한다.
    ㄴ 포탈 닫기
  3. 팝업창 내 이미지 노출한다. (이미지는 수시로 변경될 것으로 추측)
    ㄴ 팝업창 컴포넌트에 이미지를 props로 받기
  4. 오늘 하루 그만보기 기능을 추가한다.
    ㄴ 만료기한이 있는 쿠키를 사용하자 (1일 기한)

구현결과

1️⃣ 리액트 포탈로 모달 만들기

// index.html

<body>
  <div id="root"></div>
  {/* ✅ 모달이 띄어질 수 있는 태그 */}
  <div id="modal-root"></div>
  <script
    type="module"
    src="/src/main.tsx"></script>
</body>
// src/components/Modal/index.tsx

import { ReactNode } from 'react';
import ReactDOM from 'react-dom';

interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  onUnSeeToday: () => void;
  children: ReactNode;
}

const Modal = (props: ModalProps) => {
  const { isOpen, onClose, onUnSeeToday, children } = props;
  if (!isOpen) return null;

  return ReactDOM.createPortal(
    <div
      onClick={onClose}
      className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
      <div
        onClick={e => e.stopPropagation()}
        className="relative h-[470px] w-[330px] overflow-hidden rounded-lg bg-white shadow-lg">
        {/* ✅ 모달에 들어가는 이미지 */}
        {children}
        {/* ✅ 버튼 그룹 */}
        <div className="absolute bottom-0 flex w-full items-center justify-between bg-gray-50 px-4 py-4">
          <button 
            onClick={onUnSeeToday}
            className="text-[14px] text-gray-500">
            하루동안 보지 않기
          </button>
          <button
            onClick={onClose}
            className="text-[14px] text-gray-500">
            닫기
          </button>
        </div>
      </div>
    </div>,
    document.getElementById('modal-root')!
  );
};

export default Modal;
// 모달을 사용하는 곳

import Modal from '@/components/modal';
import { useEffect, useState } from 'react';

const MainPage = () => {
  const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

  const handleModalClose = () => {
    setIsModalOpen(false);
  };
 
  // ✅ 메인 페이지가 마운트되자마자 모달이 띄워질 수 있게 useEffect 사용
  useEffect(() => {
    setIsModalOpen(true);
  }, []);

  return (
    <>
      <div>메인 페이지</div>
      <Modal
        isOpen={isModalOpen}
        onClose={handleModalClose}>
        <img
          src="https://i.pinimg.com/736x/18/1b/38/181b389a5419c0738bc9b233535f7ae9.jpg"
          alt="팝업창 이미지"
          className="h-full w-full object-contain"
        />
      </Modal>
    </>
  );
};

export default MainPage;

2️⃣ 쿠키로 하루동안 보지 않기 기능 만들기

import Modal from '@/components/modal';
import { useEffect, useState } from 'react';
import { useCookies } from 'react-cookie';

const MainPage = () => {
  const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

  const handleModalClose = () => {
    setIsModalOpen(false);
  };

  // ✅ 하루동안 팝업 보지 않기
  const [cookies, setCookie] = useCookies(['POPUP_TODAY']);
  const handleUnSeeToday = () => {
    setCookie('POPUP_TODAY', 'true', {
      maxAge: 60 * 60 * 24 // 1일
    });
    setIsModalOpen(false);
  };

  useEffect(() => {
    // ✅ 팝업 보지 않기에 대한 쿠키가 있으면 팝업을 띄우지 않음
    if (cookies.POPUP_TODAY) return;

    setIsModalOpen(true);
  }, []);

  return (
    <>
      <div>메인 페이지</div>
      <Modal
        isOpen={isModalOpen}
        onClose={handleModalClose}
        onUnSeeToday={handleUnSeeToday}>
        <img
          src="https://i.pinimg.com/736x/18/1b/38/181b389a5419c0738bc9b233535f7ae9.jpg"
          alt="팝업창 이미지"
          className="h-full w-full object-contain"
        />
      </Modal>
    </>
  );
};

export default MainPage;
import { ReactNode } from 'react';
import ReactDOM from 'react-dom';

interface PopupModalProps {
  isOpen: boolean;
  onClose: () => void;
  onUnSeeToday?: () => void;
  children: ReactNode;
}

const PopupModal = (props: ModalProps) => {
  const { isOpen, onClose, onUnSeeToday, children } = props;
  if (!isOpen) return null;

  return ReactDOM.createPortal(
    <div
      onClick={onClose}
      className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
      <div
        onClick={e => e.stopPropagation()}
        className="relative h-[470px] w-[330px] overflow-hidden rounded-lg bg-white shadow-lg">
        {/* 모달에 들어가는 이미지 */}
        {children}
        {/* 버튼 그룹 */}
        <div className="absolute bottom-0 flex w-full items-center justify-between bg-gray-50 px-4 py-4">
          {/* ✅ 사용한 부분 */}
          <button
            onClick={onUnSeeToday}
            className="text-[14px] text-gray-500">
            하루동안 보지 않기
          </button>
          <button
            onClick={onClose}
            className="text-[14px]![](https://velog.velcdn.com/images/be_matthewsong/post/30b4d734-6f51-45bc-8ca4-32e180ca1eef/image.gif)
 text-gray-500">
            닫기
          </button>
        </div>
      </div>
    </div>,
    document.getElementById('modal-root')!
  );
};

export default PopupModal;

배운 점

  • 모달은 많이 다뤄봐서 단순한 UI 정도 수준까지 느껴지고, 하루동안 팝업 보지 않기 등과 같은 쿠키를 이용한 컴포넌트는 처음 구현해보는데 조건에 따라 처리만 해준다면 그렇게 어려운 로직은 아닌 것 같다고 느꼈다.
  • 이번에는 가독성이 그렇게 좋지 않은 걸 봐서는 추상화가 덜 되었던 것 같다.
    • 지금 보니 하루동안 보지 않기 등을 추상화를 하면 가독성과 책임 분리가 더 잘 될 것 같다.
profile
어제보다 더 나은 오늘을 만들 수 있게

0개의 댓글

관련 채용 정보