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
78 changes: 66 additions & 12 deletions package-lock.json

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

19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@
"lint": "next lint"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.3.3"
"next": "15.3.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.2"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.3.3",
"@eslint/eslintrc": "^3"
"@types/node": "^20",
"@types/react": "^19.1.7",
"@types/react-dom": "^19.1.6",
"tailwindcss": "^4",
"typescript": "^5"
}
}
92 changes: 83 additions & 9 deletions src/app/checkout/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,95 @@
// CheckoutPage
import { useState } from "react";
import { ProductItem } from "@/types/Product";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";

interface CheckoutItem {
product: ProductItem;
quantity: number;
productId: string;
title: string;
lprice: string;
}
// 과제 3

interface StoredCheckoutData {
items: CheckoutItem[];
total: number;
}

// 과제 3
export default function CheckoutPage() {
const [items, setItems] = useState<CheckoutItem[]>([]);
// 3.1. 결제하기 구현
const [checkoutData, setCheckoutData] = useState<StoredCheckoutData | null>(null);
const router = useRouter();

useEffect(() => {
try {
const savedData = localStorage.getItem("checkoutData");

if (savedData) {
const parsedData: StoredCheckoutData = JSON.parse(savedData);
setCheckoutData(parsedData);

localStorage.removeItem("checkoutData");
console.log("localStorage에서 결제 정보가 삭제되었습니다.");
} else {
setCheckoutData(null);
console.log("localStorage에 결제 정보가 없습니다.");
}
} catch (error) {
console.error("결제 정보를 불러오는 중 오류 발생:", error);
setCheckoutData(null);
}
}, []);

if (!checkoutData || !checkoutData.items || checkoutData.items.length === 0) {
return (
<div className="p-6 max-w-3xl mx-auto bg-white rounded shadow mt-6 text-center">
<h1 className="text-2xl font-bold mb-4">✅ 결제 완료</h1>
<p className="text-gray-600 mb-6">결제된 아이템이 없습니다.</p>
<button
onClick={() => router.push("/")}
className="px-6 py-2 bg-blue-600 text-white rounded-md shadow hover:bg-blue-700 transition-colors"
>
홈으로 가기
</button>
</div>
);
}

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>
{/* 3.1. 결제하기 구현 */}
<div></div>
{/* 3.2. 홈으로 가기 버튼 구현 */}

<div className="mb-6">
<h2 className="text-xl font-semibold mb-3">주문 상품 목록</h2>
<ul className="border rounded p-4 space-y-3">
{checkoutData.items.map((item) => (
<li
key={item.productId}
className="flex justify-between items-center border-b pb-2 last:border-b-0 last:pb-0"
>
<div>
<p className="font-medium" dangerouslySetInnerHTML={{ __html: item.title }}></p>
<p className="text-sm text-gray-600">수량: {item.quantity}</p>
</div>
<p className="text-lg font-bold text-blue-600">
{(Number(item.lprice) * item.quantity).toLocaleString()}원
</p>
</li>
))}
</ul>
</div>

<div className="text-right font-bold text-xl mt-4 pt-4 border-t">
총 결제 금액: {checkoutData.total.toLocaleString()}원
</div>

<div className="flex justify-center mt-8">
<button
onClick={() => router.push("/")}
className="px-6 py-2 bg-blue-600 text-white rounded-md shadow hover:bg-blue-700 transition-colors"
>
홈으로 가기
</button>
</div>
</div>
);
}
11 changes: 6 additions & 5 deletions 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 @@ -23,11 +24,11 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<html lang="ko">
<body>
<UserProvider>
{children}
</UserProvider>
</body>
</html>
);
Expand Down
27 changes: 26 additions & 1 deletion src/app/mypage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
'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>
<Header title="마이페이지" />

{/* Mypage 정보를 UserContext 활용하여 표시 (이름, 아이디, 나이, 핸드폰번호 모두 포함) */}
<div className="mt-8 bg-white rounded-xl shadow-md p-6 w-full max-w-md">
<p className="text-lg mb-2">
<strong>이름:</strong> {user.name}
</p>
<p className="text-lg mb-2">
<strong>아이디:</strong> {user.userId}
</p>
<p className="text-lg mb-2">
<strong>나이:</strong> {user.age}세
</p>
<p className="text-lg mb-2">
<strong>핸드폰번호:</strong> {user.phoneNumber}
</p>
</div>

{/* 1.3. 홈으로 가기 버튼 구현(Link or Router 활용) */}
<Link href="/" className="mt-6 text-blue-500 underline">
홈으로 가기
</Link>
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function SearchHome() {
// 페이지 최초 렌더링 될 때, setUser로 이름 설정
useEffect(() => {
// 학번 + 이름 형태로 작성 (ex. 2025***** 내이름 )
setUser({ name: "" });
setUser({ name: "202102718 최승윤" });
}, []);

return (
Expand Down
15 changes: 13 additions & 2 deletions src/component/search/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"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);

// 검색 기능
const search = async () => {
Expand All @@ -18,14 +21,22 @@ export default function SearchInput() {
}
};

// 2.2. SearchInput 컴포넌트가 최초 렌더링 될 때, input tag에 포커스 되는 기능
const handleInputChange = () => {};
// 2.2. 입력 값 변경 시 query 상태 업데이트
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
};

// 과제 1-2-3: 페이지 최초 렌더링 시, input에 포커스 되는 기능 (useRef)
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);

return (
<div className="flex justify-center items-center gap-2 mt-4">
<input
ref={inputRef}
type="text"
value={query}
onChange={handleInputChange}
Expand Down
Loading