Skip to content
Merged
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
19 changes: 17 additions & 2 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
/* config options here */
reactCompiler: true,
images: {
domains: [
"example.com",
"sprint-fe-project.s3.ap-northeast-2.amazonaws.com",
"st.depositphotos.com",
"i.pinimg.com",
"upload.wikimedia.org",
"cdn.wccftech.com",
"encrypted-tbn0.gstatic.com",
"image.hanatour.com",
"cdn.choicenews.co.kr",
"health.chosun.com",
"via.placeholder.com",
"i.imgur.com",
"images.samsung.com",
],
},
};

export default nextConfig;
45 changes: 45 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "eslint"
},
"dependencies": {
"@tanstack/react-query": "^5.90.7",
"clsx": "^2.1.1",
"dayjs": "^1.11.19",
"next": "16.0.1",
Expand All @@ -18,6 +19,7 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@tanstack/eslint-plugin-query": "^5.91.2",
"autoprefixer": "^10.4.21",
"babel-plugin-react-compiler": "1.0.0",
"eslint": "^9",
Expand Down
47 changes: 39 additions & 8 deletions src/Components/common/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
"use client";
import Image from "next/image";
import PandaLogo from "@/public/images/Group19.png";
import Link from "next/link";
import { useAuth } from "@/providers/AuthProvider";
import { useRouter } from "next/navigation";

export default function Header() {
const { user, logout, isInitialized } = useAuth();
const router = useRouter();

if (!isInitialized) return null;

return (
<header className="flex justify-between items-center w-full h-[70px] px-25 border-b border-[#DFDFDF] bg-white ">
<div className="flex justify-center items-center ">
Expand All @@ -16,25 +24,48 @@ export default function Header() {
</Link>
<nav className="fex justify-center items-center">
<Link
href="/page2"
href="/articles"
className="w-[109px] h-[69px] p-[24px_16px_24px_15px] text-center font-pretended text-[18px] text-[#3692FF] font-bold cursor-pointer"
>
자유게시판
</Link>
<Link
href="/page3"
href="/items"
className="w-[109px] h-[69px] p-[24px_23px_24px_23px] text-center font-pretended text-[18px] font-bold cursor-pointer"
>
중고게시판
</Link>
</nav>
</div>
<Link
href="/login"
className=" bg-[#3692FF] text-white px-[23px] py-3 my-3.5 rounded-lg gap-2.5"
>
로그인
</Link>

<div className="flex items-center gap-4 pr-6">
{user ? (
<div className="flex items-center justify-center gap-2">
<Image
src={user.image}
width={40}
height={40}
alt="프로필 이미지"
/>
<span className="text-gray-600 text-[18px] font-normal">
{user.nickname} 님
</span>
<button
onClick={logout}
className="flex items-center justify-center bg-gray-200 text-gray-800 text-[16px] w-20 h-10 rounded-lg "
>
로그아웃
</button>
</div>
) : (
<button
onClick={() => router.push("/login")}
className=" flex justify-center items-center bg-[#3692FF] text-[16px] text-white px-[23px] py-3 rounded-lg gap-2.5 leading-[26px]"
>
로그인
</button>
)}
</div>
</header>
);
}
39 changes: 39 additions & 0 deletions src/Components/ui/ProductsList/Pagination.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";
import { IoIosArrowBack } from "react-icons/io";
import { IoIosArrowForward } from "react-icons/io";

export default function Pagination({ page, totalPages, onChange }) {
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);

return (
<div className="flex justify-center items-center gap-1 mt-[43px] mb-[140px]">
<button
onClick={() => onChange(Math.max(page - 1, 1))}
disabled={page === 1}
className="w-10 h-10 shrink-0 border-2 border-gray-200 rounded-full flex items-center justify-center "
>
<IoIosArrowBack className="w-4 h-4" />
</button>

{pages.map((num) => (
<button
key={num}
onClick={() => onChange(num)}
className={`w-10 h-10 border border-gray-200 rounded-full flex items-center justify-center ${
page === num ? "bg-[#2F80ED] text-white" : "bg-[#ffffff] "
}`}
>
{num}
</button>
))}

<button
onClick={() => onChange(Math.min(page + 1, totalPages))}
disabled={page >= totalPages}
className="w-10 h-10 shrink-0 border-2 border-gray-200 rounded-full flex items-center justify-center "
>
<IoIosArrowForward />
</button>
</div>
);
}
47 changes: 47 additions & 0 deletions src/Components/ui/ProductsList/ProductsBest.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { useEffect, useState } from "react";
import { getProducts } from "@/lib/services/products";
import ProductsCard from "./ProductsCard";

export default function ProductsBest() {
const [bestProducts, setBestProducts] = useState([]);
const [loading, setLoading] = useState(false);

useEffect(() => {
async function fetchBest() {
try {
setLoading(true);
const data = await getProducts({
orderBy: "favorite",
page: 1,
pageSize: 4,
});
setBestProducts(data.list);
} catch (error) {
console.error("베스트 상품 불러오기 실패:", error);
} finally {
setLoading(false);
}
}
fetchBest();
}, []);

return (
<section className="flex flex-col gap-6 mt-6 ">
<h2 className="text-[20px] font-bold text-[#111827] leading-8">
베스트 상품
</h2>

{loading ? (
<p className="text-gray-500 text-center">로딩 중...</p>
) : (
<ul className="grid grid-cols-4 gap-6">
{bestProducts.map((item) => (
<ProductsCard key={item.id} {...item} />
))}
</ul>
)}
</section>
);
}
57 changes: 57 additions & 0 deletions src/Components/ui/ProductsList/ProductsCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";
import Image from "next/image";
import React, { useState } from "react";
import heart from "@/public/images/ic_heart.png";
import defaultImg from "@/public/images/eximg.png";
import Link from "next/link";

export default function ProductsCard({
id,
name,
description,
images,
favoriteCount,
price,
}) {
console.log("상품 ID:", id, "이미지 데이터:", images);
const [currentImg, setCurrentImg] = useState(images?.[0] || defaultImg);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☕ Thinking...

이 경우... 페이지네이션을 통해 상품 목록이 싹 바뀌었을 때 컴포넌트를 새로 불러오는 게 아니라면, 이미지가 변하지 않을 수 있어요.

이런 경우엔 조금 번거롭더라도, useEffect()로 images의 변경여부를 검사하고 set 해 주는 로직을 추가 해 두는 것도 안전하지 않을까 생각이 드네요.


return (
<li>
<Link href={`/items/${id}`}>
<div className="mt-4">
<Image
src={currentImg || defaultImg}
alt={name}
width={220}
height={220}
priority
className="mb-4 rounded-lg object-cover object-center w-[220px] h-[220px]"
onError={() => setCurrentImg(defaultImg)}
// unoptimized Image 도메인 최적화 제한 해제 !
/>
<div className="flex flex-col gap-1.5">
<p className="text-[14px] font-medium text-[#1F2937] leading-6 ">
{description}
</p>
<p className="text-[16px] font-bold leading-[26px] text-[#1F2937] ">
{price ? Number(price).toLocaleString() : 0}원
</p>
<div className="flex items-center gap-1">
<Image
src={heart}
width={16}
height={16}
alt="하트이모티콘"
className="w-4 h-4 border-gray-600"
/>
<p className="text-[12px] text-[#4B5563] leading-[18px] font-medium">
{favoriteCount}
</p>
</div>
</div>
</div>
</Link>
</li>
);
}
Loading