diff --git a/src/pages/Items.module.css b/src/pages/Items/Items.module.css
similarity index 100%
rename from src/pages/Items.module.css
rename to src/pages/Items/Items.module.css
diff --git a/src/pages/Items/components/AllProducts/AllProducts.js b/src/pages/Items/components/AllProducts/AllProducts.js
new file mode 100644
index 00000000..475cb504
--- /dev/null
+++ b/src/pages/Items/components/AllProducts/AllProducts.js
@@ -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 (
+ <>
+
+
+ >
+ );
+}
+
+export default AllProducts;
diff --git a/src/components/AllProducts/AllProducts.module.css b/src/pages/Items/components/AllProducts/AllProducts.module.css
similarity index 100%
rename from src/components/AllProducts/AllProducts.module.css
rename to src/pages/Items/components/AllProducts/AllProducts.module.css
diff --git a/src/hooks/useBestProducts.js b/src/pages/Items/components/BestProducts/BestProducts.js
similarity index 66%
rename from src/hooks/useBestProducts.js
rename to src/pages/Items/components/BestProducts/BestProducts.js
index c606dc5c..1a7de7c3 100644
--- a/src/hooks/useBestProducts.js
+++ b/src/pages/Items/components/BestProducts/BestProducts.js
@@ -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(() => {
@@ -23,5 +24,8 @@ export default function useBestProducts(itemsPerDevice) {
return () => window.removeEventListener("resize", resizeHandler);
}, [itemsPerDevice]);
- return bestProducts;
+
+ return
;
}
+
+export default BestProducts;
diff --git a/src/components/Pagination/Pagination.js b/src/pages/Items/components/Pagination/Pagination.js
similarity index 92%
rename from src/components/Pagination/Pagination.js
rename to src/pages/Items/components/Pagination/Pagination.js
index 74a21be6..b7b8c4f9 100644
--- a/src/components/Pagination/Pagination.js
+++ b/src/pages/Items/components/Pagination/Pagination.js
@@ -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;
@@ -58,4 +60,4 @@ function Pagination({
);
}
-export default Pagination;
\ No newline at end of file
+export default Pagination;
diff --git a/src/components/Pagination/Pagination.module.css b/src/pages/Items/components/Pagination/Pagination.module.css
similarity index 100%
rename from src/components/Pagination/Pagination.module.css
rename to src/pages/Items/components/Pagination/Pagination.module.css
diff --git a/src/components/ImageWithFallback.js b/src/pages/Items/components/ProductCard/ImageWithFallback.js
similarity index 100%
rename from src/components/ImageWithFallback.js
rename to src/pages/Items/components/ProductCard/ImageWithFallback.js
diff --git a/src/components/ProductCard/ProductCard.js b/src/pages/Items/components/ProductCard/ProductCard.js
similarity index 83%
rename from src/components/ProductCard/ProductCard.js
rename to src/pages/Items/components/ProductCard/ProductCard.js
index 771aa8ad..41b6a437 100644
--- a/src/components/ProductCard/ProductCard.js
+++ b/src/pages/Items/components/ProductCard/ProductCard.js
@@ -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 (
{
+ 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,
+ };
+}
\ No newline at end of file
diff --git a/src/pages/Items/hooks/useResponsiveLimit.js b/src/pages/Items/hooks/useResponsiveLimit.js
new file mode 100644
index 00000000..8023abf5
--- /dev/null
+++ b/src/pages/Items/hooks/useResponsiveLimit.js
@@ -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;
+}
diff --git a/src/styles/variables.css b/src/styles/variables.css
index 20f011c8..794d6d48 100644
--- a/src/styles/variables.css
+++ b/src/styles/variables.css
@@ -140,6 +140,28 @@
--spacing-xl: 32px;
--spacing-2xl: 40px;
+ /* ===================== */
+ /* z-index */
+ /* ===================== */
+ /* Base layers */
+ --z-base: 0;
+ --z-above-base: 1;
+
+ /* UI Components */
+ --z-dropdown: 1000;
+ --z-modal: 2000;
+ --z-toast: 3000;
+ --z-tooltip: 4000;
+
+ /* Fixed elements */
+ --z-header: 100;
+ --z-footer: 100;
+ --z-sidebar: 200;
+
+ /* Overlay elements */
+ --z-overlay: 1500;
+ --z-overlay-above: 2500;
+
/* ======================= */
/* (반응형) 모바일 환경 변수 설정 */
/* ======================= */