Skip to content

Conversation

@leohan6540
Copy link
Collaborator

@leohan6540 leohan6540 commented Sep 19, 2025

체크리스트 [기본]

  • 상품 등록 페이지 주소는 "/additem" 입니다.
  • 페이지 주소가 "/additem" 일때 상단네비게이션바의 '중고마켓' 버튼의 색상은 "3692FF"입니다.
  • 상품 이미지는 최대 한개 업로드가 가능합니다.
  • 각 input의 placeholder 값을 정확히 입력해주세요.
  • 이미지를 제외하고 input 에 모든 값을 입력하면 '등록' 버튼이 활성화 됩니다.
  • API를 통한 상품 등록은 추후 미션에서 적용합니다.

체크리스트 [심화]

  • 상품 등록
  • 이미지 안의 X 버튼을 누르면 이미지가 삭제됩니다.
  • 추가된 태그 안의 X 버튼을 누르면 해당 태그는 삭제됩니다.
스프린트미션 6 스프린트미션 6-2

@leohan6540 leohan6540 requested a review from kiJu2 September 19, 2025 06:20
@leohan6540 leohan6540 added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Sep 19, 2025
@leohan6540 leohan6540 changed the title React 한장희 sprint5 React 한장희 sprint6 Sep 19, 2025
@leohan6540 leohan6540 changed the title React 한장희 sprint6 [한장희] sprint6 Sep 19, 2025
@kiJu2 kiJu2 changed the base branch from main to React-한장희 September 20, 2025 07:24
@kiJu2
Copy link
Collaborator

kiJu2 commented Sep 20, 2025

스프리트 미션 하시느라 수고 많으셨어요.
학습에 도움 되실 수 있게 꼼꼼히 리뷰 하도록 해보겠습니다. 😊

Comment on lines +10 to +14
<ProductGrid
products={products}
gridSize={responsiveValues.bestPicContainerSize}
picSize={responsiveValues.bestPicSize}
/>
Copy link
Collaborator

Choose a reason for hiding this comment

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

(참고만 해도 됩니다 😉)ProductGridgridSize, picSize가 드릴링이 되는 것 같아서 한 번 리팩토링 해봤습니다 !

diff --git a/vite-project/src/components/BestProductSection/BestProducts.jsx b/vite-project/src/components/BestProductSection/BestProducts.jsx
index 82f5c22..468fddc 100644
--- a/vite-project/src/components/BestProductSection/BestProducts.jsx
+++ b/vite-project/src/components/BestProductSection/BestProducts.jsx
@@ -1,17 +1,30 @@
-import { useResponsivePage } from "../../hooks/useResponsivePage";
 import ProductGrid from "../common/ProductGrid";
+import ProductCard from "../common/ProductCard";
+import { useResponsivePage } from "../../hooks/useResponsivePage";
+import { DEVICE_STYLES } from "../common/DeviceClasses";

 const BestProducts = ({ products }) => {
-  const responsiveValues = useResponsivePage();
+  const { device } = useResponsivePage();
+
+  const containerClass = DEVICE_STYLES[device].best.container;
+  const pictureClass = DEVICE_STYLES[device].best.picture;

   return (
     <div className="flex flex-col gap-3 ">
       <h2 className="text-xl font-bold ">베스트 상품</h2>
-      <ProductGrid
-        products={products}
-        gridSize={responsiveValues.bestPicContainerSize}
-        picSize={responsiveValues.bestPicSize}
-      />
+      <ProductGrid>
+        {products.map((product) => (
+          <ProductCard
+            key={product.id}
+            className={containerClass}
+            name={product.name}
+            price={product.price}
+            favoriteCount={product.favoriteCount}
+            images={product.images}
+            picSizeClass={pictureClass}
+          />
+        ))}
+      </ProductGrid>
     </div>
   );
 };
diff --git a/vite-project/src/components/ProductListSection/Container.jsx b/vite-project/src/components/ProductListSection/Container.jsx
index 0d02c77..905a802 100644
--- a/vite-project/src/components/ProductListSection/Container.jsx
+++ b/vite-project/src/components/ProductListSection/Container.jsx
@@ -2,12 +2,14 @@ import { useState } from "react";
 import useGetProducts from "../../hooks/useGetProducts";
 import usePagination from "../../hooks/usePagination";
 import ProductGrid from "../common/ProductGrid";
+import ProductCard from "../common/ProductCard";
 import Pagination from "../ProductListSection/Pagination";
 import searchIcon from "../../assets/searchIcon.svg";
 import Dropdown from "./Dropdown";
-import { useResponsivePage } from "../../hooks/useResponsivePage";
 import { Link } from "react-router-dom";
 import { SORT_OPTIONS } from "../../constant/SORT_OPTIONS";
+import { useResponsivePage } from "../../hooks/useResponsivePage";
+import { DEVICE_STYLES } from "../common/DeviceClasses";

 const Container = ({ pageSize, isMobile }) => {
   const [orderBy, setOrderBy] = useState(SORT_OPTIONS[0].value);
@@ -17,7 +19,10 @@ const Container = ({ pageSize, isMobile }) => {
     orderBy,
     currentPage,
   });
-  const ResponsiveValues = useResponsivePage();
+  const { device } = useResponsivePage();
+
+  const containerClass = DEVICE_STYLES[device].normal.container;
+  const pictureClass = DEVICE_STYLES[device].normal.picture;

   const handleSelect = (val) => {
     console.log("$$", val);
@@ -92,11 +97,19 @@ const Container = ({ pageSize, isMobile }) => {
         </div>
       )}

-      <ProductGrid
-        products={products}
-        picSize={ResponsiveValues.normalPicSize}
-        gridSize={ResponsiveValues.normalPicContainerSize}
-      />
+      <ProductGrid>
+        {products.map((product) => (
+          <ProductCard
+            key={product.id}
+            className={containerClass}
+            name={product.name}
+            price={product.price}
+            favoriteCount={product.favoriteCount}
+            images={product.images}
+            picSizeClass={pictureClass}
+          />
+        ))}
+      </ProductGrid>
       <Pagination
         currentPage={currentPage}
         setCurrentPage={setCurrentPage}
diff --git a/vite-project/src/components/common/ProductGrid.jsx b/vite-project/src/components/common/ProductGrid.jsx
index 3b5835d..871a009 100644
--- a/vite-project/src/components/common/ProductGrid.jsx
+++ b/vite-project/src/components/common/ProductGrid.jsx
@@ -1,30 +1,7 @@
-import ProductCard from "./ProductCard";
-import { GRID_SIZES } from "./ProductGridClasses";
-import { PIC_SIZES } from "./ProductGridClasses";
-
-const ProductGrid = ({
-  gridSize = "grid224",
-  picSize = "normalPicSize",
-  products,
-}) => {
-  const sizeClass = GRID_SIZES[gridSize] || "";
-  const picSizeClass = PIC_SIZES[picSize] || "";
-
+const ProductGrid = ({ children }) => {
   return (
     <div className="flex flex-wrap justify-center gap-5">
-      {products.map((product) => {
-        return (
-          <ProductCard
-            key={product.id}
-            className={`${sizeClass}`}
-            name={product.name}
-            price={product.price}
-            favoriteCount={product.favoriteCount}
-            images={product.images}
-            picSizeClass={picSizeClass}
-          />
-        );
-      })}
+      {children}
     </div>
   );
 };
diff --git a/vite-project/src/components/common/ProductGridClasses.js b/vite-project/src/components/common/ProductGridClasses.js
deleted file mode 100644
index 8ad09e3..0000000
--- a/vite-project/src/components/common/ProductGridClasses.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export const GRID_SIZES = {
-  grid168: "w-[168px]",
-  grid224: "w-56 h-72",
-  grid228: "w-72 h-[378px]",
-  grid343: "w-[343px] h-[434px]",
-};
-
-export const PIC_SIZES = {
-  normalPicSize: "w-56 h-56 ",
-  normalPicMobile: "w-[168px] h-[168px]",
-  bestPicSize: "w-[282px] h-[282px]",
-  bestPicTabletMobile: "w-[343px] h-[343px]",
-};
diff --git a/vite-project/src/hooks/useResponsivePage.js b/vite-project/src/hooks/useResponsivePage.js
index edf831d..d4ca7ed 100644
--- a/vite-project/src/hooks/useResponsivePage.js
+++ b/vite-project/src/hooks/useResponsivePage.js
@@ -17,6 +17,6 @@ export const useResponsivePage = () => {
     return () => window.removeEventListener("resize", onResize);
   }, []);

-  const values = useMemo(() => CONFIG[device], [device]);
+  const values = useMemo(() => ({ ...CONFIG[device], device }), [device]);
   return values;
 };

핵심은 ProductGrid의 역할이 프롭스를 전달만 해주는 것 같아서요 !
일관적인 스타일 용도로만 사용하도록 바꿔봤습니다 !

참고만 하시고 사용하지 않으셔도 됩니다 😉

Comment on lines +3 to +40
const Input = ({
inputTypeStyle = "basic",
as,
type,
id,
imgUrl,
onChange,
onClick,
onKeyDown,
...props
}) => {
const styleClass = INPUT_TYPE_STYLE[inputTypeStyle] || "";
const Component = as || "input";

return (
<div>
{type === "file" ? (
<div className="flex gap-8">
<div className="flex flex-col gap-4">
<label className={`${styleClass} bg-gray-100 cursor-pointer`}>
<img src={props.src} alt={props.alt} />
<Component
accept="image/*"
type={type}
{...props}
id={id}
className="hidden"
onChange={onChange}
/>
</label>
{imgUrl && (
<p className="text-red-400">
*이미지 등록은 최대 1개까지 가능합니다
</p>
)}
</div>
{imgUrl && (
<div className="relative">
Copy link
Collaborator

Choose a reason for hiding this comment

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

오호. input 외에 다른 컴포넌트로 사용될 수 있도록 해놓으셨군요.

확장성을 고려하신 것 같군요? 장희님을 보면 참 도전적이고 학습에 열려있는 수강생이란 느낌이 많이 들어요 ! 리스펙합니다 🥺

다만, 하나의 컴포넌트에서 많은 역할을 수행하려다 보니 조금 복잡해진 느낌이 없잖아 있군요 !

다음과 같이 분리해보는 것도 고려해보실 수 있을 것 같습니다 😊:

const InputText = ({ styleClass, ...props }) => (
  <input className={`${styleClass} bg-gray-100`} {...props} />
);

const InputFile = ({ styleClass, imgUrl, onDelete, ...props }) => (
  <div className="flex gap-8">
    <div className="flex flex-col gap-4">
      <label className={`${styleClass} bg-gray-100 cursor-pointer`}>
        <input type="file" className="hidden" {...props} />
      </label>
      {imgUrl && <p className="text-red-400">*이미지 등록은 최대 1개</p>}
    </div>
    {imgUrl && (
      <div className="relative">
        <img src={imgUrl} alt="preview" />
        <img src="/ic_X.svg" alt="삭제" onClick={onDelete} />
      </div>
    )}
  </div>
);

export default function Input({ type, ...props }) {
  const styleClass = INPUT_TYPE_STYLE[props.inputTypeStyle] || "";
  if (type === "file") return <InputFile styleClass={styleClass} {...props} />;
  return <InputText styleClass={styleClass} type={type} {...props} />;
}

return (
<div>
<Header />
<main className="max-w-screen-xl px-4 mt-6 md:px-6 md:mx-auto">
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 ~ 시맨틱 태그도 잊지 않았군요 👍

Comment on lines +41 to +51
const handleHashTagEnter = (e) => {
if (e.key === "Enter") {
e.preventDefault();
const newTag = e.target.value.trim();
if (newTag && !hashtags.includes(newTag)) {
setHashtags((prev) => [...prev, newTag]);
e.target.value = "";
handleInputChange();
}
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

사용성까지 고려한 꼼꼼함.. ✨✨

엔터까지 고려하셨군요 ! 🥺

@kiJu2
Copy link
Collaborator

kiJu2 commented Sep 20, 2025

크으.. 장희님 수고하셨습니다.
만약 제가 과외 선생님이라면 장희님 같은 학생을 만나고싶을거예요.
정말 열정적이고 귀가 트여있는게 느껴집니다 🥺

그리고.. 번아웃.. 이시라면서요 ㄷㄷ...
누구보다 개발을 즐기시는 것 같은데요..?

무튼, 이번 미션 정말 수고 많으셨습니다 장희님 💪💪

@kiJu2 kiJu2 merged commit 70e9188 into codeit-bootcamp-frontend:React-한장희 Sep 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants