본문 바로가기
Spring Boot (프로젝트)

[쇼핑몰 프로젝트] 2. 리액트 화면 구현

by Hoozy 2023. 10. 29.
  • 피그마를 리액트로 옮기는 과정에서 생각보다 시간이 많이 걸린다는 것을 느꼈다.
  • 그래도 하나하나 페이지를 완성해나가면서 프론트에 대해서 익힐 수 있어서 좋다고 생각하며 개발 중이다.

리액트 화면 구현

홈페이지

  • 쇼핑몰 프로젝트의 메인 페이지로, 한 페이지당 6개의 상품을 제공하고, 각 상품의 이미지를 클릭하면 상세페이지로 이동하게끔 기획했다.
  • 로그인, 회원가입, 마이페이지, 로그아웃을 헤더 컴포넌트로 따로 생성하여 관리했다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import "./HomePage.css";
import { useNavigate } from "react-router-dom";
import Product from "./Product";
import LogoutHeader from "./LogoutHeader";
import Footer from "./Footer";

function HomePage() {
  const [currentPage, setCurrentPage] = useState(1); 
  const [pageInfo, setPageInfo] = useState([]);
  const [products, setProducts] = useState([]);
  const [pages, setPages] = useState([]);

  const getProduct = async () => {
    const res = await axios.get("/product/"+{currentPage}); // axios로 
    setProducts(res.products); // 응답 중에서 상품들 리스트
    setPageInfo(res.pageInfo); // 응답 중 페이지에 대한 정보(현재 페이지 리스트의 시작페이지 마지막페이지, 최종페이지,  

    const pageArr = [];
    // 현재 페이지의 시작페이지부터 마지막 페이지 까지 페이지 번호 넣기
    for (let i = pageInfo.startPage; i <= pageInfo.lastPage; i++) {
      pageArr.push(i);
    }
    setPages(pageArr);

  };

  useEffect =
    (() => {
      getProduct();
    },
    [currentPage]); // currentPage의 상태가 변할 때마다 getProduct() 함수를 실행하여 페이지에 해당하는 상품들 가져오기

  const goToPage = (page) => {
    setCurrentPage(page);
  };

  return (
    <div className="page">
      <LogoutHeader /> // 로그인하면 제공되는 access-Token이 있으면 LoginHeader, 아니면 LogoutHeader
      <main>
        <div className="mainContainer">
          {products.map((product, idx) => ( // 제품을 하나하나 가져오기
            <Product key={product.id} product={product} idx={idx} />
          ))}
        </div>

        <div className="pagination">
          {currentPage !== 1 && ( // 현재 페이지가 첫 페이지가 아니면 처음페이지, 이전페이지 버튼 생성
            <>
              <button className="firstPage" onClick={() => goToPage(1)}>
              </button>
              <button
                className="prevPage"
                onClick={() => goToPage(currentPage - 1)}
              >
              </button>
            </>
          )}

          {pages.map((page) => ( // 현재 페이지에 해당하는 5개이하의 페이지 번호들 배열
            <button
              key={page}
              onClick={() => goToPage(page)}
              className={currentPage === page ? "pageActive" : "pageBtn"}
            >
              {page}
            </button>
          ))}

          {currentPage !== pageInfo.maxPage && ( // 현재 페이지가 마지막 페이지가 아니면 다음페이지, 마지막 페이지 버튼 생성
            <>
              <button
                className="nextPage"
                onClick={() => goToPage(currentPage + 1)}
              ></button>
              <button
                className="lastPage"
                onClick={() => goToPage(pageInfo.maxPage)}
              ></button>
            </>
          )}
        </div>
      </main>
      <Footer />
    </div>
  );
}
export default HomePage;

상세 페이지

  • 각 제품의 상세페이지로, 상품명, 회원의 쿠폰 적용, 이벤트인 금토일에는 30% 세일을 추가로 해서 가격에 적용시킨 페이지이다.
  • Portone 사의 PG API를 활용하여 토스페이먼츠를 통해 테스트 결제를 제공한다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import { useNavigate, useParams } from "react-router-dom";
import LogoutHeader from "./LogoutHeader";
import Footer from "./Footer";

function DetailPage() {
  const { pId } = useParams(); // url/{pid} 처럼 변수 가져오기
  const [isDrop, setDrop] = useState(false);
  const [isEvent, setEvent] = useState(false);
  const [product, setProduct] = useState([]); // 현재 상품의 정보를 담을 객체
  const [firstPrice, setFirstPrice] = useState(100000); // 원가를 설정
  const [price, setPrice] = useState(100000); // 최종 가격을 설정

  const getProduct = async () => {
    const res = await axios.get("/product/"+{pId});

    setProduct(res.data); // 상품의 상태를 변경한다.
    setFirstPrice(product.price); // 원가를 상품의 가격으로 바꾼다
    setPrice(product.price); // 처음에는 할인이 되기 전 가격이기 때문에 원가랑 같다.
  };

  const couponDrop = () => {
    setDrop((isDrop) => !isDrop); // 클릭마다 drop 여부를 바꿔서 쿠폰이 보이거나 안보이게
  };

  const discount = (per) => { // one, two, three로 할인 퍼센트를 구분해서 최종가격에 적용한다.
    couponDrop();
    switch (
      per // 세일마다 가격 다르게
    ) {
      case "one":
        setPrice(Math.ceil(firstPrice * 0.9)); // 원가에 대해 할인을 적용해서 할인이 중복으로 적용되는 것을 방지한다.
        break;
      case "two":
        setPrice(Math.ceil(firstPrice * 0.8));
        break;
      case "three":
        setPrice(Math.ceil(firstPrice * 0.7));
        break;
    }
  };

  useEffect =
    (() => {
      // event일 떄마다 30퍼 세일
      discount("three");
    },
    [isEvent]);

  return (
    <div className="page">
      <LogoutHeader />
      <main>
        <div className="detailBox">
          <div className="detailImgBox"></div>
          <div className="detailInfoBox">
            <div className="detailTitle">
              <h2>상품명상품명상품명상품명상품명상품명상품명상품명상품명</h2>
            </div>
            <div className="detailCoupon">
              <button className="detailCouponBtn" onClick={() => couponDrop()}>
                쿠폰을 선택하세요.
              </button>
              <div className={isDrop ? "dropContent" : "hideContent"}>
                <button onClick={() => discount("one")}>10% 할인 쿠폰</button>
                <button onClick={() => discount("two")}>20% 할인 쿠폰</button>
                <button onClick={() => discount("three")}>30% 할인 쿠폰</button>
              </div>
            </div>
            <div className="detailEvent">
              <h2>
                {isEvent
                  ? "금토일 이벤트 30% 할인"
                  : "할인중이 아닙니다. 금토일을 기다려주세요."}
              </h2>
            </div>
            <div className="detailPrice">
              <h2>{price}원</h2>
            </div>
          </div>
        </div>
        <button className="payBtn">
          결제하기
        </button>
      </main>
      <Footer />
    </div>
  );
}

export default DetailPage;

마이페이지

  • 로그인 한 유저의 마이페이지로, 지금까지의 결제내역을 제공한다.
  • 결제내역은 성공한 건만 제공되며, 결제취소 또한 제공된다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import "./HomePage.css";
import { useNavigate } from "react-router-dom";
import List from "./List";
import LoginHeader from "./LoginHeader";
import Footer from "./Footer";

function MyPage() {
  const [currentPage, setCurrentPage] = useState(1);
  const [pageInfo, setPageInfo] = useState([]);
  const [products, setProducts] = useState([]);
  const [pages, setPages] = useState([]);

  const getProduct = async () => {
    const res = await axios.get("/product/"+{currentPage});
    setProducts(res.products); // 응답 중에서 상품들 리스트
    setPageInfo(res.pageInfo); // 응답 중 페이지에 대한 정보

    const pageArr = [];
    // 현재 페이지의 시작페이지부터 마지막 페이지 까지 페이지 번호 넣기
    for (let i = pageInfo.startPage; i <= pageInfo.lastPage; i++) {
      pageArr.push(i);
    }
    setPages(pageArr);
  };

  useEffect =
    (() => {
      getProduct();
    },
    [currentPage]); // 현재 페이지가 바뀔 때마다 상품 새로 가져오기

  const goToPage = (page) => {
    setCurrentPage(page);
  };

  return (
    <div className="page">
      <LoginHeader />
      <main>
        <div className="mypageTitle">나의 결제내역</div>
        <div className="mainContainer">
          {products.map((product, idx) => ( // 제품을 하나하나 가져오기
            <List key={product.id} product={product} idx={idx} />
          ))}
        </div>

        <div className="pagination">
          {currentPage !== 1 && ( // 현재 페이지가 첫 페이지가 아니면 처음페이지, 이전페이지 버튼 생성
            <>
              <button className="firstPage" onClick={() => goToPage(1)}>
              </button>
              <button
                className="prevPage"
                onClick={() => goToPage(currentPage - 1)}
              >
              </button>
            </>
          )}

          {pages.map((page) => ( // 현재 페이지에 해당하는 5개이하의 페이지 번호들 배열
            <button
              key={page}
              onClick={() => goToPage(page)}
              className={currentPage === page ? "pageActive" : "pageBtn"}
            >
              {page}
            </button>
          ))}

          {currentPage !== pageInfo.maxPage && ( // 현재 페이지가 마지막 페이지가 아니면 다음페이지, 마지막 페이지 버튼 생성
            <>
              <button
                className="nextPage"
                onClick={() => goToPage(currentPage + 1)}
              ></button>
              <button
                className="lastPage"
                onClick={() => goToPage(pageInfo.maxPage)}
              ></button>
            </>
          )}
        </div>
      </main>
      <Footer />
    </div>
  );
}
export default MyPage;

장바구니 페이지

  • 로그인 한 회원이 장바구니에 추가하면 장바구니 내역을 보여주는 페이지이다.
  • 최대 6개의 물품을 넣을 수 있고, 쿠폰, 이벤트, 각 물품의 재고가 있으면 최대 3개까지 구매가 가능하다.
  • 각 물품마다 쿠폰을 사용할 수 있고, 회원은 최대 3개의 쿠폰이 제공된다.

// 장바구니 페이지 

import React, { useState, useEffect } from "react";
import axios from "axios";
import "./HomePage.css";
import { useNavigate } from "react-router-dom";
import Cart from "./Cart";
import LoginHeader from "./LoginHeader";
import Footer from "./Footer";

function MyCart() {
  //   const [products, setProducts] = useState([]);
  const [coupon, setCoupon] = useState(2); // 자식인 cart에서 가져올 현재 로그인 유저가 가지고 있는 쿠폰 수
  const [totalPrice, setTotalPrice] = useState(100000); // 자식마다 결제 금액을 가져와서 합치기
  const [priceArr, setPriceArr] = useState([0,0,0,0,0,0]);

  const changeCoupon = (coupon) => {
    console.log(coupon);
    setCoupon(coupon);
  };

  const setTotal = (idx, price) => { // idx(상품순서)에 따른 총금액 정하기
    priceArr[idx] = price // 순서에 맞게 가격 가져오기
    setPriceArr(priceArr);

    let total = 0;
    for(let i = 0; i < priceArr.length; i++) {
      total += priceArr[i];
    }

    setTotalPrice(total);
  };

  const getCart = async () => {
    const res = await axios.get("/product/cart");
    // setProducts(res.data); // 응답 중에서 상품들 리스트
  };

  return (
    <div className="page">
      <LoginHeader />
      <main>
        <div className="mypageTitle">나의 장바구니(남은 쿠폰 수 : {coupon}개)</div>
        <div className="mainContainer">
          {products.map((product, idx) => (
            <Cart
              key={product.id}
              cart={product}
              idx={idx}
              coupon={coupon}
              setTotal={setTotal}
              changeCoupon={changeCoupon}
            /> // getCoupon, setTotal 메소드 자식도 사용 가능
          ))}
        </div>
        <div className="totalPriceBox">
            <div className="totlePrice">총금액 : {totalPrice}원</div>
            <button type="" className="payBtn">
              결제하기
            </button>
        </div>
      </main>
      <Footer />
    </div>
  );
}
export default MyCart;


// 장바구니 안의 각 상품 Cart

import React, { useState, useEffect } from "react";
import axios from "axios";
import classNames from "classnames";

const Cart = ({ cart, idx, setTotal, coupon, changeCoupon }) => {
  const [isDrop, setDrop] = useState(false); // 쿠폰 drop 여부
  const [isCountDrop, setCountDrop] = useState(false); // 수량 drop 여부
  const [buyCount, setBuyCount] = useState(0); // 구매 개수
  const [firstPrice, setFirstPrice] = useState(100000); // 상품 원가격
  const [price, setPrice] = useState(100000); // 상품 최종 구매가격
  const [count, setCount] = useState(30); // 현재 구매하게 되면 남는 재고
  const [firstCount, setFirstCount] = useState(30); // 상품 최초 재고
  // const [coupon, setCoupon] = useState(2); // 보유 쿠폰 개수
  const [isCouponCount, setIsCouponCount] = useState(0); // 쿠폰 사용 개수

  const [useCoupon, setUseCoupon] = useState(0); // 쿠폰을 사용할 수 있는 수 최대 3개

  const url = "http://shopping.phinf.naver.net/main_1031546/10315467179.jpg";

  let isEvent = false;
  let now = new Date(); // 현재
  let day = now.getDay(); // 현재 요일 일요일부터 0~6
  if (day === 0 || day === 5 || day === 6) {
    // 금토일 이면
    isEvent = true;
    setFirstPrice((firstPrice) => Math.ceil(firstPrice * 0.7)); // 이벤트면 시작부터 30퍼 세일
  } else {
    isEvent = false;
  }

  const couponDrop = () => {
    if (buyCount) {
      if (coupon) {
        // 쿠폰이 있을 때
        if (!isCouponCount) {
          // 쿠폰을 사용하지 않았을 때
          setDrop((isDrop) => !isDrop); // 클릭마다 drop 여부
        }
      }
    }
  };

  const setCouponCount = (count) => {
    let sale = (1 - 0.15 * count.i);
    changeCoupon((coupon) => coupon - count.i); // 고른 개수만큼 빼기
    setIsCouponCount(count); // 사용한 쿠폰 개수
    changeCoupon(coupon - count.i); // 부모 컴포넌트인 나의 장바구니에 이 회원의 남은 쿠폰 개수 보내기
    setPrice((price) => Math.ceil(price * sale)); // 현재 상품 총 금액 변경
    setTotal(idx, Math.ceil(price * sale)); // 회원 총 금액 변경
    setDrop((isDrop) => !isDrop); // 클릭마다 drop 여부
  };

  const changeBuyCount = (buy) => { // 구매 개수 변경
    setBuyCount(buy);
    setPrice(firstPrice * buy);
    setTotal(idx, firstPrice * buy); // 회원 총 금액 변경
    setCountDrop(!isCountDrop);
    setCount(firstCount - buy); // 제품 개수 변경
  };

  useEffect(() => {
    // 구매수량, 쿠폰수 가 바뀔 때 마다 쿠폰 사용할 수 있는 개수 적용하기
    // 사용할 수 있는 쿠폰 개수는 buycount 보다 적고, coupon보다 적어야함
    if (count >= coupon) {
      // 선택한 수량이 사용가능한 쿠폰 개수를 초과할 때 -> 쿠폰 개수만큼 사용가능
      if (coupon <= 3) {
        // 쿠폰 개수가 3개보다 크면 3개로 고정, 적으면 쿠폰 개수로 고정
        setUseCoupon(coupon);
      } else {
        setUseCoupon(3);
      }
    } else {
      // 선택한 수량이 사용가능한 쿠폰보다 적을 때 -> 수량만큼만 사용가능
      setUseCoupon(coupon);
    }
  }, [buyCount, coupon]);

  const arr = [1,2,3];

  const countDrop = () => {
    if(!isCouponCount) { // 쿠폰을 사용했으면 드롭 안됨
      setCountDrop((isCountDrop) => !isCountDrop); // 클릭마다 drop 여부
    }
  };

  const cancelCoupon = () => {
    // 쿠폰 취소
    changeCoupon((coupon) => coupon + isCouponCount.i); // 취소한 쿠폰 개수만큼 되돌리기
    setIsCouponCount(0); // 사용한 쿠폰 개수 0개로 변경
    changeCoupon(coupon + isCouponCount.i); // 부모 컴포넌트인 나의 장바구니에 이 회원의 남은 쿠폰 개수 보내기
    setPrice(firstPrice * buyCount); // 총 금액에서 할인가격 복구
    setTotal(idx, firstPrice * buyCount); // 회원 총 금액에서 할인가격 복구
  };

  // const setButton = () => {
  //   console.log(useCoupon);
  //   const arr = [];
  //   for(let i = 1; i <= useCoupon; i++) {
  //     arr.push(<button className="dropBtn" onClick={() => setCouponCount(i)}>{i}개</button>);
  //   }

  //   return arr;
  // };

  return (
    <div className={classNames("list", "list" + { idx })}>
      <button
        className="mainImgBox"
        style={{ background: `url(${url})`, backgroundSize: "cover" }}
      ></button>
      <div className="mainInfoBox">
        <button className="cartTitle">
          상품명상품명상품명상품명상품명상품명상품명상품명상품명
        </button>
        <div className="cartCount">남은 수량 : {count}개</div>
        <div className="cartCoupon">
          <button
            className="cartCouponBtn"
            onClick={isCouponCount ? () => cancelCoupon() : () => couponDrop()}
          >
            {buyCount
              ? isCouponCount
                ? "쿠폰 사용 취소하기"
                : coupon
                ? `15% 할인 쿠폰 남은 개수 : ${coupon}개`
                : "남은 쿠폰이 없습니다."
              : "구매 수량부터 선택해주세요."}
          </button>
          <div
            className={isDrop && buyCount ? "dropCouponContent" : "hideContent"}
          >
            {
              arr.map((i, idx) => {
                if(i <= useCoupon) { // 현재 사용할 수 있는 쿠폰 수 만큼만 쿠폰 나타내기
                  return (<button key={i} className="dropBtn" onClick={() => setCouponCount({i})}>{i}개</button>);
                }
              })
            }

          </div>
        </div>
        <div className="cartEvent">
          {isEvent ? "금토일 이벤트 30% 할인" : "이벤트 요일이 아닙니다."}
        </div>
        <div className="cartPayBox">
          <div className="cartPayPrice">{price}원</div>
          <button className="cartCountBtn" onClick={() => countDrop()}>
            {buyCount ? <div>{buyCount}개</div> : <div>수량 선택</div>}
          </button>
          <div className={isCountDrop ? "dropCountContent" : "hideContent"}>
            <button className="dropBtn" onClick={() => changeBuyCount(1)}>
              1개
            </button>
            <button className="dropBtn" onClick={() => changeBuyCount(2)}>
              2개
            </button>
            <button className="dropBtn" onClick={() => changeBuyCount(3)}>
              3개
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Cart;

로그인 페이지

  • 이메일과 비밀번호를 입력하여 로그인하면 서버에서 토큰을 제공받는 페이지이다.
  • 이메일의 형식이 틀리거나, 비밀번호가 영어,숫자를 포함한 8~16글자가 아니면 유효성에 어긋나 로그인이 불가하다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";

function LoginPage() {
  const navigate = useNavigate();

  const [email, setEmail] = useState("");
  const [pwd, setPwd] = useState("");

  // 유효성이 맞지 않으면 나타나는 에러 메시지
  const [wrongEmail, setWrongEmail] = useState("");
  const [wrongPwd, setWrongPwd] = useState("");

  // 유효성 체크
  const [isEmail, setIsEmail] = useState(false);
  const [isPwd, setIsPwd] = useState(false);

  const emailRegEx = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i // 이메일을 뜻하는 정규식
  const passwordRegEx = /^(?=.*[a-zA-Z])(?=.*[0-9]).{8,16}$/ // 8~16글자이면서 영어, 숫자 는 최소 한글자가 들어가야 한다는 정규식

  const isValid = isEmail && isPwd; // 이메일과 비밀번호의 유효성이 만족하면 로그인을 할 수 있다는 것을 체크하는 변수

  const emailCheck = (email) => { // 이메일을 입력할 때마다 이메일의 유효성을 검사해서 맞으면 isEmail을 true로 만들어준다. 
    if (emailRegEx.test(email)) {
      setEmail(email);
      setIsEmail(true);
      setWrongEmail("");
    } else {
      setEmail("");
      setIsEmail(false);
      setWrongEmail("이메일 형식에 맞지 않습니다.");
    }
  };

  const passwordCheck = (pwd) => { // 비밀번호를 입력할 때마다 비밀번호의 유효성을 검사해서 맞으면 isPwd을 true로 만들어준다.
    if (passwordRegEx.test(pwd)) {
      setPwd(pwd);
      setIsPwd(true);
      setWrongPwd("");
    } else {
      setPwd("");
      setIsPwd(false);
      setWrongPwd("비밀번호는 8글자 이상, 16글자 이하 입니다.");
    }
  };

  const submit = async () => { // 로그인을 시도하여 성공하면 토큰을 받는 함수
    if (isValid) {
      const res = await axios
        .post("/login", {
          email: email,
          pwd: pwd,
        })
        .then((res) => {
          window.alert("로그인에 성공하였습니다.");
          navigate("/");
        })
        .error((error) => {
          window.alert("로그인에 실패하였습니다.");
        });
    } else {
      return false;
    }
  };

  return (
    <div className="page">
      <div className="miniPage">
        <div className="miniHeader">
          <button type="" className="miniHeaderVector"></button>
          <div className="miniHeaderFont">로그인</div>
        </div>
        <div className="miniBox">
          <input
            className="miniInputBox"
            placeholder="email@email.com"
            type="email"
            onChange={(e) => emailCheck(e.target.value)}
          />
          <div className="miniBoxLine"></div>
          <div className="wrongEmail">{wrongEmail}</div> // 유효성에 어긋나면 나타나는 에러메시지.

          <input
            className="miniInputBox"
            placeholder="비밀번호"
            type="password"
            onChange={(e) => passwordCheck(e.target.value)}
          />
          <div className="miniBoxLine"></div>
          <div className="wrongPwd">{wrongPwd}</div> // 유효성에 어긋나면 나타나는 에러메시지.

          <button
            className={isValid ? "miniButton" : "miniFailButton"} // 모든 유효성이 통과하면 색이 바뀌게끔 하는 코드
            onClick={isValid ? () => submit() : undefined} // 모든 유효성이 통과해야 함수가 실행되도록 하는 코드
          >
            로그인
          </button>
        </div>
      </div>
    </div>
  );
}

export default LoginPage;

회원가입 페이지

  • 이메일, 비밀번호, 비밀번호 확인으로 회원가입을 시도하는 페이지이다.
  • 이메일이 존재하거나, 이메일의 형식이 틀리거나, 비밀번호가 영어,숫자를 포함한 8~16글자가 아니거나, 비밀번호와 확인이 다르면 유효성에 어긋나 회원가입이 불가하다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";

function RegisterPage() {
  const navigate = useNavigate();

  const [email, setEmail] = useState("");
  const [pwd, setPwd] = useState("");
  const [pwdConfirm, setPwdConfirm] = useState("");

  // 유효성이 맞지 않으면 나타나는 에러 메시지
  const [wrongEmail, setWrongEmail] = useState("");
  const [wrongPwd, setWrongPwd] = useState("");
  const [wrongPwdConfirm, setWrongPwdConfirm] = useState("");

  // 유효성 체크
  const [isEmail, setIsEmail] = useState(false);
  const [isPwd, setIsPwd] = useState(false);
  const [isPwdConfirm, setIsPwdConfirm] = useState(false);
  const isValid = isEmail && isPwd && isPwdConfirm;

  const emailRegEx = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i // 이메일을 뜻하는 정규식
  const passwordRegEx = /^(?=.*[a-zA-Z])(?=.*[0-9]).{8,16}$/ // 8~16글자이면서 영어, 숫자 는 최소 한글자가 들어가야 한다는 정규식

  const emailDuplicate = async (email) => {
    // 이메일 중복체크
    const res = await axios.post("/register/duplicate/" + { email });
    const check = res.data;

    return check === 1 ? true : false; // 중복아니면 1이어서 트루
  };

  const emailCheck = (email) => { // 이메일를 입력할 때마다 이메일의 유효성을 검사해서 맞으면 isPwd을 true로 만들어준다.
    if (emailRegEx.test(email)) {
      if (emailDuplicate(email)) { // 중복이 아닐 때
        setEmail(email);
        setIsEmail(true);
        setWrongEmail("");
      } else {
        setEmail("");
        setIsEmail(false);
        setWrongEmail("이미 존재하는 이메일입니다.");
      }
    } else {
      setEmail("");
      setIsEmail(false);
      setWrongEmail("이메일 형식에 맞지 않습니다.");
    }
  };

  const passwordCheck = (pwd) => { // 비밀번호를 입력할 때마다 비밀번호의 유효성을 검사해서 맞으면 isPwd을 true로 만들어준다.
    if (passwordRegEx.test(pwd)) {
      // 비밀번호 형식이 맞을 때
      setPwd(pwd);
      setIsPwd(true);
      setWrongPwd("");
    } else {
      setPwd("");
      setIsPwd(false);
      setWrongPwd("영어 대소문자, 숫자를 조합하여 8~16자리를 입력해주세요.");
    }
  };

  const passwordConfirmCheck = (pwdConfirm) => { // 비밀번호 확인를 입력할 때마다 비밀번호 확인의 유효성을 검사해서 맞으면 isPwd을 true로 만들어준다.
    if (pwdConfirm === pwd) {
      // 일치할 때
      setPwdConfirm(pwdConfirm);
      setIsPwdConfirm(true);
      setWrongPwdConfirm("");
    } else {
      setPwdConfirm("");
      setIsPwdConfirm(false);
      setWrongPwdConfirm("비밀번호가 일치하지 않습니다.");
    }
  };

  const submit = async () => {
    if (isValid) {
      // 유효성 검사를 다 통과했을 때
      console.log(isValid);
      const res = await axios.post(
        "/register",
        {
          email: email, // 서버로 보낼 데이터
          pwd: pwd,
        }
          .then((res) => {
            window("회원가입에 성공하였습니다.");

            // 홈페이지로 이동
            navigate("/");
          })
          .catch((error) => {
            window.alert("회원가입에 실패하였습니다.");
          })
      );
    } else {
      return false;
    }
  };

  useEffect = (() => {}, [isEmail, isPwd, isPwdConfirm]);

  return (
    <div className="page">
      <div className="miniPage">
        <div className="miniHeader">
          <button type="" className="miniHeaderVector"></button>
          <div className="miniHeaderFont">가입하기</div>
        </div>
        <div>
          <div className="miniBox">
            <input
              className="miniInputBox"
              placeholder="email@email.com"
              type="email"
              onChange={(e) => emailCheck(e.target.value)}
            />
            <div className="miniBoxLine"></div>
            <div className="wrongEmail">{wrongEmail}</div>

            <input
              className="miniInputBox"
              placeholder="비밀번호"
              type="password"
              onChange={(e) => passwordCheck(e.target.value)}
            />
            <div className="miniBoxLine"></div>
            <div className="wrongPwd">{wrongPwd}</div>

            <input
              className="miniInputBox"
              placeholder="비밀번호 확인"
              type="password"
              onChange={(e) => passwordConfirmCheck(e.target.value)}
            />
            <div className="miniBoxLine"></div>
            <div className="wrongPwdConfirm">{wrongPwdConfirm}</div>

            <button
              className={isValid ? "miniButton" : "miniFailButton"} // 유효성을 다 통과하면 버튼을 활성화해주는 코드
              onClick={isValid ? () => submit() : undefined} // 유효성을 다 통과하면 버튼 클릭 시 함수를 연결
            >
              회원가입
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

export default RegisterPage;

결제 성공 페이지

  • 결제를 성공해서 최종 결제 내역이 생겼을 때 구매한 제품명, 결제한 금액, 결제 수단, 결제 날짜를 제공하는 페이지이다.
  • 마이페이지로 이동을 제공해 결제내역을 볼 수 있다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";

function SuccessPage() {
  const navigate = useNavigate();

  const myPage = () => { // 마이페이지로 이동시키는 함수
    navigate("/mypage");
  };

  return (
    <div className="page">
      <div className="miniPage">
        <div className="miniHeader">
          <div className="payHeaderFont">결제 성공</div>
        </div>
        <div className="payBox">
          <div className="payProductBox">
            <div>구매 제품명:</div>
            <div className="payProduct">
              상품명상품명상품명상품명상품명상품명상품명상품명상품명
            </div>
          </div>
          <div className="payPriceBox">
            <div>결제 금액:</div> <div className="payPrice">100000원</div>
          </div>
          <div className="payMethodBox">
            <div>결제 수단:</div> <div className="payMethod">카드</div>
          </div>
          <div className="payDateBox">
            <div>결제 날짜:</div> <div className="payDate">2022.11.02</div>
          </div>
          <button className="miniButton" onClick={() => myPage()}>
            마이페이지
          </button>
        </div>
      </div>
    </div>
  );
}

export default SuccessPage;

결제 실패 페이지

  • 어떠한 이유로 결제에 실패했을 때 나타나는 페이지
  • 결제 실패의 이유를 제공한다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import { useNavigate, useParams } from "react-router-dom";

function FailPage() {
  const { pId } = useParams();
  const navigate = useNavigate();

  const detailPage = () => { // 결제를 실패한 상품의 상세페이지로 가서 재결제를 할 수 있게 상세페이지로 이동시키는 함수
    navigate("/detail" + pId);
  };

  const homePage = () => { // 홈페이지로 이동시키는 함수
    navigate("/");
  };

  return (
    <div className="page">
      <div className="miniPage">
        <div className="miniHeader">
          <div className="payHeaderFailFont">결제 실패</div>
        </div>
        <div className="payBox">
          <div className="payProductBox">
            <div>실패 이유:</div>
            <div className="payProduct">
            잔액 부족
            </div>
          </div>
          <button className="miniButton" onClick={() => detailPage()}>
            제품 상세페이지
          </button>
          <button className="paySecondBtn" onClick={() => homePage()}>
            홈페이지
          </button>
        </div>
      </div>
    </div>
  );
}

export default FailPage;

결제 취소 성공 페이지

  • 결제취소를 클릭하고 결제 취소에 성공하였을 때 취소한 제품명, 취소한 금액, 결제 수단, 취소 날짜를 제공하는 페이지이다.
  • 결제 취소에 실패하면 결제 취소 페이지에 실패한 이유를 제공해준다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";

function CancelPage() {
  const navigate = useNavigate();

  const myPage = () => { // 마이페이지로 이동시키는 함수
    navigate("/mypage");
  };

  return (
    <div className="page">
      <div className="miniPage">
        <div className="miniHeader">
          <div className="payHeaderFont">결제 취소 성공</div>
        </div>
        <div className="payBox">
          <div className="payProductBox">
            <div>취소 제품명:</div>{" "}
            <div className="payProduct">
              상품명상품명상품명상품명상품명상품명상품명상품명상품명
            </div>
          </div>
          <div className="payPriceBox">
            <div>취소 금액:</div> <div className="payPrice">100000원</div>
          </div>
          <div className="payMethodBox">
            <div>결제 수단:</div> <div className="payMethod">카드</div>
          </div>
          <div className="payDateBox">
            <div>취소 날짜:</div> <div className="payDate">2022.11.02</div>
          </div>
          <button className="miniButton" onClick={() => myPage()}>
            마이페이지
          </button>
        </div>
      </div>
    </div>
  );
}

export default CancelPage;

댓글