Skip to content
2 changes: 2 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import "./App.css";
import HomePage from "./pages/HomePage";
import ItemsPage from "./pages/ItemsPage";
import Layout from "./layout/Layout";
import AddItemPage from "./pages/AddItemPage";

function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route index element={<HomePage />} />
<Route path="items" element={<ItemsPage />} />
<Route path="additem" element={<AddItemPage />} />
</Route>
</Routes>
);
Expand Down
Binary file added src/assets/icons/icon-X.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/icon-plus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions src/components/Button.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
const Button = ({ children, className, onClick }) => {
const Button = ({ children, className, onClick, isDisable }) => {
const status = isDisable
? "bg-secondary-400 cursor-not-allowed"
: "bg-primary-100 hover:bg-primary-200 active:bg-primary-300 cursor-pointer";
return (
<button
className={`bg-primary-100 hover:bg-primary-200 active:bg-primary-300 text-secondary-100 cursor-pointer rounded-[8px] px-23 py-8 text-lg font-semibold ${className}`}
className={`text-secondary-100 rounded-[8px] px-23 py-8 text-lg font-semibold ${status} ${className}`}
onClick={onClick}
disabled={isDisable}
>
{children}
</button>
Expand Down
11 changes: 11 additions & 0 deletions src/components/Input.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const Input = ({ placeholder, className, value, onChange }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
공통 컴포넌트는 다양하게 사용될 수 있어야 합니다!
Input의 type 속성도 받을 수 있으면 좋을 것 같아요~

Suggested change
const Input = ({ placeholder, className, value, onChange }) => {
const Input = ({ type, placeholder, className, value, onChange }) => {

Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
className을 받으시고 빈 문자열로 초기화해주셔야 className에 undefined가 들어가지 않습니다~

Suggested change
const Input = ({ placeholder, className, value, onChange }) => {
const Input = ({ placeholder, className = "", value, onChange }) => {

return (
<input
value={value}
placeholder={placeholder}
className={`font-regular focus:outline-primary-100 text-secondary-800 placeholder:text-secondary-400 bg-secondary-100 rounded-[12px] px-24 py-15 text-lg ${className}`}
onChange={onChange}
/>
);
};
export default Input;
12 changes: 6 additions & 6 deletions src/components/Pagination.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import PaginationButton from "./PaginationButton";
import arrowLeftActive from "../assets/icons/icon-arrow-left-active.png";
import arrowLeftInactive from "../assets/icons/icon-arrow-left-inactive.png";
import arrowRightActive from "../assets/icons/icon-arrow-right-active.png";
import arrowRightInactive from "../assets/icons/icon-arrow-right-inactive.png";
import ArrowLeftActive from "../assets/icons/icon-arrow-left-active.png";
import ArrowLeftInactive from "../assets/icons/icon-arrow-left-inactive.png";
import ArrowRightActive from "../assets/icons/icon-arrow-right-active.png";
import ArrowRightInactive from "../assets/icons/icon-arrow-right-inactive.png";

const displayConfig = {
mobile: { itemCount: 4 },
Expand All @@ -27,7 +27,7 @@ const Pagination = ({ page: currentPage, setPage, totalCount, display }) => {
isActive={currentPage > 5}
>
<img
src={currentPage <= 5 ? arrowLeftInactive : arrowLeftActive}
src={currentPage <= 5 ? ArrowLeftInactive : ArrowLeftActive}
alt="왼쪽 화살표"
className="size-16"
/>
Expand All @@ -50,7 +50,7 @@ const Pagination = ({ page: currentPage, setPage, totalCount, display }) => {
isActive={lastIndex !== lastPage}
>
<img
src={lastIndex === lastPage ? arrowRightInactive : arrowRightActive}
src={lastIndex === lastPage ? ArrowRightInactive : ArrowRightActive}
alt="오른쪽 화살표"
className="size-16"
/>
Expand Down
6 changes: 4 additions & 2 deletions src/components/Search.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Input from "./Input";

const Search = ({ className }) => {
return (
<input
<Input
placeholder="검색할 상품을 입력해주세요"
className={`font-regular bg-secondary-100 rounded-[12px] bg-[url(./assets/icons/icon-search.png)] bg-size-[24px_24px] bg-position-[16px] bg-no-repeat py-9 pr-20 pl-44 text-lg ${className}`}
className={`h-42 bg-[url(./assets/icons/icon-search.png)] bg-size-[24px_24px] bg-position-[16px] bg-no-repeat pl-44 ${className}`}
/>
);
};
Expand Down
13 changes: 13 additions & 0 deletions src/components/Tag.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import XIcon from "../assets/icons/icon-X.png";

const Tag = ({ children, onDelete }) => {
return (
<div className="bg-secondary-100 text-secondary-800 font-regular flex items-center justify-between gap-8 rounded-[26px] py-5 pr-12 pl-16 text-lg">
{children}
<button onClick={() => onDelete(children)} className="cursor-pointer">
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
button type 속성의 기본값은 submit입니다~ 아래의 경우는 button이 더 적절할 것 같아요~

Suggested change
<button onClick={() => onDelete(children)} className="cursor-pointer">
<button type="button" onClick={() => onDelete(children)} className="cursor-pointer">

<img src={XIcon} alt="x아이콘" className="size-20" />
</button>
</div>
);
};
export default Tag;
29 changes: 29 additions & 0 deletions src/components/TagInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState } from "react";
import Input from "./Input";
import Tag from "./Tag";

const TagInput = ({ value, onChange }) => {
const [tagList, setTagList] = useState(["#티셔츠", "#상의"]);
const onDelete = (deleteTag) => {
setTagList(tagList.filter((tag) => tag !== deleteTag));
};
return (
<>
<Input
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
태그가 추가되는 로직을 추가해주세요~

value={value}
placeholder="태그를 입력해주세요"
onChange={onChange}
/>
<ul className="flex flex-wrap gap-12">
{tagList.map((tag) => {
return (
<li key={tag}>
<Tag onDelete={onDelete}>{tag}</Tag>
</li>
);
})}
</ul>
</>
);
};
export default TagInput;
11 changes: 11 additions & 0 deletions src/components/TextArea.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const TextArea = ({ placeholder, className, value, onChange }) => {
return (
<textarea
value={value}
placeholder={placeholder}
className={`font-regular focus:outline-primary-100 text-secondary-800 placeholder:text-secondary-400 bg-secondary-100 resize-none rounded-[12px] px-24 py-15 text-lg ${className}`}
onChange={onChange}
/>
);
};
export default TextArea;
58 changes: 58 additions & 0 deletions src/components/UploadImage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useState } from "react";
import PlusIcon from "../assets/icons/icon-plus.png";
import XIcon from "../assets/icons/icon-X.png";

const UploadImage = () => {
const [preview, setPreview] = useState(null);
const [isError, setIsError] = useState(false);
const uploadImage = (e) => {
const file = e.target.files[0];
if (!file) return;
const imageUrl = URL.createObjectURL(file);
setPreview(imageUrl);
};
const showError = () => {
if (preview) setIsError(true);
};
const onDelete = () => {
setPreview(null);
setIsError(false);
};
return (
<>
<div className="pc:gap-24 flex gap-10">
<label
className="bg-secondary-100 pc:size-282 font-regular text-secondary-400 flex size-168 cursor-pointer flex-col items-center justify-center gap-12 rounded-[12px] text-lg"
onClick={showError}
>
<img src={PlusIcon} alt="더하기" className="size-48" />
이미지 등록
{!preview && (
<input type="file" className="hidden" onChange={uploadImage} />
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>
{preview && (
<div className="relative">
<img
src={preview}
alt="미리보기"
className="pc:size-282 size-168 rounded-[12px] object-cover"
/>
<button
className="absolute top-12 right-12 size-24 cursor-pointer"
onClick={onDelete}
>
<img src={XIcon} alt="x버튼" />
</button>
</div>
)}
</div>
{isError && (
<p className="text-error-red font-regular font-lg">
*이미지 등록은 최대 1개까지 가능합니다.
</p>
)}
</>
);
};
export default UploadImage;
10 changes: 8 additions & 2 deletions src/components/header/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { NavLink } from "react-router";
import { NavLink, useLocation } from "react-router";

const Navbar = () => {
const location = useLocation();
console.log(location);
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청

Suggested change
console.log(location);

return (
<ul className="tablet:text-2lg tablet:gap-30 text-secondary-600 flex grow gap-8 text-lg font-bold">
<li>
Expand All @@ -9,7 +11,11 @@ const Navbar = () => {
<li>
<NavLink
to="/items"
className={({ isActive }) => (isActive ? "text-primary-100" : "")}
className={({ isActive }) =>
isActive || location.pathname === "/additem"
? "text-primary-100"
: ""
}
>
중고마켓
</NavLink>
Expand Down
61 changes: 61 additions & 0 deletions src/pages/AddItemPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useEffect, useState } from "react";
import Button from "../components/Button";
import Input from "../components/Input";
import TagInput from "../components/TagInput";
import TextArea from "../components/TextArea";
import UploadImage from "../components/UploadImage";

const AddItemPage = () => {
const [isDisable, setIsDisable] = useState(true);
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [price, setPrice] = useState("");
const [tag, setTag] = useState("");
useEffect(() => {
if (name && description && price && tag) setIsDisable(false);
else setIsDisable(true);
}, [name, description, price, tag]);
Comment on lines +14 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
아래처럼 작성하시는 것이 더 가독성에 좋을 것 같아요

Suggested change
useEffect(() => {
if (name && description && price && tag) setIsDisable(false);
else setIsDisable(true);
}, [name, description, price, tag]);
useEffect(() => {
const isValidForm = name && description && price && tag;
setIsDisable(!isValidForm);
}, [name, description, price, tag]);

Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
등록 버튼 활성화 조건중 tag는 문자열이 아니라 문자열의 배열입니다~ 태그 추가 기능을 구현하시면서 이 부분도 같이 수정하시면 좋겠습니다~


return (
<div className="tablet:pt-16 tablet:pb-78 tablet:px-24 text-secondary-800 mx-auto flex max-w-1200 flex-col gap-24 px-15 pt-24 pb-70 font-bold">
<div className="flex justify-between">
<h1 className="text-xl">상품 등록하기</h1>
<Button isDisable={isDisable}>등록</Button>
</div>
<div className="flex flex-col gap-16">
<h2 className="text-2lg">상품 이미지</h2>
<UploadImage />
</div>
<div className="flex flex-col gap-16">
<h2 className="text-2lg">상품명</h2>
<Input
value={name}
placeholder="상품명을 입력해주세요"
onChange={(e) => setName(e.target.value)}
/>
Comment on lines +30 to +35
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과 연결해주시는 것을 추천드려요!

Suggested change
<h2 className="text-2lg">상품명</h2>
<Input
value={name}
placeholder="상품명을 입력해주세요"
onChange={(e) => setName(e.target.value)}
/>
<label htmlFor="name" className="text-2lg">상품명</label>
<Input
id="name"
value={name}
placeholder="상품명을 입력해주세요"
onChange={(e) => setName(e.target.value)}
/>

</div>
<div className="flex flex-col gap-16">
<h2 className="text-2lg">상품 소개</h2>
<TextArea
value={description}
placeholder="상품 소개를 입력해주세요"
className="h-282"
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div className="flex flex-col gap-16">
<h2 className="text-2lg">판매가격</h2>
<Input
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
지금과 같은 금액 input 인풋의 경우 number 타입이 더 적절할 것 같아요!

value={price}
placeholder="판매 가격을 입력해주세요"
onChange={(e) => setPrice(e.target.value)}
/>
</div>
<div className="flex flex-col gap-16">
<h2 className="text-2lg">태그</h2>
<TagInput value={tag} onChange={(e) => setTag(e.target.value)} />
</div>
</div>
);
};
export default AddItemPage;
Loading