Skip to content

Commit

Permalink
Merge pull request #38 from New-Syatte/essentials-redesign-#37
Browse files Browse the repository at this point in the history
Essentials redesign #37
ruddnjs3769 authored May 10, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents 20f1148 + 4603076 commit 6662397
Showing 28 changed files with 793 additions and 492 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "react-shop-app",
"name": "syatt",
"version": "0.1.0",
"private": true,
"scripts": {
1 change: 1 addition & 0 deletions public/checkmark_io.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
261 changes: 162 additions & 99 deletions src/app/(cart)/cart/CartClient.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
"use client";

import { useRouter } from "next/navigation";
import Image from "next/image";
import { useDispatch, useSelector } from "react-redux";
import {
ADD_TO_CART,
CALCULATE_SUBTOTAL,
CALCULATE_TOTAL_QUANTITY,
CLEAR_CART,
DECREASE_CART,
REMOVE_FROM_CART,
REMOVE_CHECKED_ITEMS_FROM_CART,
CALCULATE_CHECKED_ITEMS_QUANTITY,
CALCULATE_CHECKED_ITEMS_SUBTOTAL,
SELECT_ALL_ITEMS,
UNCHECK_ALL_ITEMS,
ALTERNATE_CHECKED_ITEMS,
selectAllChecked,
SAVE_URL,
selectCartItems,
selectCartTotalAmount,
selectCartTotalQuantity,
} from "@/redux/slice/cartSlice";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import Heading from "@/components/heading/Heading";
import Link from "next/link";
import priceFormat from "@/utils/priceFormat";
import Button from "@/components/button/Button";
import { CartItem } from "@/type/cart";
import { AiFillCaretDown, AiFillCaretUp } from "react-icons/ai";
import deliveryFee from "@/constants/deliveryFee";
import URLS from "@/constants/urls";
import { RxCross2 } from "react-icons/rx";
import { HiMinus, HiPlus } from "react-icons/hi2";
import CartInfoArticle from "./CartInfoArticle";
import CartIcon from "@/assets/cart/cartIcon.svg";

const ICON_CLASS =
"flex text-[24px] transition-all cursor-pointer hover:text-brand hover:scale-200 mx-1 ";
"flex w-4 h-4 text-xs text-white bg-primaryBlue rounded-sm transition-all cursor-pointer hover:text-brand hover:scale-200 mx-1 ";

export default function CartClient() {
const cartItems = useSelector(selectCartItems);
const cartTotalAmount = useSelector(selectCartTotalAmount);
const cartTotalQuantity = useSelector(selectCartTotalQuantity);
const [isScriptLoaded, setIsScriptLoaded] = useState(false);
const isAllChecked = useSelector(selectAllChecked);
const [isDisabled, setIsDisabled] = useState(false);

const dispatch = useDispatch();
const router = useRouter();
@@ -46,105 +49,165 @@ export default function CartClient() {
const removeCart = (cart: CartItem) => {
dispatch(REMOVE_FROM_CART(cart));
};

const clearCart = () => {
dispatch(CLEAR_CART());
};

const url = typeof window !== "undefined" ? window.location.href : "";
const checkout = () => {
router.push(URLS.CHECKOUT_ADDRESS);
};
// url, checkout은 사용되지 않고 잇다
const altCheck = (id: string) => {
dispatch(ALTERNATE_CHECKED_ITEMS({ id }));
};

const altCheckAll = () => {
if (isAllChecked) {
dispatch(UNCHECK_ALL_ITEMS());
}
if (!isAllChecked) {
dispatch(SELECT_ALL_ITEMS());
}
};

useEffect(() => {
dispatch(CALCULATE_SUBTOTAL());
dispatch(CALCULATE_TOTAL_QUANTITY());
dispatch(CALCULATE_CHECKED_ITEMS_SUBTOTAL());
dispatch(CALCULATE_CHECKED_ITEMS_QUANTITY());
dispatch(SAVE_URL(""));

if (
cartItems.length === 0 ||
cartItems.every(item => item.isChecked === false)
) {
setIsDisabled(true);
} else {
setIsDisabled(false);
}
}, [dispatch, cartItems]);

return (
<section className="w-[1280px] mx-auto my-12 min-h-[44.6vh]">
<Heading title={"장바구니"} />
{cartItems.length === 0 ? (
<>
<p className={"text-center font-bold text-[30px]"}>
장바구니가 비었습니다.
</p>
<div
className={"text-center font-bold text-[30px] border-[2px] mt-4"}
>
<Link href={URLS.PRODUCT_STORE}>계속 쇼핑하기</Link>
</div>
</>
) : (
<>
<div className={"border-[1px] mt-4"} />
{cartItems.map(cart => {
const { id, name, imageURL, price, cartQuantity } = cart;
return (
<div key={cart.id}>
<div
className={"flex justify-between items-center px-3"}
key={id}
>
<div>
<Image src={imageURL} alt={name} width={200} height={200} />
</div>
<div>
<p className={"text-[20px] text-nomal"}>{name}</p>
</div>
<div className={"flex justify-between items-center gap-4"}>
<AiFillCaretDown
className={ICON_CLASS}
onClick={() => decreaseCart(cart)}
/>
<p className={"text-[20px]"}>{cartQuantity}</p>
<AiFillCaretUp
className={ICON_CLASS}
onClick={() => increaseCart(cart)}
useEffect(() => {
setIsScriptLoaded(true);
}, []);

if (isScriptLoaded)
return (
<section className="w-[80%] mx-auto my-24 min-h-[80vh] flex flex-col items-start justify-start">
<Heading title={"장바구니"} fontSize="6xl" />
<div className="flex w-full mt-10 gap-20">
<div className="w-2/3">
<div className="w-full border-y border-lightGray py-10">
{cartItems.length !== 0 &&
cartItems.map(cart => {
const { id, name, imageURL, price, cartQuantity } = cart;
return (
<div
className={"flex justify-between items-center px-3 py-3"}
key={id}
>
<input
type="checkbox"
checked={cart.isChecked}
onClick={() => altCheck(id)}
className="appearance-none w-5 h-5 border border-lightGray checked:bg-[url('/checkmark_io.svg')] bg-no-repeat bg-center checked:bg-primaryBlue"
/>
<div className="w-[100px] h-[100px] flex justify-center items-center border border-lightGray">
<Image
src={imageURL}
alt={name}
width={0}
height={0}
sizes="100vw"
style={{ width: "80%", height: "auto" }}
/>
</div>
<div className="w-1/3">
<p className={"text-lg text-nomal w-full"}>{name}</p>
</div>
<div
className={"flex justify-between items-center gap-4"}
>
<HiMinus
className={ICON_CLASS}
onClick={() => decreaseCart(cart)}
/>
<p className={"text-lg"}>{cartQuantity}</p>
<HiPlus
className={ICON_CLASS}
onClick={() => increaseCart(cart)}
/>
</div>
<p className={"text-[22px] font-bold w-1/5 text-right"}>
{priceFormat(price * cartQuantity)}
</p>
<div>
<button onClick={() => removeCart(cart)}>
<RxCross2 className="w-5 h-5 text-lightGray" />
</button>
</div>
</div>
);
})}
{cartItems.length === 0 && (
<div className="w-full h-96 flex flex-col justify-center items-center gap-10">
<div className="w-1/3 flex justify-center items-center">
<Image
src={CartIcon}
alt="cart icon"
width={0}
height={0}
sizes="100vw"
style={{ width: "50%", height: "auto" }}
/>
</div>
<div>
<span className={"text-[20px] text-darkgray"}>
{deliveryFee === 0 ? "무료배송" : deliveryFee}
</span>
</div>

<p className={"text-[24px]"}>
{priceFormat(price * cartQuantity)}
<p className="text-3xl text-[#dddddd] font-bold">
장바구니가 비어 있습니다
</p>
<div>
<Button onClick={() => removeCart(cart)}>삭 제</Button>
</div>
</div>
<div className={"border-[1px] mt-4"} />
</div>
);
})}
</>
)}
<div className="mt-8 flex justify-between items-start">
<Button type="button" style="py-3 px-12" onClick={clearCart}>
카트 비우기
</Button>

<div>
<div className={"flex justify-between items-center"}>
<p className={"text-[20px]"}>전체 상품 개수</p>
<p className={"text-[24px]"}>{cartTotalQuantity}</p>
)}
</div>
<div className="flex justify-start items-center gap-2 text-lg font-bold mt-5">
<input
type="checkbox"
id="checkAll"
checked={isAllChecked}
onChange={altCheckAll}
className={
cartItems.length === 0
? "cursor-default"
: "cursor-pointer" +
" appearance-none w-5 h-5 border border-lightGray checked:bg-[url('/checkmark_io.svg')] bg-no-repeat bg-center checked:bg-primaryBlue"
}
disabled={cartItems.length === 0}
/>
<label
htmlFor="checkAll"
className={
cartItems.length === 0 ? "cursor-default" : "cursor-pointer"
}
>
전체 선택
</label>
<p className="cursor-default">{"|"}</p>
<button
onClick={() => dispatch(REMOVE_CHECKED_ITEMS_FROM_CART())}
disabled={
cartItems.length === 0 ||
cartItems.every(item => !item.isChecked)
}
>
선택 삭제
</button>
</div>
</div>
<div className={"flex gap-4"}>
<h4 className={"font-semibold mb-4 text-xl"}>합계</h4>
<p className={"font-semibold text-2xl"}>
{priceFormat(cartTotalAmount)}
</p>
<div className="flex flex-col justify-start items-start w-1/4 gap-5">
<CartInfoArticle />
<div className="w-full h-14">
<Button
onClick={checkout}
style="text-xl font-bold"
disabled={isDisabled}
>
주문하기
</Button>
</div>
</div>
<Button onClick={checkout} style="w-full h-10">
주 소 입 력
</Button>
</div>
</div>
</section>
);
</section>
);
else return <></>;
}
49 changes: 49 additions & 0 deletions src/app/(cart)/cart/CartInfoArticle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use client";
import { useSelector, useDispatch } from "react-redux";
import {
selectCheckedTotalAmount,
selectCheckedTotalQuantity,
CALCULATE_CHECKED_ITEMS_QUANTITY,
CALCULATE_CHECKED_ITEMS_SUBTOTAL,
} from "@/redux/slice/cartSlice";
import { useEffect } from "react";
import deliveryFee from "@/constants/deliveryFee";
import priceFormat from "@/utils/priceFormat";

const CartInfoArticle = () => {
const cartTotalAmount = useSelector(selectCheckedTotalAmount);
const cartTotalQuantity = useSelector(selectCheckedTotalQuantity);
const dispatch = useDispatch();

useEffect(() => {
dispatch(CALCULATE_CHECKED_ITEMS_QUANTITY());
dispatch(CALCULATE_CHECKED_ITEMS_SUBTOTAL());
}, [dispatch]);
return (
<div className="flex flex-col justify-between border border-lightGray h-[200px] w-full p-4 rounded-md">
<div>
<div className={"flex justify-between items-center"}>
<p className={"text-[22px] font-bold"}>전체 상품 개수</p>
<p className={"text-[22px]"}>{cartTotalQuantity}</p>
</div>
<div className="flex justify-between items-center">
<p className={"text-[22px] font-bold"}>배송비</p>
<p className={"text-[22px]"}>{deliveryFee}</p>
</div>
</div>
<div
className={
"flex justify-between items-end gap-4 border-t border-lightGray pt-4"
}
>
<h4 className={"font-bold text-[22px]"}>총 결제금액</h4>
<div className={"font-bold text-2xl flex justify-start items-end"}>
<p>{priceFormat(cartTotalAmount)}</p>
<p className="text-lg"></p>
</div>
</div>
</div>
);
};

export default CartInfoArticle;
6 changes: 1 addition & 5 deletions src/app/(cart)/cart/page.tsx
Original file line number Diff line number Diff line change
@@ -10,9 +10,5 @@ export default async function CartPage() {
redirect(URLS.SIGNIN);
}

return (
<>
<CartClient />
</>
);
return <CartClient />;
}
277 changes: 169 additions & 108 deletions src/app/(checkout)/checkout-address/CheckoutAddressClient.tsx
Original file line number Diff line number Diff line change
@@ -11,20 +11,21 @@ import Heading from "@/components/heading/Heading";
import Button from "@/components/button/Button";
import { useSession } from "next-auth/react";
import URLS from "@/constants/urls";
import CartInfoArticle from "@/app/(cart)/cart/CartInfoArticle";

const initialState = {
name: "",
line: "",
city: "",
postalCode: "",
phone: "",
memo: "",
};

const initialState2 = {
name: "",
phone: "",
userEmail: "",
memo: "",
};

const id = "daum-postcode"; // script가 이미 rendering 되어 있는지 확인하기 위한 ID
@@ -45,6 +46,8 @@ export default function CheckoutAddressClient() {
...initialState2,
});

const [isSame, setIsSame] = useState<boolean>(true);

const dispatch = useDispatch();
const router = useRouter();

@@ -76,7 +79,9 @@ export default function CheckoutAddressClient() {
}
};

const handleShipping = async (e: ChangeEvent<HTMLInputElement>) => {
const handleShipping = async (
e: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement>,
) => {
const { name, value } = e.target;
setShippingAddress({ ...shippingAddress, [name]: value });
};
@@ -88,6 +93,7 @@ export default function CheckoutAddressClient() {

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(shippingAddress, billingAddress);
dispatch(
SAVE_SHIPPING_ADDRESS({
...shippingAddress,
@@ -101,117 +107,172 @@ export default function CheckoutAddressClient() {
router.push(URLS.CHECKOUT);
};

const LABELSTYLE =
"block font-medium mt-[50px] mb-[22px] text-[20px] text-darkgray";
const hypenTel = (target: any) => {
target.value = target.value
.replace(/[^0-9]/g, "")
.replace(/^(\d{2,3})(\d{3,4})(\d{4})$/, `$1-$2-$3`);
};

const LABELSTYLE = "block font-bold mt-[50px] mb-3 text-[20px] text-darkgray";
const INPUTSTYLE =
"block w-full text-2xl font-light p-4 mx-auto my-0 border border-black rounded-md outline-none";
"block w-full h-10 text-xl font-light p-4 mx-auto my-0 border border-lightGray rounded-md outline-none";

return (
<section className="w-[1020px] mx-auto my-12">
<Heading title={"상세주문"} fontSize={"3xl"} />
<form className="w-full flex" onSubmit={handleSubmit}>
<div className="w-full p-4">
<h3 className="font-bold text-[1.4rem] mx-0 my-4">배송지 주소</h3>
<label className={LABELSTYLE}>받는 사람 이름</label>
<input
required
name={"name"}
className={INPUTSTYLE}
value={shippingAddress.name}
onChange={e => handleShipping(e)}
placeholder="받는 사람 이름"
type="text"
/>
<div className="flex justify-between items-center mr-3">
<label className={LABELSTYLE}>우편번호</label>
<Button
onClick={ZipCodeSearch}
styleType="blank"
style={
"mt-[46px] mb-[18px] bg-colorBlack text-white text-medium p-2"
}
>
우편번호 검색
</Button>
<section className="w-[80%] mx-auto my-24 min-h-[80vh] flex flex-col items-start justify-start">
<Heading title={"배송지 입력"} fontSize={"6xl"} />
<form className="flex w-full mt-10 gap-20" onSubmit={handleSubmit}>
<div className="w-2/3">
<div className="w-full flex flex-col">
<div className="w-full p-4 border-t border-lightGray">
<div className="w-full flex gap-8">
<div className="w-1/3">
<label className={LABELSTYLE}>이름(주문자)</label>
<input
required
className={INPUTSTYLE}
name={"name"}
value={billingAddress.name}
onChange={e => handleBilling(e)}
type="text"
disabled={isSame}
/>
</div>
<div className="w-1/3">
<label className={LABELSTYLE}>연락처</label>
<input
required
className={INPUTSTYLE}
name={"phone"}
value={billingAddress.phone}
onChange={e => handleBilling(e)}
type="tel"
pattern="[0-9]{3}-[0-9]{4}-[0-9]{4}"
disabled={isSame}
onInput={e => hypenTel(e.target)}
/>
</div>
</div>
<div className="flex justify-start items-center mt-10">
<input
id="same"
type="checkbox"
className="appearance-none w-5 h-5 border border-lightGray checked:bg-[url('/checkmark_io.svg')] bg-no-repeat bg-center checked:bg-primaryBlue"
onChange={() => setIsSame(!isSame)}
checked={isSame}
/>
<label htmlFor="same" className="text-lg font-bold ml-2">
수령자와 동일합니다.
</label>
</div>
</div>
<div className="w-full p-4">
<div className="w-full flex gap-8">
<div className="w-1/3">
<label className={LABELSTYLE}>이름(수령자)</label>
<input
required
name={"name"}
className={INPUTSTYLE}
value={shippingAddress.name}
onChange={e => {
handleShipping(e);
if (isSame) {
handleBilling(e);
}
}}
type="text"
/>
</div>
<div className="w-1/3">
<label className={LABELSTYLE}>연락처</label>
<input
required
className={INPUTSTYLE}
name={"phone"}
value={shippingAddress.phone}
onChange={e => {
handleShipping(e);
if (isSame) {
handleBilling(e);
}
}}
type="tel"
pattern="[0-9]{3}-[0-9]{4}-[0-9]{4}"
onInput={e => hypenTel(e.target)}
/>
</div>
</div>
<div className="flex w-full">
<div className="w-1/4 mr-3">
<label className={LABELSTYLE}>우편번호</label>
<input
required
readOnly
className={INPUTSTYLE}
name={"postalCode"}
value={zipCode}
type="text"
/>
</div>
<div className="w-3/4">
<label className={LABELSTYLE}>주소</label>
<div className="flex gap-2">
<input
required
readOnly
className={INPUTSTYLE}
name={"city"}
value={roadAddress}
type="text"
/>
<div className="w-1/5">
<Button onClick={ZipCodeSearch} styleType="primary">
우편번호 검색
</Button>
</div>
</div>
</div>
</div>
<label className={LABELSTYLE}>상세 주소 입력</label>
<input
required
className={INPUTSTYLE}
name={"line"}
value={shippingAddress.line}
onChange={e => handleShipping(e)}
type="text"
/>
<label className={LABELSTYLE}>배송 요청 사항</label>
<textarea
required
className={INPUTSTYLE + "mb-6 h-20 focus:outline-none"}
name={"memo"}
value={shippingAddress.memo}
onChange={e => handleShipping(e)}
/>
</div>
</div>
<input
required
readOnly
className={INPUTSTYLE}
name={"postalCode"}
value={zipCode}
placeholder="우편번호 입력"
type="text"
/>

<label className={LABELSTYLE}>주소</label>
<input
required
readOnly
className={INPUTSTYLE}
name={"city"}
value={roadAddress}
placeholder="도시"
type="text"
/>

<label className={LABELSTYLE}>상세 주소 입력</label>
<input
required
className={INPUTSTYLE}
name={"line"}
value={shippingAddress.line}
onChange={e => handleShipping(e)}
placeholder="상세 주소 입력"
type="text"
/>
<label className={LABELSTYLE}>연락처</label>
<input
required
className={INPUTSTYLE}
name={"phone"}
value={shippingAddress.phone}
onChange={e => handleShipping(e)}
placeholder="연락처를 입력하세요"
type="text"
/>
</div>

<div className="w-full p-4">
<h3 className="font-bold text-[1.4rem] mx-0 my-4">주문하신 분</h3>
<label className={LABELSTYLE}>보내는 사람 이름</label>
<input
required
className={INPUTSTYLE}
name={"name"}
value={billingAddress.name}
onChange={e => handleBilling(e)}
placeholder="주문자 성명"
type="text"
/>

<label className={LABELSTYLE}>연락처</label>
<input
required
className={INPUTSTYLE}
name={"phone"}
value={billingAddress.phone}
onChange={e => handleBilling(e)}
placeholder="연락처를 입력하세요"
type="text"
/>
<label className={LABELSTYLE}>배송 요청 사항</label>
<input
required
className={INPUTSTYLE + " mb-6"}
name={"memo"}
value={billingAddress.memo}
onChange={e => handleBilling(e)}
placeholder="배송 요청 사항 입력"
type="text"
/>

<Button type={"submit"}>주문하기</Button>
<div className="flex flex-col justify-start items-start w-1/4 gap-5">
<CartInfoArticle />
<div className="flex w-full gap-2">
<div className="w-1/2 h-14">
<Button
onClick={() => {
history.back();
}}
style="text-xl font-bold"
styleType="secondary"
>
이전으로
</Button>
</div>
<div className="w-1/2 h-14">
<Button type="submit" style="text-xl font-bold">
다음
</Button>
</div>
</div>
</div>
</form>
</section>
91 changes: 55 additions & 36 deletions src/app/(checkout)/checkout-success/page.tsx
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@ import { formatTime } from "@/utils/dayjs";
import Button from "@/components/button/Button";
import Link from "next/link";
import URLS from "@/constants/urls";
import Image from "next/image";
import SuccessCheck from "@/assets/cart/successCheck.svg";

type Props = {
searchParams: {
@@ -26,47 +28,64 @@ export default async function CheckoutSuccess({ searchParams }: Props) {
return res.json();
});

const listStyle = "text-xl mb-4";
const listStyle = "flex justify-between text-lg mb-4";
const subTitle = "text-[22px] font-bold";
return (
<>
<section className="w-[1020px] mx-auto mt-[3rem]">
<Heading title={"결재 성공 "} />
<ul className="p-4">
<section className="w-[80%] mx-auto my-24 min-h-[80vh] flex flex-col items-center justify-center">
<Image
src={SuccessCheck}
alt="successIcon"
width={80}
height={80}
className="my-10"
/>
<Heading title={"결제 완료"} fontSize="6xl" />
<ul className="p-8 border border-lightGray w-2/5 rounded-md mt-12">
<li className={listStyle}>
<b className={subTitle}>결제 상품</b>
{payment?.orderName}
</li>
<li className={listStyle}>
<b className={subTitle}>주문 번호</b>
{payment?.orderId}
</li>
<li className={listStyle + " pb-2"}>
<b className={subTitle}>결제 수단</b>
{payment?.method}
</li>
{/* {payment.card && (
<li className={listStyle}>
<b>결재 상품:</b>
{payment?.orderName}
<b className={subTitle}>카드 번호</b>
{payment.card?.number}
</li>
)}
{payment.approvedAt && (
<li className={listStyle}>
<b>주문 번호:</b>
{payment?.orderId}
<b className={subTitle}>결제승인날짜</b>
{formatTime(payment.approvedAt)}
</li>
<li className={listStyle}>
<b>결재 수단:</b>
{payment?.method}
)} */}
{payment.totalAmount && (
<li className="flex justify-between mt-4 border-t border-lightGray pt-6">
<b className={subTitle}>결제 금액</b>
<b className={subTitle}>
{priceFormat(payment.totalAmount) + "원"}
</b>
</li>
{payment.card && (
<li className={listStyle}>
<b>카드 번호:</b>
{payment.card?.number}
</li>
)}
{payment.totalAmount && (
<li className={listStyle}>
<b>결재 금액:</b>
{priceFormat(payment.totalAmount)}
</li>
)}
{payment.createdAt && (
<li className={listStyle}>
<b>결재승인날짜:</b>
{formatTime(payment.createdAt)}
</li>
)}
</ul>
<Button style="py-3 px-12">
<Link href={URLS.ORDER_HISTORY}>주문 상태 보기</Link>
</Button>
</section>
</>
)}
</ul>
<div className="flex w-1/3 gap-4 mt-10">
<div className="w-1/2 h-14">
<Button styleType="secondary" style="text-xl font-bold">
<Link href={URLS.rootURL}>홈으로</Link>
</Button>
</div>
<div className="w-1/2 h-14">
<Button styleType="primary" style="text-xl font-bold">
<Link href={URLS.ORDER_HISTORY}>주문 내역 확인</Link>
</Button>
</div>
</div>
</section>
);
}
67 changes: 45 additions & 22 deletions src/app/(checkout)/checkout/CheckoutClient.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client";
import Heading from "@/components/heading/Heading";
import Button from "@/components/button/Button";
import { FormEvent } from "react";
import { FormEvent, useEffect } from "react";
import CheckoutForm from "@/components/checkoutForm/CheckoutForm";
import { loadTossPayments } from "@tosspayments/payment-sdk";
import {
CLEAR_CART,
selectCartItems,
selectCartTotalAmount,
selectCartTotalQuantity,
REMOVE_CHECKED_ITEMS_FROM_CART,
selectCheckedCartItems,
selectCheckedTotalAmount,
selectCheckedTotalQuantity,
} from "@/redux/slice/cartSlice";
import { useDispatch, useSelector } from "react-redux";
import { toast } from "react-toastify";
@@ -22,18 +22,21 @@ import dayjs from "dayjs";
import { saveCart } from "@/services/sanity/cart";
import { Order } from "@/type/order";
import URLS from "@/constants/urls";
import CartInfoArticle from "@/app/(cart)/cart/CartInfoArticle";
import { useState } from "react";

export default function CheckoutClient() {
const { data: session } = useSession();
const userEmail = session?.user?.email;
const [isScriptLoaded, setIsScriptLoaded] = useState<boolean>(false);

const dispatch = useDispatch();
const router = useRouter();
const cartItems = useSelector(selectCartItems);
const cartItems = useSelector(selectCheckedCartItems);
const shippingAddress = useSelector(selectShippingAddress);
const billingAddress = useSelector(selectBillingAddress);
const cartTotalAmount = useSelector(selectCartTotalAmount);
const cartTotalQuantity = useSelector(selectCartTotalQuantity);
const cartTotalAmount = useSelector(selectCheckedTotalAmount);
const cartTotalQuantity = useSelector(selectCheckedTotalQuantity);

const clientkey = process.env.NEXT_PUBLIC_TOSS_CLIENT_KEY;
const secretkey = process.env.NEXT_PUBLIC_TOSS_SECRET_KEY;
@@ -98,7 +101,7 @@ export default function CheckoutClient() {
};
await saveCart(orderData as Order);
// db에 저장
dispatch(CLEAR_CART());
dispatch(REMOVE_CHECKED_ITEMS_FROM_CART());
router.push(`${URLS.CHECKOUT_SUCCESS}?orderId=${orderId}`);
})
.catch(error => {
@@ -112,21 +115,41 @@ export default function CheckoutClient() {
}
};

return (
<section>
<div className="w-[1020px] mx-auto my-12">
<Heading title={"주문하기"} />
<form onSubmit={handleSubmit}>
<div>
useEffect(() => {
setIsScriptLoaded(true);
}, []);

if (isScriptLoaded)
return (
<section className="w-[80%] mx-auto my-24 min-h-[80vh] flex flex-col items-start justify-start">
<Heading title={"상품결제"} fontSize="6xl" />
<form onSubmit={handleSubmit} className="flex w-full mt-10 gap-20">
<div className="w-2/3">
<CheckoutForm />
</div>
<div className={"flex justify-center items-center"}>
<Button type={"submit"} style="py-3 px-12">
결재하기
</Button>
<div className="flex flex-col justify-start items-start w-1/4 gap-5">
<CartInfoArticle />
<div className="flex w-full gap-2">
<div className="w-1/2 h-14">
<Button
onClick={() => {
history.back();
}}
style="text-xl font-bold"
styleType="secondary"
>
이전으로
</Button>
</div>
<div className="w-1/2 h-14">
<Button type="submit" style="text-xl font-bold">
결제하기
</Button>
</div>
</div>
</div>
</form>
</div>
</section>
);
</section>
);
else return <></>;
}
16 changes: 8 additions & 8 deletions src/app/(order)/order/details/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import deliveryFee from "@/constants/deliveryFee";
import Link from "next/link";
import { Order } from "@/type/order";
import URLS from "@/constants/urls";
import Button from "@/components/button/Button";
interface OrderDetailsProps {
params: {
id: string;
@@ -24,8 +25,8 @@ const OrderDetails = async ({ params }: OrderDetailsProps) => {
<div className="w-full border-b-2 border-colorBlack pb-14">
<OrderProduct order={order} />
</div>
<div className="flex w-full border-b-2 border-colorBlack pb-24 gap-x-28 p-7">
<h2 className="text-xl font-bold w-32">배송지 정보</h2>
<div className="flex justify-between items-center w-full border-b-2 border-colorBlack pb-24 gap-x-28 p-7">
<h2 className="text-xl font-bold w-1/4">배송지 정보</h2>
<div className="text-darkGray">
<div className="flex gap-7 mb-2">
<p>{order.shippingAddress.name}</p>
@@ -40,9 +41,8 @@ const OrderDetails = async ({ params }: OrderDetailsProps) => {
</div>
<div className="flex w-full pb-24 p-7 justify-between border-b-2 border-colorBlack">
<div className="flex gap-x-28">
<h2 className="text-xl font-bold w-32">주문 결제 정보</h2>
<h2 className="text-xl font-bold w-1/4">주문 결제 정보</h2>
<div className="text-darkGray">
<div>결제 수단</div>
<div>{formattedOrderTime}</div>
</div>
</div>
@@ -65,7 +65,7 @@ const OrderDetails = async ({ params }: OrderDetailsProps) => {
</div>
{deliveryEvents && (
<div className="flex flex-col w-full pb-24 p-7">
<h2 className="text-xl font-bold w-32 mb-6">배송 현황</h2>
<h2 className="text-xl font-bold w-1/4 mb-6">배송 현황</h2>
{deliveryEvents.map((event, index) => (
<div key={index} className="flex h-28 flex-col gap-2 p-6">
<p className="font-bold">{event.node.status.code}</p>
@@ -75,12 +75,12 @@ const OrderDetails = async ({ params }: OrderDetailsProps) => {
))}
</div>
)}
<div className="flex items-center justify-center w-full">
<div className="flex items-center justify-center w-full mt-16">
<Link
className="border border-colorBlack w-[350px] h-[80px] text-2xl font-bold flex justify-center items-center"
className="w-[350px] h-[80px] text-2xl font-bold flex justify-center items-center"
href={URLS.ORDER_HISTORY}
>
주문 목록
<Button styleType="primary">주문 목록</Button>
</Link>
</div>
</>
15 changes: 10 additions & 5 deletions src/app/(order)/order/history/OrderHistoryClient.tsx
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import { STORE_ORDER, selectOrders } from "@/redux/slice/orderSlice";
import { Order } from "@/type/order";
import { TrackingResponseEvent } from "@/type/order";
import Loader from "@/components/loader/Loader";
import Heading from "@/components/heading/Heading";

interface OrderHistoryClientProps {
userEmail: string;
@@ -60,16 +61,20 @@ const OrderHistoryClient = ({ userEmail }: OrderHistoryClientProps) => {
if (!orders) return <Loader />;

return (
<div className="w-full flex flex-col gap-y-40">
<StatusProgress />
<section className="w-full flex flex-col gap-y-40">
<div className="flex flex-col justify-start items-start">
<Heading title="배송상황" fontSize="3xl" />
<div className="border-b border-lightGray mb-7 w-full" />
<StatusProgress />
</div>
<div>
<div className="flex justify-between mb-[30px]">
<h2 className="text-xl font-semibold">주문 내용</h2>
<div className="flex justify-between mb-[30px] border-b border-lightGray">
<Heading title="주문내역" fontSize="3xl" />
<PeriodSelector />
</div>
<OrderList />
</div>
</div>
</section>
);
};

45 changes: 31 additions & 14 deletions src/app/(order)/order/history/OrderList.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@ import { RootState } from "@/redux/store";
import Pagination from "@/components/pagination/Pagination";
import OrderProduct from "./OrderProduct";
import { selectOrders } from "@/redux/slice/orderSlice";
import DocsIcon from "@/assets/order/docs.svg";
import Image from "next/image";

const OrderList = () => {
//기간 별 filtering
@@ -34,21 +36,36 @@ const OrderList = () => {
indexOfFirstOrder,
indexOfLastOrder,
);

return (
<>
<div>
{currentOrders?.map((order, index) => (
<OrderProduct order={order} index={index} key={index} />
))}
<Pagination
currentPage={currentPage}
productsPerPage={ordersPerPage}
setCurrentPage={setCurrentPage}
totalProducts={orders.length}
/>
if (currentOrders.length === 0 || !currentOrders)
return (
<div className="w-full min-h-[50vh] flex flex-col justify-center items-center gap-6">
<div className="w-1/8">
<Image
src={DocsIcon}
alt="docs-icon"
width={0}
height={0}
sizes="100vw"
style={{ width: "100%", height: "auto" }}
/>
</div>
<p className="text-[22px] text-lightGray font-bold">
주문 내역이 없습니다.
</p>
</div>
</>
);
return (
<div>
{currentOrders?.map((order, index) => (
<OrderProduct order={order} index={index} key={index} />
))}
<Pagination
currentPage={currentPage}
productsPerPage={ordersPerPage}
setCurrentPage={setCurrentPage}
totalProducts={orders.length}
/>
</div>
);
};

90 changes: 55 additions & 35 deletions src/app/(order)/order/history/OrderProduct.tsx
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import { ORDER_STATUS } from "@/constants/status";
import { Order } from "@/type/order";
import { useDispatch } from "react-redux";
import { trackDeliveryThunk } from "@/redux/slice/orderSlice";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import URLS from "@/constants/urls";

interface OrderProductProps {
@@ -19,6 +19,7 @@ const OrderProduct = ({ order, index = 0 }: OrderProductProps) => {
const pathname = usePathname();
const orderTime = new Date(order.createdAt).toISOString();
const orderStatus = order.orderStatus;
const [isDetail, setIsDetail] = useState(false);

const statusTitleArr = ORDER_STATUS.map(status => status.title);
const statusArray = ORDER_STATUS.map(status => status.value);
@@ -28,54 +29,73 @@ const OrderProduct = ({ order, index = 0 }: OrderProductProps) => {
dispatch<any>(trackDeliveryThunk(order));
}, [dispatch, orderStatus]);

useEffect(() => {
if (pathname === "/order/history") {
setIsDetail(false);
} else {
setIsDetail(true);
}
}, []);

return (
<div className={`w-full pb-10`} key={index}>
<div
className={`flex w-full justify-between border-b-2 ${
pathname == "/order/order-history"
? "border-lightGray"
: "border-colorBlack"
} pb-3`}
<Link
href={
!isDetail
? `${URLS.ORDER_DETAILS}/${order._id}`
: "javascript:void(0)"
}
className={`flex w-full h-[60px] justify-between pb-3 bg-bgWhiteSmoke border border-lightGray rounded-md p-3 ${
!isDetail ? "cursor-pointer" : "cursor-default"
} px-4 transition-all transition-duration-600 ease-in-out`}
>
<h3>{`${orderTime.split("T")[0].replaceAll("-", ".")}${
pathname === "/order/order-history" ? "" : ` / ${order._id}`
}`}</h3>
{pathname && pathname === "/order/order-history" && (
<Link
href={`${URLS.ORDER_DETAILS}/${order._id}`}
className="flex items-center text-darkgray text-md hover:underline"
>
<p>주문상세</p> <BsChevronRight />
</Link>
)}
</div>
<div className="flex justify-start items-center gap-10 text-lg font-bold">
<h3>주문일자: {orderTime.split("T")[0]}</h3>
<h3>주문번호: {order._id}</h3>
</div>
<div className="flex justify-end items-center gap-10 font-bold">
<h3 className="text-lg">총 결제금액</h3>
<h3 className="text-[22px]">{order.orderAmount}</h3>
</div>
</Link>
{order.cartItems &&
order.cartItems.map((product, index) => (
<div
key={index}
className="h-[170px] py-[10px] flex items-center justify-between"
className="h-[170px] py-[10px] flex items-center justify-between px-8"
>
<div className="flex justify-start items-center gap-16 w-1/3">
<Image
src={product.imageURL || ""}
alt={"제품사진"}
width={150}
height={150}
/>
<p>{index + 1}</p>
<div className="w-20 h-20 flex justify-center items-center border border-lightGray">
<Image
src={product.imageURL || ""}
alt={"제품사진"}
width={0}
height={0}
sizes="100vw"
style={{ width: "70%", height: "auto" }}
/>
</div>
<div className="flex flex-col justify-start items-start">
<p className="font-normal text-black mb-2 whitespace-nowrap">
<p className="font-normal text-black mb-2 whitespace-nowrap text-lg">
{product.name}
</p>
<p className="font-normal text-darkGray whitespace-nowrap">{`${
product.cartQuantity
}개 / ${Number(
product.price * product.cartQuantity,
).toLocaleString()} won`}</p>
<p className="font-normal text-darkGray whitespace-nowrap">
{statusTitleArr[statusArray.indexOf(order.orderStatus)]}
</p>
</div>
</div>
<div className="w-1/3 flex justify-end items-center">
<div className="flex justify-center items-center w-1/2 text-darkgray">
{statusTitleArr[statusArray.indexOf(order.orderStatus)]}
<div className="w-1/3 flex justify-end items-center text-lg">
<div className="flex justify-end items-center w-1/2 gap-20">
<p className="font-normal whitespace-nowrap">
{product.cartQuantity}
</p>
<p className="font-normal whitespace-nowrap">
{Number(
product.price * product.cartQuantity,
).toLocaleString()}
</p>
</div>
</div>
</div>
26 changes: 16 additions & 10 deletions src/app/(order)/order/history/StatusProgress.tsx
Original file line number Diff line number Diff line change
@@ -20,19 +20,25 @@ const StatusProgress = () => {
}, {});

return (
<div className="w-full h-[120px] bg-[#f1f1f5] flex justify-around items-center">
<div className="w-full flex justify-around items-center">
{/* statusArray에서 각 요소들과, 그에 맞는 statusCount를 배치 */}
{statusArray.map((status, index) => (
<div
key={index}
className="flex flex-col justify-center items-center relative font-medium text-lg"
>
<p className="mb-2.5">{statusTitleArr[index]}</p>
<p>{statusCount[status] ? statusCount[status] : 0}</p>
{status !== "done" && (
<BsChevronRight className="absolute -right-[68px] text-darkgray" />
<>
<div
key={index}
className="flex flex-col justify-center items-center relative font-medium text-lg bg-bgWhiteSmoke w-[200px] h-[120px] rounded-md border border-lightGray p-3"
>
<p className="mb-2.5 text-[22px] font-bold">
{statusTitleArr[index]}
</p>
<p className="text-3xl font-bold">
{statusCount[status] ? statusCount[status] : 0}
</p>
</div>
{index < statusArray.length - 1 && (
<BsChevronRight className="text-3xl text-black" />
)}
</div>
</>
))}
</div>
);
15 changes: 7 additions & 8 deletions src/app/(order)/order/layout.tsx
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import URLS from "@/constants/urls";
import Heading from "@/components/heading/Heading";

const OrderLayout = async ({ children }: { children: React.ReactNode }) => {
const session = await getServerSession(authOptions); // 서버에서 session 정보 호출
@@ -11,16 +12,14 @@ const OrderLayout = async ({ children }: { children: React.ReactNode }) => {
redirect(URLS.SIGNIN);
}
const links = [URLS.CART, URLS.ORDER_HISTORY];
const listStr = ["장바구니", "주문내역"];
const listStr = ["나의 장바구니", "배송/주문 확인"];
return (
<section className="w-[1280px] mx-auto mb-[200px]">
<header className="border-b-2 w-full border-whitegray h-[304px] mb-24 box-border flex justify-start items-end">
<h2 className="text-[40px] font-semibold pb-[30px]">마이페이지</h2>
<section className="w-[80%] mx-auto my-24 min-h-[80vh] flex flex-col items-center justify-center">
<header className="w-full mb-16 box-border flex justify-start items-end">
<Heading title="마이페이지" fontSize="6xl" />
</header>
<div className="flex">
<div className="mr-20">
<SideBar linkArray={links} listStr={listStr} />
</div>
<div className="w-full flex gap-20">
<SideBar linkArray={links} listStr={listStr} />
<div className="w-full flex flex-col gap-y-40">
<div>{children}</div>
</div>
3 changes: 3 additions & 0 deletions src/assets/cart/cartIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/cart/checkmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/cart/successCheck.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/order/docs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 11 additions & 6 deletions src/components/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

interface IButtonProps {
type?: "submit" | "reset" | "button" | undefined;
styleType?: "primary" | "blank";
styleType?: "primary" | "secondary" | "blank";
style?: string;
[x: string]: any;
}
@@ -17,9 +17,14 @@ const Button = ({
let btnType = "";
switch (styleType) {
case "primary":
btnType = `w-[166px] h-[50px] bg-colorBlack text-white ${
disabled ? "bg-gray-600 text-gray-300" : ""
}`;
btnType = `w-full h-full text-white rounded-md ${
disabled ? "bg-lightGray text-white" : "bg-primaryBlue"
} ${style}`;
break;
case "secondary":
btnType = `w-full h-full bg-white text-primaryBlue border border-primaryBlue rounded-md ${
disabled ? "bg-gray-200 text-gray-400 border-none" : ""
} ${style}`;
break;
case "blank":
btnType = style;
@@ -29,8 +34,8 @@ const Button = ({
}
return (
<button
className={`cursor-pointer box-border inline-flex justify-center items-center ${
disabled ? "cursor-not-allowed" : ""
className={`box-border inline-flex justify-center items-center ${
disabled ? "cursor-default" : "cursor-pointer"
} ${btnType}`}
type={type}
disabled={disabled}
92 changes: 34 additions & 58 deletions src/components/checkoutForm/CheckoutForm.tsx
Original file line number Diff line number Diff line change
@@ -3,81 +3,57 @@
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
CALCULATE_SUBTOTAL,
CALCULATE_TOTAL_QUANTITY,
selectCartItems,
selectCartTotalAmount,
selectCartTotalQuantity,
CALCULATE_CHECKED_ITEMS_QUANTITY,
CALCULATE_CHECKED_ITEMS_SUBTOTAL,
selectCheckedCartItems,
} from "@/redux/slice/cartSlice";
import Link from "next/link";
import priceFormat from "@/utils/priceFormat";
import Image from "next/image";

export default function CheckoutForm() {
const cartItems = useSelector(selectCartItems);
const cartTotalQuantity = useSelector(selectCartTotalQuantity);
const cartTotalAmount = useSelector(selectCartTotalAmount);
const cartItems = useSelector(selectCheckedCartItems);
const dispatch = useDispatch();

useEffect(() => {
dispatch(CALCULATE_SUBTOTAL());
dispatch(CALCULATE_TOTAL_QUANTITY());
dispatch(CALCULATE_CHECKED_ITEMS_SUBTOTAL());
dispatch(CALCULATE_CHECKED_ITEMS_QUANTITY());
}, [dispatch, cartItems]);

return (
<div>
<h3 className="text-[1.7rem] mb-4">주문 요약</h3>
<div>
{cartItems.length === 0 ? (
<>
<p>장바구니 상품이 없습니다.</p>
<Link href={"/"}>홈으로</Link>
</>
) : (
<>
<div>
{cartItems.map(item => {
const { id, name, price, cartQuantity, imageURL } = item;
return (
// <div key={ id } className={ styles.card }>
<div
key={id}
className={
"flex justify-start items-center px-3 gap-3 text-[20px] border border-1px"
}
>
<Image src={imageURL} alt={name} width={100} height={100} />
<p>
<b>상품:</b> {name}
</p>
<p>
<b>개수:</b> {cartQuantity}
</p>
<p>
<b>가격:</b> {price}
</p>
<p>
<b>합계:</b> {priceFormat(price * cartQuantity)}
</p>
</div>
);
})}

<div className="py-20 px-5 border-y border-lightGray">
{cartItems.map((item, index) => {
const { id, name, price, cartQuantity, imageURL } = item;
return (
<div
className={"flex justify-end text-[25px] mt-3 font-semibold"}
key={id}
className={
"flex justify-between items-center px-3 gap-3 text-[20px]"
}
>
<p>
<b>총 상품 개수:</b> {cartTotalQuantity}
</p>
</div>
<div className={" flex justify-end text-[25px] py-3"}>
<p>
<b>합 계:</b> {priceFormat(cartTotalAmount)}
<p>{index + 1}</p>
<div className="w-[100px] h-[100px] flex justify-center items-center border border-lightGray">
<Image
src={imageURL}
alt={name}
width={0}
height={0}
sizes="100vw"
style={{ width: "80%", height: "auto" }}
/>
</div>
<div className="w-1/3">
<p className={"text-lg text-nomal w-full"}>{name}</p>
</div>
<p className="text-lg">{cartQuantity}</p>
<p className="font-bold text-[22px]">
{priceFormat(price * cartQuantity)}
</p>
</div>
</div>
</>
)}
);
})}
</div>
</div>
</div>
);
2 changes: 1 addition & 1 deletion src/components/datePicker/DatePickerClient.tsx
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ const CustomInput = forwardRef(
ref: React.Ref<HTMLButtonElement>,
) => (
<button
className="w-[107px] h-[33px] text-darkgray border-[1px] border-whitegray flex justify-center items-center text-sm"
className="w-[150px] h-[40px] text-darkgray border-[1px] border-whitegray flex justify-center items-center text-lg rounded-sm"
onClick={onClick}
ref={ref}
>
8 changes: 3 additions & 5 deletions src/components/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -46,10 +46,10 @@ const Pagination = ({
}

const listStyle =
"text-lg border border-gray-300 min-w-12 h-12 p-1 flex justify-center items-center cursor-pointer mx-1";
"text-lg min-w-12 h-12 p-1 flex justify-center items-center cursor-pointer mx-1";

return (
<ul className="list-none mt-4 pt-4 border-t-2 border-gray-300 flex justify-center items-center">
<ul className="list-none mt-4 pt-4 flex justify-center items-center">
<li
onClick={paginatePrevPage}
className={currentPage === pageNumbers[0] ? "hidden" : ""}
@@ -64,9 +64,7 @@ const Pagination = ({
key={number}
onClick={() => paginate(number)}
className={`${listStyle} ${
currentPage === number
? `border border-colorBlack text-colorBlack`
: ""
currentPage === number ? ` text-colorBlack` : ""
}`}
>
{number}
60 changes: 12 additions & 48 deletions src/layouts/periodSelector/PeriodSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"use client";
import Image from "next/image";
import CalendarIcon from "@/assets/calendar-icon.svg";
import React, { useState, useEffect } from "react";
import { setStartDate, setEndDate } from "@/redux/slice/periodSlice";
import { useDispatch } from "react-redux";
import DatePickerClient from "@/components/datePicker/DatePickerClient";
import Button from "@/components/button/Button";

const PeriodSelector = () => {
const [inputStartDate, setInputStartDate] = useState<Date | null>(new Date());
@@ -13,21 +12,6 @@ const PeriodSelector = () => {
);
const dispatch = useDispatch();

const handleStaticPeriod = (
e: React.MouseEvent<HTMLButtonElement>,
month: number,
) => {
e.preventDefault();
const today = new Date();
const year = today.getFullYear();
const monthIndex = today.getMonth();
const day = today.getDate();
const startDate = new Date(year, monthIndex - month, day);
const endDate = new Date(year, monthIndex, day);
dispatch(setStartDate(startDate));
dispatch(setEndDate(endDate));
};

useEffect(() => {
if (inputStartDate! > inputEndDate!) setInputStartDate(inputEndDate);
}, [inputEndDate]);
@@ -44,30 +28,7 @@ const PeriodSelector = () => {
};

return (
<div className="flex justify-center gap-6">
<div className="flex gap-1 items-center">
<p>
<Image src={CalendarIcon} alt="기간별" width={33} height={33} />
</p>
<button
className="btn_extra_small"
onClick={e => handleStaticPeriod(e, 3)}
>
3개월
</button>
<button
className="btn_extra_small"
onClick={e => handleStaticPeriod(e, 6)}
>
6개월
</button>
<button
className="btn_extra_small"
onClick={e => handleStaticPeriod(e, 12)}
>
1년
</button>
</div>
<div className="w-1/2 flex justify-end items-center gap-6">
<div className="flex items-center justify-between gap-1">
<div className="relative">
<DatePickerClient
@@ -76,7 +37,7 @@ const PeriodSelector = () => {
setInputDate={setInputStartDate}
/>
</div>
<p>~</p>
<p className="text-xl font-bold">~</p>
<div className="relative">
<DatePickerClient
inputEndDate={inputEndDate}
@@ -86,12 +47,15 @@ const PeriodSelector = () => {
/>
</div>
</div>
<button
className="flex w-[87px] h-[33px] justify-center items-center bg-black text-white text-sm"
onClick={e => handleSubmitPeriod(e)}
>
조회하기
</button>
<div className="w-1/5 h-10 flex justify-center items-center">
<Button
styleType="primary"
onClick={handleSubmitPeriod}
style="font-bold"
>
조회하기
</Button>
</div>
</div>
);
};
29 changes: 14 additions & 15 deletions src/layouts/sideBar/SideBar.tsx
Original file line number Diff line number Diff line change
@@ -9,21 +9,20 @@ type Props = {

const SideBar = ({ linkArray, activeParams, listStr, ...restProps }: Props) => {
return (
<ul className="flex flex-col w-[257px]">
<h2 className="mb-9 text-[32px] font-bold">MENU</h2>
{linkArray.map((link, index) => (
<li
key={index}
className={`mb-8 ${
activeParams === link ? "font-bold" : ""
} hover:underline`}
>
<Link href={link} className="whitespace-nowrap">
{listStr[index].toUpperCase()}
</Link>
</li>
))}
</ul>
<div className="w-1/6">
<ul className="flex flex-col justify-center items-start gap-2 w-full h-auto border border-lightGray bg-bgWhiteSmoke p-5 rounded-md">
{linkArray.map((link, index) => (
<li
key={index}
className={`w-full cursor-pointer before:content-["•"] before:mr-4 last:border-t last:border-lightGray last:pt-2 font-bold text-lg`}
>
<Link href={link} className="whitespace-nowrap">
{listStr[index].toUpperCase()}
</Link>
</li>
))}
</ul>
</div>
);
};

96 changes: 92 additions & 4 deletions src/redux/slice/cartSlice.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,9 @@ interface ICartState {
cartItems: CartItem[];
cartTotalQuantity: number;
cartTotalAmount: number;
cartCheckedTotalQuantity: number;
cartCheckedTotalAmount: number;
isAllChecked: boolean;
previousURL: string;
}

@@ -19,6 +22,9 @@ const initialState: ICartState = {
: [],
cartTotalQuantity: 0,
cartTotalAmount: 0,
cartCheckedTotalQuantity: 0,
cartCheckedTotalAmount: 0,
isAllChecked: false,
previousURL: "",
};

@@ -46,6 +52,7 @@ const cartSlice = createSlice({
name: action.payload.name,
price: action.payload.price,
cartQuantity: increaseCount,
isChecked: true,
_key: action.payload.id,
};

@@ -119,10 +126,79 @@ const cartSlice = createSlice({

localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
},
CLEAR_CART: state => {
state.cartItems = [];
toast.success("장바구니가 비었습니다.");
REMOVE_CHECKED_ITEMS_FROM_CART: state => {
const newCartItem = state.cartItems.filter(item => !item.isChecked);
state.cartItems = newCartItem;
toast.success("선택한 상품이 장바구니에서 삭제되었습니다.");
localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
},
SELECT_ALL_ITEMS: state => {
state.cartItems.map(item => {
item.isChecked = true;
});
toast.success("모든 상품이 선택/해제 되었습니다."); // 추후 삭제
localStorage.setItem("cartItems", JSON.stringify(state.cartItems));

// isAllchecked 확인
state.isAllChecked = true;
},
UNCHECK_ALL_ITEMS: state => {
state.cartItems.map(item => {
item.isChecked = false;
});
localStorage.setItem("cartItems", JSON.stringify(state.cartItems));

// isAllchecked 확인
state.isAllChecked = false;
},
CALCULATE_CHECKED_ITEMS_SUBTOTAL: state => {
const array: number[] = [];

state.cartItems
.filter(item => item.isChecked)
.map(item => {
const { price, cartQuantity } = item;

const cartItemAmount = price * cartQuantity;
return array.push(cartItemAmount);
});

const totalAmount = array.reduce((a, b) => {
return a + b;
}, 0);

state.cartCheckedTotalAmount = totalAmount;
},
CALCULATE_CHECKED_ITEMS_QUANTITY: state => {
const array: number[] = [];

state.cartItems
.filter(item => item.isChecked)
.map(item => {
const { cartQuantity } = item;

const quantity = cartQuantity;
return array.push(quantity);
});

const totalQuantity = array.reduce((a, b) => {
return a + b;
}, 0);

state.cartCheckedTotalQuantity = totalQuantity;
},
ALTERNATE_CHECKED_ITEMS: (state, action) => {
const productIndex = state.cartItems.findIndex(
item => item.id === action.payload.id,
);

state.cartItems[productIndex].isChecked =
!state.cartItems[productIndex].isChecked;
localStorage.setItem("cartItems", JSON.stringify(state.cartItems));
// isAllchecked 확인
state.isAllChecked = state.cartItems.every(item => item.isChecked)
? true
: false;
},
},
});
@@ -131,16 +207,28 @@ export const {
ADD_TO_CART,
CALCULATE_TOTAL_QUANTITY,
CALCULATE_SUBTOTAL,
CLEAR_CART,
REMOVE_CHECKED_ITEMS_FROM_CART,
SELECT_ALL_ITEMS,
UNCHECK_ALL_ITEMS,
REMOVE_FROM_CART,
DECREASE_CART,
CALCULATE_CHECKED_ITEMS_SUBTOTAL,
CALCULATE_CHECKED_ITEMS_QUANTITY,
SAVE_URL,
ALTERNATE_CHECKED_ITEMS,
} = cartSlice.actions;

export const selectCartItems = (state: RootState) => state.cart.cartItems;
export const selectCheckedCartItems = (state: RootState) =>
state.cart.cartItems.filter(item => item.isChecked);
export const selectCartTotalQuantity = (state: RootState) =>
state.cart.cartTotalQuantity;
export const selectCartTotalAmount = (state: RootState) =>
state.cart.cartTotalAmount;
export const selectCheckedTotalQuantity = (state: RootState) =>
state.cart.cartCheckedTotalQuantity;
export const selectCheckedTotalAmount = (state: RootState) =>
state.cart.cartCheckedTotalAmount;
export const selectAllChecked = (state: RootState) => state.cart.isAllChecked;

export default cartSlice.reducer;
1 change: 1 addition & 0 deletions src/type/cart.ts
Original file line number Diff line number Diff line change
@@ -4,4 +4,5 @@ export interface CartItem {
name: string;
price: number;
cartQuantity: number;
isChecked: boolean;
}
2 changes: 1 addition & 1 deletion src/type/order.ts
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ export type BillingAddress = {
name: string;
phone: string;
userEmail: string;
memo: string;
};

export type ShippingAddress = {
@@ -27,6 +26,7 @@ export type ShippingAddress = {
city: string;
line: string;
phone: string;
memo: string;
};

type OrderStatus =
7 changes: 4 additions & 3 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ module.exports = {
extend: {
colors: {
bgGray: "#f9f9f9",
bgWhiteSmoke: "#f4f4f4",
primaryBlue: "#9bb5b5",
lightGray: "#d9d9d9",
darkGray: "#888",
@@ -37,9 +38,9 @@ module.exports = {
],
},
screens: {
sm: { min: '360px', max: '819px' },
md: { min: '820px', max: '1023px' },
lg: { min: '1080px' },
sm: { min: "360px", max: "819px" },
md: { min: "820px", max: "1023px" },
lg: { min: "1080px" },
},
},
},

0 comments on commit 6662397

Please sign in to comment.