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
8 changes: 8 additions & 0 deletions .idea/.gitignore

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

11 changes: 11 additions & 0 deletions .idea/cnu-next.iml

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

4 changes: 4 additions & 0 deletions .idea/encodings.xml

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

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

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

9 changes: 9 additions & 0 deletions .idea/misc.xml

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

8 changes: 8 additions & 0 deletions .idea/modules.xml

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

6 changes: 6 additions & 0 deletions .idea/vcs.xml

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

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.

88 changes: 79 additions & 9 deletions src/app/checkout/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// CheckoutPage
import { useState } from "react";
"use client";
import {useEffect, useState} from "react";
import { ProductItem } from "@/types/Product";
import Link from "next/link";

interface CheckoutItem {
product: ProductItem;
Expand All @@ -10,12 +12,80 @@ interface CheckoutItem {
export default function CheckoutPage() {
const [items, setItems] = useState<CheckoutItem[]>([]);
// 3.1. 결제하기 구현
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>
);

useEffect(() => {
try {
const storedItems = localStorage.getItem("checkoutItems");
console.log("Stored items:", storedItems); // 디버깅 로그
if (storedItems) {
const parsedItems: CheckoutItem[] = JSON.parse(storedItems);
// 유효한 항목만 필터링
const validItems = parsedItems.filter(
(item) =>
item?.product &&
typeof item.product === "object" &&
"productId" in item.product &&
"title" in item.product &&
"lprice" in item.product &&
typeof item.quantity === "number"
);
console.log("Parsed valid items:", validItems); // 디버깅 로그
setItems(validItems);
localStorage.removeItem("checkoutItems");
}
} catch (error) {
console.error("Error loading checkout items:", error);
setItems([]);
}
}, []);

const total = items.reduce(
(sum, item) =>
sum +
(item.product?.lprice ? Number(item.product.lprice) * item.quantity : 0),
0
);
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>
{items.length === 0 ? (
<p className="text-gray-500">결제된 아이템이 없습니다.</p>
) : (
<div>
<ul className="space-y-4">
{items.map((item) => (
<li
key={item.product.productId}
className="flex justify-between items-center border-b pb-2"
>
<div>
<p
dangerouslySetInnerHTML={{ __html: item.product.title }}
></p>
<p className="text-sm text-gray-500">
수량: {item.quantity}
</p>
</div>
<p className="text-red-500 font-bold">
{(Number(item.product.lprice) * item.quantity).toLocaleString()}
</p>
</li>
))}
</ul>
<div className="text-right font-bold text-lg mt-4">
총 금액: {total.toLocaleString()}원
</div>
</div>
)}
{/* 3.2: 홈 화면으로 돌아가기 */}
<div className="mt-6 text-center">
<Link href="/">
<button className="px-4 py-2 bg-blue-600 text-white rounded-md shadow hover:bg-blue-700">
홈 화면으로 돌아가기
</button>
</Link>
</div>
</div>
);
}
6 changes: 5 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import React from "react";
import {UserProvider} from "@/context/UserContext";

const geistSans = Geist({
variable: "--font-geist-sans",
Expand All @@ -27,7 +29,9 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<UserProvider>
{children}
</UserProvider>
</body>
</html>
);
Expand Down
60 changes: 47 additions & 13 deletions src/app/mypage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
// 과제 1: 마이페이지 구현
export default function MyPage() {
// 1.1. UserContext를 활용한 Mypage 구현 (UserContext에 아이디(userId: string), 나이(age: number), 핸드폰번호(phoneNumber: string) 추가)

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 활용하여 표시 (이름, 아이디, 나이, 핸드폰번호 모두 포함) */}
"use client";
import { useUser } from "@/context/UserContext";
import Header from "@/component/layout/Header";
import Link from "next/link";
import {useEffect} from "react";

{/* 1.3. 홈으로 가기 버튼 구현(Link or Router 활용) */}
</div>
);
}
export default function MyPage() {
const { user, setUser } = useUser();
useEffect(() => {
setUser({
userId: "202003130",
name: "송수민",
age: 25,
phoneNumber: "010-1234-5678",
});
}, [setUser]);
return (
<div className="flex flex-col items-center min-h-screen bg-gray-50">
<Header title="마이페이지" />
<div className="bg-white rounded-lg shadow-md p-6 mt-8 w-full max-w-md">
<h2 className="text-xl font-semibold mb-4 text-center text-gray-800">사용자 정보</h2>
<div className="space-y-3">
<div className="flex justify-between items-center border-b pb-2">
<span className="text-gray-600 font-medium">이름:</span>
<span className="text-gray-800">{user.name || "설정되지 않음"}</span>
</div>
<div className="flex justify-between items-center border-b pb-2">
<span className="text-gray-600 font-medium">아이디:</span>
<span className="text-gray-800">{user.userId || "설정되지 않음"}</span>
</div>
<div className="flex justify-between items-center border-b pb-2">
<span className="text-gray-600 font-medium">나이:</span>
<span className="text-gray-800">{user.age > 0 ? `${user.age}세` : "설정되지 않음"}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-600 font-medium">핸드폰번호:</span>
<span className="text-gray-800">{user.phoneNumber || "설정되지 않음"}</span>
</div>
</div>
</div>
<Link href="/">
<button className="bg-blue-500 text-white px-6 py-3 rounded-lg mt-6 hover:bg-blue-600 transition duration-200">
홈으로 가기
</button>
</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: "202003130송수민" });
}, []);

return (
Expand Down
15 changes: 12 additions & 3 deletions src/component/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* 실습 1 */
/* 실습 4 useContext */
import { useUser } from "@/context/UserContext";
import Link from "next/link";

interface HeaderProps {
title: string;
Expand All @@ -13,9 +14,9 @@ const Header = ({ title }: HeaderProps) => {

return (
<div className="header flex justify-between items-center px-4 py-2">
<h1 className="text-xl font-bold">{title}</h1>
<h1 className="text-xl px-36 font-bold">{title}</h1>

<div className="flex items-center gap-2">
<div className="flex items-center px-36 gap-2">
{/* public directory에 profile.svg 파일 넣은 후, image tag에 경로 지정 */}
<img
src="/profile.svg"
Expand All @@ -27,5 +28,13 @@ const Header = ({ title }: HeaderProps) => {
</div>
);
};

export const HomeButton = () => {
return (
<Link href="/">
<button className="bg-blue-500 text-white px-4 py-2 rounded">
홈으로 가기
</button>
</Link>
);
};
export default Header;
47 changes: 30 additions & 17 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 { useEffect, useRef } from "react";

export default function SearchInput() {
const { query, setQuery, setResult } = useSearch();
const inputRef = useRef<HTMLInputElement>(null);

// 2.2: 최초 렌더링 시 input 포커스
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);

// 검색 기능
const search = async () => {
Expand All @@ -19,25 +28,29 @@ export default function SearchInput() {
};

// 2.2. SearchInput 컴포넌트가 최초 렌더링 될 때, input tag에 포커스 되는 기능
const handleInputChange = () => {};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
};


// 과제 1-2-3: 페이지 최초 렌더링 시, input에 포커스 되는 기능 (useRef)
// 과제 1-2-3: 페이지 최초 렌더링 시, input에 포커스 되는 기능 (useRef)

return (
<div className="flex justify-center items-center gap-2 mt-4">
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="검색어를 입력하세요"
className="w-full max-w-md px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<button
onClick={search}
className="px-4 py-2 bg-blue-600 text-white rounded-md shadow hover:bg-blue-700 transition-colors"
>
검색
</button>
</div>
<div className="flex justify-center items-center gap-2 mt-4">
<input
ref={inputRef}
type="text"
value={query}
onChange={handleInputChange}
placeholder="검색어를 입력하세요"
className="w-full max-w-md px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<button
onClick={search}
className="px-4 py-2 bg-blue-600 text-white rounded-md shadow hover:bg-blue-700 transition-colors"
>
검색
</button>
</div>
);
}
Loading