Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
Expand Down
2 changes: 1 addition & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Items from "./pages/Items";
import Items from "./pages/Items/Items.js";
import DefaultLayout from "./layouts/DefaultLayout";
import "pretendard/dist/web/static/pretendard.css";
import "./styles/global.css";
Expand Down
3 changes: 1 addition & 2 deletions src/api/config.js
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export const BASE_URL = "https://panda-market-api.vercel.app";

export const BASE_API_URL = process.env.REACT_APP_BASE_API_URL;
6 changes: 3 additions & 3 deletions src/api/products.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BASE_URL } from "./config";
import { BASE_API_URL } from "./config";

export async function fetchProducts() {
const res = await fetch(`${BASE_URL}/products`);
const res = await fetch(`${BASE_API_URL}/products`);
if (!res.ok) {
throw new Error("상품 데이터를 불러오는데 실패했어요");
}
Expand All @@ -11,7 +11,7 @@ export async function fetchProducts() {

export async function fetchPaginatedProducts({ page = 1, pageSize = 10 } = {}) {
const res = await fetch(
`${BASE_URL}/products?page=${page}&pageSize=${pageSize}`
`${BASE_API_URL}/products?page=${page}&pageSize=${pageSize}`
Copy link
Collaborator

Choose a reason for hiding this comment

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

URLSearchParams 객체 사용해서 여러개의 파라미터를 관리해보는 방법으로 바꾸면, 쿼리 스트링의 파싱, 조작, 인코딩 등의 작업을 간편하게 처리할 수 있으면서도 실수를 줄일 수 있겠죠? :)

아래 아티클 참고해보세요!
참고

);
if (!res.ok) throw new Error("상품 데이터를 불러오는데 실패했습니다.");
const json = await res.json();
Expand Down
43 changes: 0 additions & 43 deletions src/components/AllProducts/AllProducts.js

This file was deleted.

11 changes: 0 additions & 11 deletions src/components/BestProducts/BestProducts.js

This file was deleted.

27 changes: 0 additions & 27 deletions src/hooks/usePaginatedProducts.js

This file was deleted.

41 changes: 0 additions & 41 deletions src/hooks/useResponsiveLimit.js

This file was deleted.

8 changes: 4 additions & 4 deletions src/pages/Items.js → src/pages/Items/Items.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import style from "./Items.module.css"
import BestProducts from "../components/BestProducts/BestProducts";
import AllProducts from "../components/AllProducts/AllProducts";
import { BEST_PRODUCTS_PER_DEVICE,ALL_PRODUCTS_PER_DEVICE } from "../constants/products";
import { TITLE_ALL_PRODUCTS_COMP,TITLE_BEST_PRODUCTS_COMP } from "../constants/titles";
import BestProducts from "./components/BestProducts/BestProducts";
import AllProducts from "./components/AllProducts/AllProducts";
import { BEST_PRODUCTS_PER_DEVICE,ALL_PRODUCTS_PER_DEVICE } from "../../constants/products";
import { TITLE_ALL_PRODUCTS_COMP,TITLE_BEST_PRODUCTS_COMP } from "../../constants/titles";
Copy link
Collaborator

Choose a reason for hiding this comment

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

5,6 번 라인 사이 공백 한칸 띄워줄까요?

function Items() {
return (
<div className={style.container}>
Expand Down
File renamed without changes.
31 changes: 31 additions & 0 deletions src/pages/Items/components/AllProducts/AllProducts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import styles from "./AllProducts.module.css";
import useProductsPagination from "../../hooks/useProductsPagination";
import ProductSection from "../ProductSection/ProductSection";
import Pagination from "../Pagination/Pagination";

function AllProducts({ title, itemsPerDevice }) {
const { products, totalPages, page, changePage, sort, handleSortChange, } =
useProductsPagination(itemsPerDevice);

return (
<>
<ProductSection
title={title}
products={products}
sort={sort}
showSearch={true}
showRegisterButton={true}
onChangeSort={handleSortChange}
/>
<div className={styles.paginationWrapper}>
<Pagination
currentPage={page}
totalPages={totalPages}
onPageChange={changePage}
/>
</div>
</>
);
}

export default AllProducts;
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useEffect, useState } from "react";
import { fetchProducts } from "../api/products";
import { getLimitFromWindowWidth } from "../utils/getLimitFromWindowWidth";
import { fetchProducts } from "../../../../api/products";
import { getLimitFromWindowWidth } from "../../../../utils/getLimitFromWindowWidth";
import ProductSection from "../ProductSection/ProductSection";

export default function useBestProducts(itemsPerDevice) {
function BestProducts({ title, itemsPerDevice }) {
const [bestProducts, setBestProducts] = useState([]);

useEffect(() => {
Expand All @@ -23,5 +24,8 @@ export default function useBestProducts(itemsPerDevice) {

return () => window.removeEventListener("resize", resizeHandler);
}, [itemsPerDevice]);
return bestProducts;

return <ProductSection title={title} products={bestProducts} />;
}

export default BestProducts;
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import styles from "./Pagination.module.css";

const DEFAULT_MAX_PAGE_BUTTONS = 5;

function Pagination({
currentPage,
totalPages,
onPageChange,
maxPageButtons = 5,
maxPageButtons = DEFAULT_MAX_PAGE_BUTTONS,
}) {
const groupStart =
Math.floor((currentPage - 1) / maxPageButtons) * maxPageButtons + 1;
Expand Down Expand Up @@ -58,4 +60,4 @@ function Pagination({
);
}

export default Pagination;
export default Pagination;
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import styles from "./ProductCard.module.css";
import ImageWithFallback from "../ImageWithFallback";
import replaceImg from "../../assets/images/no-image-icon.png";
import ImageWithFallback from "./ImageWithFallback";
import replaceImg from "../../../../assets/images/no-image-icon.png";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHeart as farHeart } from "@fortawesome/free-regular-svg-icons";

function ProductCard({ product}) {

function ProductCard({ product }) {
return (
<div className={styles.card}>
<ImageWithFallback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
}

.dropdown {
z-index: var(--z-dropdown);
position: absolute;
top: calc(100% + 4px);
right: 0;
width: 120px;
/* 리스트 너비는 고정 */
margin: 0;
padding: 0;
background-color: var(--color-white);
Expand Down
49 changes: 49 additions & 0 deletions src/pages/Items/hooks/useProductsPagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// ✅ useProductsPagination.js
import { useState, useEffect, useCallback } from "react";
import { fetchPaginatedProducts } from "../../../api/products";
import useResponsiveLimit from "./useResponsiveLimit";
import usePaginationState from "./usePaginationState";

export default function useProductsPagination(itemsPerDevice) {

const limit = useResponsiveLimit(itemsPerDevice);
const [page, changePage] = usePaginationState(limit);
const [sort, setSort] = useState("latest");
const [products, setProducts] = useState([]);
const [totalPages, setTotalPages] = useState(1);

const handleSortChange = useCallback(
(newSort) => {
setSort(newSort);
changePage(1);
},
[changePage]
);

useEffect(() => {
const load = async () => {
const res = await fetchPaginatedProducts({ page, pageSize: limit });
let data = res.list || [];

if (sort === "likes") {
data.sort((a, b) => b.favoriteCount - a.favoriteCount);
} else {
data.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
}

setProducts(data);
setTotalPages(Math.ceil(res.totalCount / limit));
};

if (page && limit) load();
}, [page, limit, sort]);

return {
products,
totalPages,
page,
changePage,
sort,
handleSortChange,
};
}
44 changes: 44 additions & 0 deletions src/pages/Items/hooks/useResponsiveLimit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect, useState } from "react";
import { getLimitFromWindowWidth } from "../../../utils/getLimitFromWindowWidth";

export default function useResponsiveLimit(itemsPerDevice) {
const DEFAULT_RESPONSIVE_LIMIT = itemsPerDevice?.tablet || 6;
// 아주 최소 연산으로만 초기limit값 처리
const [limit, setLimit] = useState(() => {
// 아주 최소 연산만 처리
// SSR 대응
return typeof window === "undefined"
? DEFAULT_RESPONSIVE_LIMIT // 아주 보수적인 default
: null;
});

useEffect(() => {
//window 접근 못 할 경우 return;
if (typeof window === "undefined") return;

let timeoutId;

const update = () => {
// 디바운싱 추가
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
const newLimit = getLimitFromWindowWidth(
itemsPerDevice.desktop,
itemsPerDevice.tablet,
itemsPerDevice.mobile
);
setLimit((prev) => (prev !== newLimit ? newLimit : prev));
}, 120); // 리사이즈 멈춘 뒤 120ms 후에만 실행
};

update(); // mount 시 1회 실행
window.addEventListener("resize", update);

return () => {
clearTimeout(timeoutId);
window.removeEventListener("resize", update);
};
}, [itemsPerDevice]);

return limit;
}
Loading