Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 73 additions & 7 deletions src/app/checkout/page.tsx
Original file line number Diff line number Diff line change
@@ -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<CheckoutItem[]>([]);
// 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 (
<div className="p-6 max-w-3xl mx-auto bg-white rounded shadow mt-6">
<h1 className="text-2xl font-bold mb-4">✅ 결제가 완료되었습니다!</h1>
<h1 className="text-2xl font-bold mb-4">결제가 완료되었습니다!</h1>

{/* 3.1. 결제하기 구현 */}
<div></div>
{items.length === 0 ? (
<p className="text-gray-500 text-center py-8">결제된 아이템이 없습니다</p>
) : (
<div>
<h2 className="text-lg font-semibold mb-4">주문 내역</h2>
<ul className="space-y-4 mb-6">
{items.map((item, index) => (
<li key={index} className="flex justify-between items-center border-b pb-2">
<div>
<p className="font-medium" dangerouslySetInnerHTML={{ __html: item.title }}></p>
<p className="text-sm text-gray-500">수량: {item.quantity}개</p>
</div>
<div className="text-right">
<p className="font-bold text-red-500">
{(Number(item.lprice) * item.quantity).toLocaleString()}원
</p>
<p className="text-xs text-gray-400">
개당 {Number(item.lprice).toLocaleString()}원
</p>
</div>
</li>
))}
</ul>

<div className="border-t pt-4">
<div className="flex justify-between items-center text-xl font-bold">
<span>총 결제 금액:</span>
<span className="text-red-500">{totalAmount.toLocaleString()}원</span>
</div>
</div>
</div>
)}

{/* 3.2. 홈으로 가기 버튼 구현 */}
<div className="flex justify-center mt-8">
<Link
href="/"
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
홈 화면으로 돌아가기
</Link>
</div>
</div>
);
}
5 changes: 4 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -27,7 +28,9 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<UserProvider>
{children}
</UserProvider>
</body>
</html>
);
Expand Down
37 changes: 32 additions & 5 deletions src/app/mypage/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col items-center min-h-screen bg-gray-50">
{/* 1.2. Header Component를 재활용하여 Mypage Header 표기 (title: 마이페이지) */}
<p>마이페이지</p>
{/* Mypage 정보를 UserContext 활용하여 표시 (이름, 아이디, 나이, 핸드폰번호 모두 포함) */}
<Header title="마이페이지" />

<div className="w-full max-w-md p-6 mt-8 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6">회원 정보</h2>
<div className="space-y-4">
<div>
<p className="text-gray-600">이름 : {user.name}</p>
</div>
<div>
<p className="text-gray-600">아이디 :{user.userId}</p>
</div>
<div>
<p className="text-gray-600">나이 :{user.age}</p>
</div>
<div>
<p className="text-gray-600">전화번호 :{user.phoneNumber} </p>
</div>
</div>
</div>

{/* 1.3. 홈으로 가기 버튼 구현(Link or Router 활용) */}
<Link
href="/"
className="mt-8 px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
홈으로 가기
</Link>
</div>
);
}
5 changes: 1 addition & 4 deletions src/app/search/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { SearchProvider } from "../../context/SearchContext";
import { UserProvider } from "../../context/UserContext";

export default function SearchLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<UserProvider>
<SearchProvider>{children}</SearchProvider>
</UserProvider>
<SearchProvider>{children}</SearchProvider>
);
}
9 changes: 1 addition & 8 deletions src/app/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="flex justify-center">
<div className="w-[80%]">
Expand Down
17 changes: 13 additions & 4 deletions src/component/search/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>(null);

// 2.2. SearchInput 컴포넌트가 최초 렌더링 될 때, input tag에 포커스 되는 기능
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);

// 검색 기능
const search = async () => {
Expand All @@ -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<HTMLInputElement>) => {
setQuery(e.target.value);
};

return (
<div className="flex justify-center items-center gap-2 mt-4">
<input
ref={inputRef}
type="text"
value={query}
onChange={handleInputChange}
Expand Down
23 changes: 22 additions & 1 deletion src/component/shopping/CartList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";
import { ProductItem } from "@/types/Product";
import { useRouter } from "next/navigation";

interface Props {
cart: { [productId: string]: number };
Expand All @@ -8,6 +9,8 @@ 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);
Expand All @@ -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 (
<div className="p-4 bg-white rounded shadow mt-6">
Expand Down
19 changes: 17 additions & 2 deletions src/component/shopping/ProductCart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand All @@ -20,15 +26,24 @@ 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 (
<div className="p-10">
{/* 상품 리스트 */}
<ProductList items={items} onAddToCart={handleAddToCart} />
{/* 장바구니 */}
{/* 2.1. 조건부 카트 보이기: 카트에 담긴 상품이 없으면 카트가 보이지 않고, 카트에 담긴 물건이 있으면 카트가 보인다 */}
<CartList cart={cart} products={items} onRemove={handleRemoveFromCart} />
{showCart && (
<CartList cart={cart} products={items} onRemove={handleRemoveFromCart} />
)}
</div>
);
}
14 changes: 11 additions & 3 deletions src/context/UserContext.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -22,7 +24,13 @@ export const UserContext = createContext<UserContextType | undefined>(

// 2. Provider 생성
export const UserProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User>({ name: "" });
const [user, setUser] = useState<User>({
name: "202102656 신재호",
userId: "JaehoShin",
age: 25,
phoneNumber: "010-1234-5678"
});

return (
<UserContext.Provider value={{ user, setUser }}>
{children}
Expand Down