- 피그마를 리액트로 옮기는 과정에서 생각보다 시간이 많이 걸린다는 것을 느꼈다.
- 그래도 하나하나 페이지를 완성해나가면서 프론트에 대해서 익힐 수 있어서 좋다고 생각하며 개발 중이다.
리액트 화면 구현
- 리액트를 시작하기에 앞서 리액트와 JavaScript의 활용법과 장점들을 공부하고 가면 좋겠어서 아래 링크에 새로 리액트와 JavaScript에 대한 정보를 적어 놓았다.
- https://hoozy.tistory.com/entry/React-%EB%B0%8F-JS-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%B0%8F-JS-%EC%A0%95%EB%B3%B4%EC%99%80-%ED%99%9C%EC%9A%A9%EB%B2%95
홈페이지
- 쇼핑몰 프로젝트의 메인 페이지로, 한 페이지당 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;
'Spring Boot (프로젝트)' 카테고리의 다른 글
[쇼핑몰 프로젝트] 4. Spring Boot, 엔티티 기본 설정 (0) | 2023.11.06 |
---|---|
[쇼핑몰 프로젝트] 3. ERD 기획 (2) | 2023.11.06 |
[쇼핑몰 프로젝트] 1. 페이지 기획(feat. 피그마) (0) | 2023.10.28 |
[쇼핑몰 프로젝트]PG 시스템을 공부하기 위한 쇼핑몰 토이 프로젝트 기획 (0) | 2023.10.28 |
[스프링 부트] 프로젝트 6 S3 백엔드 구현 (0) | 2023.04.17 |
댓글