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
8 changes: 6 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<meta property="or:url" content="https://p-anda-market.netlify.app/" />
<meta property="og:title" content="판다마켓" />
<meta property="og:description" content="일상의 모든 물건을 거래해보세요" />
<meta property="og:image" content="img/img_preview.png" />
<title>판다마켓</title>
<link rel="icon" href="/favicon.png" />
</head>
<body>
<div id="root"></div>
Expand Down
Binary file added public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/vite.svg

This file was deleted.

4 changes: 4 additions & 0 deletions src/assets/icons/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/x.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 13 additions & 2 deletions src/components/Button.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
function Button({ children, onClick }) {
function Button({ children, type, onClick, disabled }) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
버튼의 type은 일반적으로 버튼 태그의 type: "button" | "reset" | "submit" 을 의미합니다.
type은 중요한 속성이니 type prop가 "button" | "reset" | "submit"을 받게 하시고, style의 종류는 variant와 같은 이름으로 변경하시는 것을 추천드립니다.

const buttonStyles = {
login: "w-128 h-48 rounded-lg bg-blue100 text-lg text-white font-semibold",
shopping:
"w-357 h-56 rounded-[40px] bg-blue100 text-2lg tablet:text-xl text-white font-semibold",
additem:
"py-8 px-24 rounded-lg bg-blue100 text-lg text-white font-semibold",
upload:
"w-74 h-42 rounded-lg bg-blue100 text-lg text-white font-semibold disabled:bg-gray400 disabled:cursor-not-allowed",
};

Comment on lines +2 to +11
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
모든 버튼 스타일에서 bg-blue100 text-white font-semibold가 중복되는 것 같아요~
(disabled:bg-gray400 disabled:cursor-not-allowed도 다 공통 적용되어야 하지 않을까싶습니다)

이런 경우 중복되는 친구들을 아래처럼 작성해주시면 코드가 더 명확해집니다.

Suggested change
const buttonStyles = {
login: "w-128 h-48 rounded-lg bg-blue100 text-lg text-white font-semibold",
shopping:
"w-357 h-56 rounded-[40px] bg-blue100 text-2lg tablet:text-xl text-white font-semibold",
additem:
"py-8 px-24 rounded-lg bg-blue100 text-lg text-white font-semibold",
upload:
"w-74 h-42 rounded-lg bg-blue100 text-lg text-white font-semibold disabled:bg-gray400 disabled:cursor-not-allowed",
};
const buttonStyles = {
login: "w-128 h-48 rounded-lg text-lg ",
shopping: "w-357 h-56 rounded-[40px] text-2lg tablet:text-xl",
additem: "py-8 px-24 rounded-lg text-lg",
upload: "w-74 h-42 rounded-lg text-lg",
};
// 사용시
// <button className={`cursor-pointer bg-blue100 text-white font-semibold disabled:bg-gray400 disabled:cursor-not-allowed ${buttonStyles[type]}`}

return (
<button
className="py-8 px-24 bg-blue100 text-white text-lg font-semibold rounded-lg cursor-pointer"
className={`${buttonStyles[type]} cursor-pointer`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
Expand Down
5 changes: 2 additions & 3 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import logoText from "../assets/images/logo_text.svg";
import profile from "../assets/icons/profile.svg";

function Header() {
const active = location.pathname === "/items" || "/additems";
return (
<div className="w-full bg-white sticky top-0 border border-[#dfdfdf]">
<div className="max-w-1520 p-9 flex items-center justify-between m-auto gap-16 tablet:px-24 tablet:gap-24">
Expand All @@ -16,9 +17,7 @@ function Header() {
<span className="cursor-pointer">자유게시판</span>
</Link>
<Link to="/items">
<span
className={`${location.pathname === "/items" ? "text-blue100" : ""} cursor-pointer`}
>
<span className={`${active ? "text-blue100" : ""} cursor-pointer`}>
중고마켓
</span>
</Link>
Expand Down
31 changes: 31 additions & 0 deletions src/components/InputField.jsx
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
input 과 textarea 두개의 컴포넌트로 나누시는 게 더 명확하고 좋을 것 같아요.
또한 버튼에서 코멘트 드린것과 같이 type은 input의 타입을 넘기게 하는것이 좋으니 더욱 분리하시면 좋을 것 같아요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
function inputField({ type, label, value, onChange, onKeyDown, placeholder }) {
if (type === "input") {
return (
<div className="flex flex-col justify-center gap-16">
<div className="text-2lg font-bold">{label}</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
label 태그로 작성하시고 input이랑 연결해주세요~

<input
className="bg-gray100 .placeholder-text-gray400 text-lg font-regular rounded-xl py-16 px-24"
value={value}
onChange={onChange}
onKeyDown={onKeyDown}
placeholder={placeholder}
/>
</div>
);
} else if (type === "textarea") {
return (
<div className="flex flex-col justify-center gap-16">
<div className="text-2lg font-bold">{label}</div>
<textarea
className="h-282 bg-gray100 .placeholder-text-gray400 text-lg font-regular rounded-xl py-16 px-24"
value={value}
onChange={onChange}
onKeyDown={onKeyDown}
placeholder={placeholder}
></textarea>
</div>
);
}
}

export default inputField;
8 changes: 6 additions & 2 deletions src/components/ItemControls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ function ItemControl({ isMobile, options }) {
<div className="text-xl text-gray900 font-bold flex-1">
전체상품
</div>
<Button onClick={handleButtonClick}>상품 등록하기</Button>
<Button type="additem" onClick={handleButtonClick}>
상품 등록하기
</Button>
</div>
<div className="flex justify-between items-center gap-8 relative">
<div className="flex justify-between items-center gap-4 flex-1">
Expand All @@ -44,7 +46,9 @@ function ItemControl({ isMobile, options }) {
placeholder="검색할 상품을 입력해주세요"
/>
</div>
<Button onClick={handleButtonClick}>상품 등록하기</Button>
<Button type="additem" onClick={handleButtonClick}>
상품 등록하기
</Button>
<Dropdown options={options} isMobile={isMobile} />
</div>
</>
Expand Down
145 changes: 144 additions & 1 deletion src/pages/AddItemPage.jsx
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
저는 관련있는 것들을 잘 모아두는 것이 가독성을 높여준다고 생각합니다. 지금의 경우 아이템을 생성하는 폼과 관련된 state 들을 각각의 useState로 관리하고 있습니다.
이런 경우 formValue 와 같은 하나의 state로 관리하시거나, useReducer를 이용해보시면 더 가독성 좋게 코드를 작성하실 수 있습니다.

https://ko.react.dev/learn/choosing-the-state-structure
https://ko.react.dev/learn/extracting-state-logic-into-a-reducer

이는 알고만 계시다가 추후 typescript 적용하실때 다시 생각해보시면 도움이 되실 것 같아요.

Original file line number Diff line number Diff line change
@@ -1,5 +1,148 @@
import { useState, useRef } from "react";
import Button from "../components/Button";
import Header from "../components/Header";
import InputField from "../components/InputField";
import plus from "../assets/icons/plus.svg";
import x from "../assets/icons/x.svg";
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
CloseIcon 과 같은 명확한 이름을 추천드려요!


function AddItemPage() {
return <div>hi</div>;
const imgRef = useRef(null);
const [imgPreview, setImgPreview] = useState("");
const [itemName, setItemName] = useState("");
const [itemDetail, setItemDetail] = useState("");
const [price, setPrice] = useState("");
const [tagInput, setTagInput] = useState("");
const [tags, setTags] = useState([]);
const isFormValid = itemName && itemDetail && price && tags.length > 0;

const handleUploadImg = (e) => {
const file = e.target.files[0];
if (file) {
const previewUrl = URL.createObjectURL(file);
setImgPreview(previewUrl);
}
};
const handleDeleteImg = () => {
setImgPreview("");
if (imgRef.current) {
imgRef.current.value = null;
}
};

const handleEnterTag = (e) => {
if (e.key === "Enter" && !e.nativeEvent.isComposing) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 칭찬
isComposing 쓰신거 잘하셨어요!

e.preventDefault();
const newTag = tagInput.trim();
if (newTag && !tags.includes(newTag)) {
setTags([...tags, newTag]);
}
setTagInput("");
Comment on lines +36 to +39
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
이 로직에 따르면 중복되는 내용의 태그가 입력시 조용히 무시되게 되니 사용자가 알 수 있는 피드백을 제공해주시면 좋을 것 같아요.

}
};
const handleDeleteTag = (index) => {
const newTags = [...tags];
newTags.splice(index, 1);
setTags(newTags);
};

return (
<div>
<Header />
<div className="max-w-1200 m-auto flex flex-col gap-24 pt-24 px-15 pb-52">
<div className="flex justify-between items-center">
<div className="text-xl font-bold">상품 등록하기</div>
<Button type="upload" disabled={!isFormValid}>
등록
</Button>
</div>
<div className="flex flex-col justify-center gap-16">
<div className="text-2lg font-bold">상품 이미지</div>
<div className="flex gap-10 pc:gap-24">
<label className="flex flex-col justify-center items-center gap-12 size-168 pc:size-282 bg-gray100 text-gray400 text-lg font-regular rounded-xl cursor-pointer">
<img className="size-48" src={plus} />
이미지 등록
<input
className="hidden"
type="file"
onChange={handleUploadImg}
ref={imgRef}
disabled={imgPreview}
/>
Comment on lines +62 to +70
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
이미지 파일만 받을 수 있도록 구현하시면 더 좋을 것 같아요~
참고로 accept 속성을 이용하시고, handleUploadImg 함수 내부에서 추가적으로 확장자를 한번 더 검사하셔야합니다.
accpet 속성은 제안일 뿐 제한이 아니기 때문에 유저가 accept의 명시된 확장자 이외의 파일도 올릴 수 있습니다~

</label>
{imgPreview ? (
<>
<img
className="size-168 pc:size-282 rounded-xl"
src={imgPreview}
alt="미리보기"
/>
<img
className="relative right-40 pc:right-52 top-8 size-22 cursor-pointer"
src={x}
onClick={handleDeleteImg}
/>
</>
) : (
""
)}
</div>
{imgPreview ? (
<div className="text-red text-lg font-regular">
*이미지 등록은 최대 1개까지 가능합니다.
</div>
) : (
""
)}
</div>
<InputField
type="input"
label="상품명"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
placeholder="상품명을 입력해주세요"
/>
<InputField
type="textarea"
label="상품소개"
value={itemDetail}
onChange={(e) => setItemDetail(e.target.value)}
placeholder="상품 소개를 입력해주세요"
/>
<InputField
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
판매가격은 숫자만 받게 해주세요~

type="input"
label="판매가격"
value={price}
onChange={(e) => setPrice(e.target.value)}
placeholder="판매 가격을 입력해주세요"
/>
<InputField
type="input"
label="태그"
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={handleEnterTag}
placeholder="태그를 입력해주세요"
/>
<div className="flex gap-12">
{tags.map((tag, index) => {
return (
<span
key={index}
Comment on lines +129 to +130
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
태그 추가 로직에서 중복되는 내용의 태그를 막고 있으니 tag를 key 값으로 사용하시면 더 좋을 것 같아요.

Suggested change
<span
key={index}
<span
key={tag}

className="flex items-center gap-8 py-6 pl-16 pr-12 bg-gray100 rounded-[26px]"
>
#{tag}
<img
className="size-22 cursor-pointer"
src={x}
onClick={() => handleDeleteTag(index)}
/>
Comment on lines +134 to +138
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
이런 클릭 가능한 요소의 경우 버튼을 사용해주세요. 더 적절한 태그가 있는데 이미지 태그에 onClick 을 주게되면 button에게 기대하는 동작을 제대로 수행하지 못합니다!

Suggested change
<img
className="size-22 cursor-pointer"
src={x}
onClick={() => handleDeleteTag(index)}
/>
<button type="button onClick={() => handleDeleteTag(index)}>
<img
className="size-22"
src={x}
/>
</button>

</span>
);
})}
</div>
</div>
</div>
);
}

export default AddItemPage;
2 changes: 1 addition & 1 deletion src/pages/ItemsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function ItemsPage() {
const [sort, setSort] = useState("recent");
const [totalProducts, setTotalProducts] = useState(1);
const [bestProducts, setBestProducts] = useState([]);
const [best, setBest] = useState(4);
const [best, setBest] = useState("");
const [totalPages, setTotalPages] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
Expand Down
19 changes: 14 additions & 5 deletions src/pages/MainPage.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import MainSection from "../components/MainSection";
import logo from "../assets/images/logo.svg";
import logoText from "../assets/images/logo_text.svg";
Expand All @@ -7,8 +7,15 @@ import bottomImg from "../assets/images/home_bottom.svg";
import img1 from "../assets/images/home_01.svg";
import img2 from "../assets/images/home_02.svg";
import img3 from "../assets/images/home_03.svg";
import Button from "../components/Button";

function MainPage() {
const navigate = useNavigate();

const handleButtonClick = () => {
navigate("/items");
};

return (
<div>
<div className="w-full bg-white sticky top-0">
Expand All @@ -17,7 +24,9 @@ function MainPage() {
<img className="w-153 hidden tablet:block" src={logo} />
<img className="w-103 block tablet:hidden" src={logoText} />
</div>
<button>로그인</button>
<Button type="login" onClick={handleButtonClick}>
로그인
</Button>
</div>
</div>
<div className="bg-[#cfe5ff] flex items-end">
Expand All @@ -27,9 +36,9 @@ function MainPage() {
일상의 모든 물건을 <br className="block tablet:hidden pc:block" />
거래해보세요
</h2>
<Link to="/items">
<button>구경하러 가기</button>
</Link>
<Button type="shopping" onClick={handleButtonClick}>
구경하러 가기
</Button>
</div>
<div>
<img className="h-172 tablet:h-340" src={topImg} />
Expand Down
Loading