From aba6a07549d5471a41e07b62de7ab51fd3b1b8d1 Mon Sep 17 00:00:00 2001 From: 95x8x9 Date: Mon, 16 Jun 2025 14:11:43 +0900 Subject: [PATCH] =?UTF-8?q?202202546=20=EA=B8=88=EC=86=8C=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 27 ++++++------ package.json | 15 ++++--- src/app/checkout/page.tsx | 59 +++++++++++++++++++++++++- src/app/layout.tsx | 3 +- src/app/mypage/page.tsx | 30 ++++++++++++- src/app/search/page.tsx | 7 ++- src/component/search/SearchInput.tsx | 9 +++- src/component/shopping/CartList.tsx | 13 +++++- src/component/shopping/ProductCart.tsx | 12 +++++- src/context/UserContext.tsx | 11 ++++- 10 files changed, 157 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22c80ad..1f113a4 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", @@ -15,7 +15,8 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", - "@types/node": "^20", + "@types/estree": "^1.0.8", + "@types/node": "^20.19.0", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", @@ -1275,9 +1276,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -1296,13 +1297,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.57", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", - "integrity": "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==", + "version": "20.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz", + "integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, "node_modules/@types/react": { @@ -5940,9 +5941,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 9bbc988..8c6c675 100644 --- a/package.json +++ b/package.json @@ -9,19 +9,20 @@ "lint": "next lint" }, "dependencies": { + "next": "15.3.3", "react": "^19.0.0", - "react-dom": "^19.0.0", - "next": "15.3.3" + "react-dom": "^19.0.0" }, "devDependencies": { - "typescript": "^5", - "@types/node": "^20", + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/estree": "^1.0.8", + "@types/node": "^20.19.0", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4", "eslint": "^9", "eslint-config-next": "15.3.3", - "@eslint/eslintrc": "^3" + "tailwindcss": "^4", + "typescript": "^5" } } diff --git a/src/app/checkout/page.tsx b/src/app/checkout/page.tsx index 0d40153..d2cc5bb 100644 --- a/src/app/checkout/page.tsx +++ b/src/app/checkout/page.tsx @@ -1,6 +1,8 @@ // CheckoutPage -import { useState } from "react"; +"use client"; import { ProductItem } from "@/types/Product"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; interface CheckoutItem { product: ProductItem; @@ -9,13 +11,66 @@ interface CheckoutItem { // 과제 3 export default function CheckoutPage() { const [items, setItems] = useState([]); + const [isEmpty, setIsEmpty] = useState(false); + const router = useRouter(); + + useEffect(() => { + const data = localStorage.getItem("checkout"); + if (data) { + const parsed = JSON.parse(data); + if (parsed.length === 0) setIsEmpty(true); + else setItems(parsed); + } else { + setIsEmpty(true); + } + localStorage.removeItem("checkout"); + }, []); + + const getTotalPrice = () => { + return items.reduce( + (sum: number, item:CheckoutItem) => sum + Number(item.product.lprice) * item.quantity, + 0 + ); + }; // 3.1. 결제하기 구현 return (

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

{/* 3.1. 결제하기 구현 */} -
+ {isEmpty ? ( +

결제된 아이템이 없습니다.

+ ) : ( +
+ {items.map((item:CheckoutItem, index:number) => ( +
+
+

{item.product.title}

+

+ {item.product.lprice.toLocaleString()}원 × {item.quantity} +

+
+
+ {(Number(item.product.lprice) * item.quantity).toLocaleString()}원 +
+
+ ))} +
+ 총 결제금액: {getTotalPrice().toLocaleString()}원 +
+
+ )} {/* 3.2. 홈으로 가기 버튼 구현 */} +
+ +
); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..3725a53 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,7 @@ export default function RootLayout({ - {children} + {children} ); diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx index 93b3ba9..583f423 100644 --- a/src/app/mypage/page.tsx +++ b/src/app/mypage/page.tsx @@ -1,14 +1,42 @@ +"use client"; + +import Header from "@/component/layout/Header"; +import { useUser } from "@/context/UserContext"; +import Link from "next/link"; + // 과제 1: 마이페이지 구현 export default function MyPage() { // 1.1. UserContext를 활용한 Mypage 구현 (UserContext에 아이디(userId: string), 나이(age: number), 핸드폰번호(phoneNumber: string) 추가) + + const { user } = useUser(); + const { userId, age, phoneNumber } = user; return (
{/* 1.2. Header Component를 재활용하여 Mypage Header 표기 (title: 마이페이지) */} -

마이페이지

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

+ 아이디: {userId} +

+

+ 나이: {age} +

+

+ 핸드폰번호: {phoneNumber} +

+
{/* 1.3. 홈으로 가기 버튼 구현(Link or Router 활용) */} +
+ + 홈으로 가기 + +
); } diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index c3b6212..ed1dd9d 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -15,7 +15,12 @@ export default function SearchHome() { // 페이지 최초 렌더링 될 때, setUser로 이름 설정 useEffect(() => { // 학번 + 이름 형태로 작성 (ex. 2025***** 내이름 ) - setUser({ name: "" }); + setUser({ + name: "202202546 금소현", + age: 23, + userId: "95x8x9", + phoneNumber: "010-1234-5678" + }); }, []); return ( diff --git a/src/component/search/SearchInput.tsx b/src/component/search/SearchInput.tsx index aea7294..4377a4c 100644 --- a/src/component/search/SearchInput.tsx +++ b/src/component/search/SearchInput.tsx @@ -1,9 +1,16 @@ "use client"; import { useSearch } from "@/context/SearchContext"; +import { useEffect, useRef } from "react"; export default function SearchInput() { const { query, setQuery, setResult } = useSearch(); + const inputRef = useRef(null); + + useEffect(() => { + inputRef.current?.focus(); + }, []); + // 검색 기능 const search = async () => { try { @@ -19,7 +26,7 @@ export default function SearchInput() { }; // 2.2. SearchInput 컴포넌트가 최초 렌더링 될 때, input tag에 포커스 되는 기능 - const handleInputChange = () => {}; + const handleInputChange = (e: React.ChangeEvent) => {setQuery(e.target.value);}; // 과제 1-2-3: 페이지 최초 렌더링 시, input에 포커스 되는 기능 (useRef) diff --git a/src/component/shopping/CartList.tsx b/src/component/shopping/CartList.tsx index adc8745..a94cf5e 100644 --- a/src/component/shopping/CartList.tsx +++ b/src/component/shopping/CartList.tsx @@ -1,5 +1,6 @@ "use client"; import { ProductItem } from "@/types/Product"; +import { useRouter } from "next/navigation"; interface Props { cart: { [productId: string]: number }; @@ -8,6 +9,7 @@ interface Props { } export default function CartList({ cart, products, onRemove }: Props) { + const router = useRouter(); const cartItems = Object.entries(cart) .map(([id, quantity]) => { const product = products.find((p) => p.productId === id); @@ -21,7 +23,16 @@ export default function CartList({ cart, products, onRemove }: Props) { ); // 2.4 결제하기: "결제하기" 버튼을 클릭하면, 현재 장바구니에 담긴 상품을 확인해 **localStorage**에 저장 후, 결제완료(/checkout) 페이지로 이동한다. - const handleCheckout = () => {}; + const handleCheckout = () => { + const checkoutData = cartItems.map(({ productId, title, lprice, quantity }) => ({ + product: { productId, title, lprice }, + quantity, + })); + + localStorage.setItem("checkout", JSON.stringify(checkoutData)); + router.push("/checkout"); + + }; return (
diff --git a/src/component/shopping/ProductCart.tsx b/src/component/shopping/ProductCart.tsx index a66c2b3..a75f59e 100644 --- a/src/component/shopping/ProductCart.tsx +++ b/src/component/shopping/ProductCart.tsx @@ -8,6 +8,11 @@ export default function ProductCart({ items }: { items: ProductItem[] }) { const [cart, setCart] = useState<{ [id: string]: number }>({}); // {"88159814281" : 1} const [showCart, setShowCart] = useState(false); // 과제 2.1 + useEffect(() => { + const hasItems = Object.keys(cart).length > 0; + setShowCart(hasItems); + }, [cart]); + // 카트에 담기 const handleAddToCart = (item: ProductItem, quantity: number) => { setCart((prev) => ({ @@ -20,7 +25,12 @@ export default function ProductCart({ items }: { items: ProductItem[] }) { }; /* 과제 2-3: Cart 아이템 지우기 */ - const handleRemoveFromCart = () => {}; + const handleRemoveFromCart = (productId: string) => { + const updatedCart = { ...cart }; + delete updatedCart[productId]; + setCart(updatedCart); + localStorage.removeItem(productId); + }; return (
diff --git a/src/context/UserContext.tsx b/src/context/UserContext.tsx index e5d3f14..45c3c7b 100644 --- a/src/context/UserContext.tsx +++ b/src/context/UserContext.tsx @@ -6,6 +6,10 @@ import { createContext, ReactNode, useContext, useState } from "react"; // User interface User { name: string; + age: number; + userId: string; + phoneNumber: string; + // age: number // 추가하고 싶은 속성들 ... } @@ -22,7 +26,12 @@ export const UserContext = createContext( // 2. Provider 생성 export const UserProvider = ({ children }: { children: ReactNode }) => { - const [user, setUser] = useState({ name: "" }); + const [user, setUser] = useState({ + name: "202202546 금소현", + age: 23, + userId: "95x8x9", + phoneNumber: "010-1234-5678", + }); return ( {children}