Frond-end/React

[React] 좋아요 적용

Nellie Kim 2024. 2. 27. 10:39
728x90

퍼블리싱으로 제공받은 CSS를 사용하여 리액트에서 하트를 누르면 단골상점으로 등록하는 작업을 진행했다.

 

CSS 

하트와 관련된 스타일이 8개가 있었다.

/* 하트 지정 시 필요한 스타일 */
/* 1. 하트 옆 체크박스 숨기는 */
.like-check-box input[type=checkbox] {
  position: absolute;
  width: 0px;
  height: 0px;
  overflow: hidden;
}

/* 2. 하트 박스 전체가 아예 노출이 안되게 하는 스타일 */ 
.like-check-box input[type=checkbox] + label {
  position: relative;
  display: inline-block;
  border: 0.0625rem solid #e0e0e0;
  border-radius: 0.25rem;
  padding: 0.375rem 0.6875rem;
  padding-left: 1.1875rem;
  font-size: 0.6875rem;
  letter-spacing: -0.05em;
  color: #999;
  background-color: #ffffff;
}

/* 3.  [하트 모양] - 클릭 전 회색*/
.like-check-box input[type=checkbox] + label:after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  background-image: url(../img/ico_heart_gray.png);
  background-position: cetner;
  background-repeat: no-repeat;
  background-size: cover;
  position: absolute;
  left: 0.5625rem;
  top: 0.4375rem;
}

/* 4.  [하트 테두리] - 클릭 후 빨간색 */
.like-check-box input[type=checkbox]:checked + label {
  border: 0.0625rem solid #ff7f00;
  background-color: #fff6ed;
  color: #ff7f00;
}


/* 5. [하트 모양] - 클릭 후 빨간색 */
.like-check-box input[type=checkbox]:checked + label:after {
  background-image: url(../img/ico_heart.png);
}

/* 6. [하트 테두리] 텍스트 없을 때 사용 - 찌그러진 하트 테두리기 나옴 (이유는 모르겠다)*/
.like-check-box.none-text input[type=checkbox] + label {
  width: 1rem;
  height: 1rem;
  padding: 0;
}

/* 7. [하트 테두리]  텍스트 없을 때 사용 - 하트가 밖으로 빠져나감 (이유는 모르겠다)*/
.like-check-box.none-text input[type=checkbox] + label:after {
  top: 50%;
  left: 50%;
  -webkit-transform: translate(-50%, -50%);
          transform: translate(-50%, -50%);
}

/* 8. 하트 관련 모든 속성을 빨간 색으로 변경 (현재는 없어도 동작함)*/
.like-check-box .total {
  color: #e82f01 !important;
}

 

 

최종 TSX 페이지

/* eslint-disable @next/next/no-img-element */

import React, { useState, useRef, useEffect } from "react";
import { useRouter } from "next/router";
import Layout from "@comps/Layout";
import Main from "@comps/Main";
import { putRegularMap } from "@apis/member";
import Toast from "@comps/toast";
import {
  Label,
  Input,
  Button,
  WindmillContext,
} from "@roketid/windmill-react-ui";



function RecordPage() {
  /**
|--------------------------------------------------
| 😃 1. 일반 변수
|--------------------------------------------------
*/
  const router = useRouter();
  const { idx } = router.query;
  const [data, setData] = useState<any>();
  const [isRegular, setIsRegular] = useState<boolean>(false); // 단골 유무
  const [isToast, setIsToast] = useState<boolean>(false); // Toast 메시지 팝업

  /**
|--------------------------------------------------
| 🔑 2. 일반 함수
|--------------------------------------------------
*/

  /** 하트 클릭 시 */
  const handlePutRegularMap = () => {
    setIsRegular(!isRegular);
    setIsToast(true)
    fetchPutRegularMap();
    // setIsToast(false)
  }

  /**
|--------------------------------------------------
| 🎁 3. fetch 함수 
|--------------------------------------------------
*/

  /**3-1. 상단의 상점 정보 조회 */
  async function fetchData(idx: any) {
    const request = {
      idx: Number(idx),
    };

    const response = await getDetailReserve(request);

    if (response.status == 200) {
      setData(response.data);

      // 날짜 쪼개기
      const reserveDate = response.data.reserveDate;
      let [datePart, timePart] = reserveDate.split(' ');
      datePart = datePart.replace(/-/g, '.');
      timePart = timePart.replace(/:00$/, '');
      setDate(datePart)
      setTime(timePart)
    }
  }


  /** 단골 등록 */
  async function fetchPutRegularMap() {
    const request = {
      storeIdx: data.storeIdx
    };
    const response = await putRegularMap(request);

    if (response.status == 200) {
      fetchData(idx)
      console.log('좋아요 성공!!!')
    } else {
      alert('실패... ')
    }
  }


  /**
|--------------------------------------------------
| ⚡ 5. useEffect
|--------------------------------------------------
*/
  /**  목록 조회 GET */
  useEffect(() => {
    fetchData(idx);
  }, [idx]); // idx가 변경될 때마다 fetchData() 호출


  /*
|--------------------------------------------------
| 🎨 화면
|--------------------------------------------------
*/
return (
    <>
      <Layout />
      <Main>
        <body>
          <div className="header">
            <div className="top-bar">
              <div className="prev">
                <a href="" className="prev-btn">
                  <i className="icon icon-prev"></i>
                  예약이력
                </a>
              </div>
            </div>
          </div>
          <div className="section">
            <div className="wrapper">
              <ul className="reservation-list">
                <li>
                  <div className="box">
                    <div className="reservation">
                      <a href="#">
                        <div className="branch-info">
                          <div className="prefix">
                            <img src="/img/sample-img-banner.png" alt="" />
                            <div className="sucess">
                              <i className="icon ico-calendar"></i>
                              예약 완료
                            </div>
                          </div>
                          <div className="suffix">
                            <div className="address">
                              레저로 스크린골프 계양점
                              <div className="sub-address gray">
                                인천광역시 계약구 계산대로 88
                              </div>
                            </div>
                            <div className="tag-check-wrap">
                              {/* 🎯 하트 적용 부분 */}
                              <div className="like-check-box none-text">
                                <input type="checkbox" name="" id="ck" checked={isRegular} onChange={handlePutRegularMap} />
                                <label htmlFor="ck"></label>
                              </div>
                            </div>
                          </div>
                        </div>
                      </a>
                    </div>
                  </div>
                </li>
              </ul>
            </div>
          </div>
          
          {(isRegular && isToast) ? (
                      <Toast message="단골 매장으로 설정했습니다." setToast={setIsToast} />
                    ) : (
                      !isRegular && isToast && (
                        <Toast message="단골 매장을 해제했습니다." setToast={setIsToast} />
                      )
                    )}
        </body>
      </Main>
    </>
  );
}

export default RecordPage;

 

 

핵심 로직은 아래와 같다. 

{/* 🎯 하트 적용 부분 */}
      <div className="like-check-box none-text">
        <input type="checkbox" name="" id="ck" checked={isRegular} onChange={handlePutRegularMap} />
        <label htmlFor="ck"></label>
      </div>

 

완료!