From 6f00a32ce5642a59b59aad2b95b041a88a8ff72b Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sun, 15 Jun 2025 17:47:06 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat=20:=20=EA=B3=BC=EC=A0=9C1=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 4 ++-- src/app/layout.tsx | 5 ++++- src/app/mypage/page.tsx | 37 ++++++++++++++++++++++++++++++++----- src/app/search/layout.tsx | 5 +---- src/app/search/page.tsx | 9 +-------- src/context/UserContext.tsx | 14 +++++++++++--- 6 files changed, 51 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22c80ad..0b88957 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "cnu-next-week02", + "name": "cnu-next", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "cnu-next-week02", + "name": "cnu-next", "version": "0.1.0", "dependencies": { "next": "15.3.3", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..b770786 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import { UserProvider } from "@/context/UserContext"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -27,7 +28,9 @@ export default function RootLayout({ - {children} + + {children} + ); diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx index 93b3ba9..f74c9cb 100644 --- a/src/app/mypage/page.tsx +++ b/src/app/mypage/page.tsx @@ -1,14 +1,41 @@ +"use client"; + +import { useUser } from "@/context/UserContext"; +import Header from "@/component/layout/Header"; +import Link from "next/link"; + // 과제 1: 마이페이지 구현 export default function MyPage() { - // 1.1. UserContext를 활용한 Mypage 구현 (UserContext에 아이디(userId: string), 나이(age: number), 핸드폰번호(phoneNumber: string) 추가) + const { user } = useUser(); return (
- {/* 1.2. Header Component를 재활용하여 Mypage Header 표기 (title: 마이페이지) */} -

마이페이지

- {/* Mypage 정보를 UserContext 활용하여 표시 (이름, 아이디, 나이, 핸드폰번호 모두 포함) */} +
+ +
+

회원 정보

+
+
+

이름 : {user.name}

+
+
+

아이디 :{user.userId}

+
+
+

나이 :{user.age}

+
+
+

전화번호 :{user.phoneNumber}

+
+
+
- {/* 1.3. 홈으로 가기 버튼 구현(Link or Router 활용) */} + + 홈으로 가기 +
); } diff --git a/src/app/search/layout.tsx b/src/app/search/layout.tsx index 9b61099..ca5b5c8 100644 --- a/src/app/search/layout.tsx +++ b/src/app/search/layout.tsx @@ -1,5 +1,4 @@ import { SearchProvider } from "../../context/SearchContext"; -import { UserProvider } from "../../context/UserContext"; export default function SearchLayout({ children, @@ -7,8 +6,6 @@ export default function SearchLayout({ children: React.ReactNode; }) { return ( - - {children} - + {children} ); } diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index c3b6212..5261821 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -5,19 +5,12 @@ import Footer from "../../component/layout/Footer"; import SearchInput from "../../component/search/SearchInput"; import ProductCart from "../../component/shopping/ProductCart"; import { useUser } from "../../context/UserContext"; -import { useEffect } from "react"; import { useSearch } from "../../context/SearchContext"; export default function SearchHome() { - const { user, setUser } = useUser(); + const { user } = useUser(); const { result } = useSearch(); - // 페이지 최초 렌더링 될 때, setUser로 이름 설정 - useEffect(() => { - // 학번 + 이름 형태로 작성 (ex. 2025***** 내이름 ) - setUser({ name: "" }); - }, []); - return (
diff --git a/src/context/UserContext.tsx b/src/context/UserContext.tsx index e5d3f14..bda2c21 100644 --- a/src/context/UserContext.tsx +++ b/src/context/UserContext.tsx @@ -1,12 +1,14 @@ "use client"; -import { createContext, ReactNode, useContext, useState } from "react"; +import { createContext, ReactNode, useContext, useState, useEffect } from "react"; // 과제 1.1 UserContext 구현 // User interface User { name: string; - // age: number + userId: string; + age: number; + phoneNumber: string; // 추가하고 싶은 속성들 ... } // UserContextType @@ -22,7 +24,13 @@ export const UserContext = createContext( // 2. Provider 생성 export const UserProvider = ({ children }: { children: ReactNode }) => { - const [user, setUser] = useState({ name: "" }); + const [user, setUser] = useState({ + name: "202102656 신재호", + userId: "JaehoShin", + age: 25, + phoneNumber: "010-1234-5678" + }); + return ( {children} From 65f7e5656b41cfc1d25729261a5a62ac1419ca2d Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sun, 15 Jun 2025 18:49:56 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat=20:=20=EA=B3=BC=EC=A0=9C=202,3?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/checkout/page.tsx | 80 +++++++++++++++++++++++--- src/component/search/SearchInput.tsx | 17 ++++-- src/component/shopping/CartList.tsx | 23 +++++++- src/component/shopping/ProductCart.tsx | 19 +++++- 4 files changed, 125 insertions(+), 14 deletions(-) diff --git a/src/app/checkout/page.tsx b/src/app/checkout/page.tsx index 0d40153..222b81f 100644 --- a/src/app/checkout/page.tsx +++ b/src/app/checkout/page.tsx @@ -1,21 +1,87 @@ -// CheckoutPage -import { useState } from "react"; -import { ProductItem } from "@/types/Product"; +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; interface CheckoutItem { - product: ProductItem; + productId: string; + title: string; + lprice: string; quantity: number; } + // 과제 3 export default function CheckoutPage() { const [items, setItems] = useState([]); - // 3.1. 결제하기 구현 + const [totalAmount, setTotalAmount] = useState(0); + + // 3.1. 결제 완료 페이지 구현 + useEffect(() => { + // localStorage에서 결제 아이템들 가져오기 + const checkoutData = localStorage.getItem("checkoutItems"); + + if (checkoutData) { + const parsedItems: CheckoutItem[] = JSON.parse(checkoutData); + setItems(parsedItems); + + // 총 금액 계산 + const total = parsedItems.reduce((sum, item) => { + return sum + (Number(item.lprice) * item.quantity); + }, 0); + setTotalAmount(total); + + // 결제 완료 후 localStorage에서 삭제 + localStorage.removeItem("checkoutItems"); + } + }, []); + return (
-

✅ 결제가 완료되었습니다!

+

결제가 완료되었습니다!

+ {/* 3.1. 결제하기 구현 */} -
+ {items.length === 0 ? ( +

결제된 아이템이 없습니다

+ ) : ( +
+

주문 내역

+
    + {items.map((item, index) => ( +
  • +
    +

    +

    수량: {item.quantity}개

    +
    +
    +

    + {(Number(item.lprice) * item.quantity).toLocaleString()}원 +

    +

    + 개당 {Number(item.lprice).toLocaleString()}원 +

    +
    +
  • + ))} +
+ +
+
+ 총 결제 금액: + {totalAmount.toLocaleString()}원 +
+
+
+ )} + {/* 3.2. 홈으로 가기 버튼 구현 */} +
+ + 홈 화면으로 돌아가기 + +
); } diff --git a/src/component/search/SearchInput.tsx b/src/component/search/SearchInput.tsx index aea7294..e7ea5da 100644 --- a/src/component/search/SearchInput.tsx +++ b/src/component/search/SearchInput.tsx @@ -1,8 +1,17 @@ "use client"; import { useSearch } from "@/context/SearchContext"; +import { useRef, useEffect } from "react"; export default function SearchInput() { const { query, setQuery, setResult } = useSearch(); + const inputRef = useRef(null); + + // 2.2. SearchInput 컴포넌트가 최초 렌더링 될 때, input tag에 포커스 되는 기능 + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); // 검색 기능 const search = async () => { @@ -18,14 +27,14 @@ export default function SearchInput() { } }; - // 2.2. SearchInput 컴포넌트가 최초 렌더링 될 때, input tag에 포커스 되는 기능 - const handleInputChange = () => {}; - - // 과제 1-2-3: 페이지 최초 렌더링 시, input에 포커스 되는 기능 (useRef) + const handleInputChange = (e: React.ChangeEvent) => { + setQuery(e.target.value); + }; return (
{ const product = products.find((p) => p.productId === id); @@ -21,7 +24,25 @@ export default function CartList({ cart, products, onRemove }: Props) { ); // 2.4 결제하기: "결제하기" 버튼을 클릭하면, 현재 장바구니에 담긴 상품을 확인해 **localStorage**에 저장 후, 결제완료(/checkout) 페이지로 이동한다. - const handleCheckout = () => {}; + const handleCheckout = () => { + if (cartItems.length === 0) { + alert("장바구니가 비어있습니다."); + return; + } + + // 장바구니에 담긴 아이템들을 localStorage에 저장 + const checkoutData = cartItems.map(item => ({ + productId: item.productId, + title: item.title, + lprice: item.lprice, + quantity: item.quantity + })); + + localStorage.setItem("checkoutItems", JSON.stringify(checkoutData)); + + // 결제완료 페이지로 이동 + router.push("/checkout"); + }; return (
diff --git a/src/component/shopping/ProductCart.tsx b/src/component/shopping/ProductCart.tsx index a66c2b3..d2f3eae 100644 --- a/src/component/shopping/ProductCart.tsx +++ b/src/component/shopping/ProductCart.tsx @@ -8,6 +8,12 @@ export default function ProductCart({ items }: { items: ProductItem[] }) { const [cart, setCart] = useState<{ [id: string]: number }>({}); // {"88159814281" : 1} const [showCart, setShowCart] = useState(false); // 과제 2.1 + // 2.1. 장바구니에 아이템이 있을 때만 장바구니 영역 보이기 + useEffect(() => { + const hasItems = Object.keys(cart).length > 0; + setShowCart(hasItems); + }, [cart]); + // 카트에 담기 const handleAddToCart = (item: ProductItem, quantity: number) => { setCart((prev) => ({ @@ -20,7 +26,14 @@ export default function ProductCart({ items }: { items: ProductItem[] }) { }; /* 과제 2-3: Cart 아이템 지우기 */ - const handleRemoveFromCart = () => {}; + const handleRemoveFromCart = (productId: string) => { + setCart((prev) => { + const newCart = { ...prev }; + delete newCart[productId]; + return newCart; + }); + localStorage.removeItem(productId); + }; return (
@@ -28,7 +41,9 @@ export default function ProductCart({ items }: { items: ProductItem[] }) { {/* 장바구니 */} {/* 2.1. 조건부 카트 보이기: 카트에 담긴 상품이 없으면 카트가 보이지 않고, 카트에 담긴 물건이 있으면 카트가 보인다 */} - + {showCart && ( + + )}
); }