Skip to content

Commit

Permalink
feat: 상품 등록 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
heonq committed Nov 3, 2024
1 parent 4f9adbb commit 2bac93d
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 26 deletions.
22 changes: 11 additions & 11 deletions docs/sprint6.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,25 @@

- [x] 중고마켓 페이지 url path를 "/items"으로 설정하세요.
- [x] 페이지 주소가 "/items" 일 때 상단내비게이션바의 "중고마켓" 버튼의 색상은 "3692FF"입니다.
- [ ] 중고마켓 페이지 판매 중인 상품은 본인이 만든 GET 메서드를 사용해 주세요.
- [ ] 다만 좋아요 순 정렬 기능은 제외해 주세요.
- [ ] 사진은 디폴트 이미지로 프론트엔드에서 처리해주세요.
- [ ] 베스트 상품 목록 조회는 구현하지 않습니다.
- [ ] '상품 등록하기' 버튼을 누르면 "/registration" 로 이동합니다. ( 빈 페이지 )
- [x] 중고마켓 페이지 판매 중인 상품은 본인이 만든 GET 메서드를 사용해 주세요.
- [x] 다만 좋아요 순 정렬 기능은 제외해 주세요.
- [x] 사진은 디폴트 이미지로 프론트엔드에서 처리해주세요.
- [x] 베스트 상품 목록 조회는 구현하지 않습니다.
- [x] '상품 등록하기' 버튼을 누르면 "/registration" 로 이동합니다. ( 빈 페이지 )

### 상품 등록 페이지

- [ ] PC, Tablet, Mobile 디자인에 해당하는 상품 등록 페이지를 만들어 주세요.
- [ ] 상품 등록 url path는 "/registration"입니다.
- [ ] 상품 등록은 본인이 만든 POST 메서드를 사용해 주세요.
- [ ] 등록 성공 시, 해당 상품 상세 페이지로 이동합니다. (빈페이지)
- [x] PC, Tablet, Mobile 디자인에 해당하는 상품 등록 페이지를 만들어 주세요.
- [x] 상품 등록 url path는 "/registration"입니다.
- [x] 상품 등록은 본인이 만든 POST 메서드를 사용해 주세요.
- [x] 등록 성공 시, 해당 상품 상세 페이지로 이동합니다. (빈페이지)

## 심화 요구사항

### 상품 등록 페이지

- [ ] 모든 입력 input box에 빈 값이 있을 경우, 등록 버튼이 비활성화됩니다.
- [ ] 태그를 입력한 후 엔터키를 누르면, 그 태그가 칩 형태로 쌓입니다.
- [x] 모든 입력 input box에 빈 값이 있을 경우, 등록 버튼이 비활성화됩니다.
- [x] 태그를 입력한 후 엔터키를 누르면, 그 태그가 칩 형태로 쌓입니다.
- [ ] 상품명, 상품 소개, 판매 가격, 태그에 대한 유효성 검사 Custom Hook을 만들어주세요. 유효성 검사를 통과하지 않을 경우, 각 input에 빨간색 테두리와, 각각의 Input 아래에 빨간색 에러 메시지를 보여주세요.

- 유효한 조건
Expand Down
2 changes: 1 addition & 1 deletion src/components/productRegistration/ProductNameInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const ProductNameInput = () => {
return (
<ProductInputContainer>
<RegistrationH2>상품명</RegistrationH2>
<Input value={productName} onChange={(e) => setProductName(e.target.value)} />
<Input type="text" value={productName} onChange={(e) => setProductName(e.target.value)} />
</ProductInputContainer>
);
};
Expand Down
9 changes: 7 additions & 2 deletions src/components/productRegistration/ProductPriceInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAtom } from "jotai";
import { TextArea, ProductInputContainer, RegistrationH2 } from "./ProductRegistrationComponent";
import { ProductInputContainer, RegistrationH2, Input } from "./ProductRegistrationComponent";
import { productPriceState } from "../../jotai/atoms/productFormState";

const ProductPriceInput = () => {
Expand All @@ -8,7 +8,12 @@ const ProductPriceInput = () => {
return (
<ProductInputContainer>
<RegistrationH2>판매가격</RegistrationH2>
<TextArea value={productPrice} onChange={(e) => setProductPrice(e.target.value)} />
<Input
type="number"
min={0}
value={productPrice}
onChange={(e) => setProductPrice(e.target.value)}
/>
</ProductInputContainer>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import { FormEvent, ReactNode } from "react";
import { FormEvent, KeyboardEvent, ReactNode } from "react";
import styled from "styled-components";
import ProductNameInput from "./ProductNameInput";
import ProductDescriptionInput from "./ProductDescriptionInput";
import ProductPriceInput from "./ProductPriceInput";
import ProductTagsInput from "./ProductTagsInput";
import RegistrationTitle from "./RegistrationTitle";
import { useAtom } from "jotai";
import { productFormState } from "../../jotai/atoms/productFormState";
import { createProduct } from "../../apis/ProductService";
import { useNavigate } from "react-router-dom";

const ProductRegistrationForm = styled.form`
margin-top: 2.6rem;
margin-bottom: 16.2rem;
padding-top: 2.6rem;
padding-bottom: 16.2rem;
width: 120rem;
display: flex;
flex-direction: column;
${(props) => props.theme.media.medium} {
width: 100%;
padding: 1.8rem 2.4rem 19.4rem 2.4rem;
}
${(props) => props.theme.media.small} {
width: 100%;
padding: 2.4rem 1.6rem 18.6rem 1.6rem;
}
`;

export const RegistrationH2 = styled.h2`
Expand All @@ -38,10 +50,27 @@ export const TextArea = styled.textarea`
`;

function ProductRegistrationComponent({ children }: { children: ReactNode }) {
const handleSubmit = (e: FormEvent) => {
const [productForm, setProductForm] = useAtom(productFormState);
const navigate = useNavigate();

const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
const newProduct = await createProduct({ ...productForm, price: Number(productForm.price) });
setProductForm();
navigate(`/product/${newProduct._id}`);
};

const preventEnter = (e: KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault();
}
};
return <ProductRegistrationForm onSubmit={handleSubmit}>{children}</ProductRegistrationForm>;

return (
<ProductRegistrationForm onKeyDown={preventEnter} onSubmit={handleSubmit}>
{children}
</ProductRegistrationForm>
);
}

export default ProductRegistrationComponent;
Expand Down
1 change: 1 addition & 0 deletions src/components/productRegistration/ProductTagsInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const ProductTagsInput = () => {
<ProductInputContainer>
<RegistrationH2>태그</RegistrationH2>
<Input
type="text"
value={productTag}
onKeyUp={handleTags}
onChange={(e) => setProductTag(e.target.value)}
Expand Down
7 changes: 6 additions & 1 deletion src/components/productRegistration/RegistrationTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useAtomValue } from "jotai";
import styled from "styled-components";
import { productFormState } from "../../jotai/atoms/productFormState";

const RegistrationTitleContainer = styled.div`
width: 100%;
Expand All @@ -22,10 +24,13 @@ const RegistrationButton = styled.button`
`;

const RegistrationTitle = () => {
const productForm = useAtomValue(productFormState);
const disabled = Object.entries(productForm).some((array) => !array[1].length);

return (
<RegistrationTitleContainer>
<RegistrationTitleH1>상품 등록하기</RegistrationTitleH1>
<RegistrationButton disabled type="submit">
<RegistrationButton disabled={disabled} type="submit">
등록
</RegistrationButton>
</RegistrationTitleContainer>
Expand Down
21 changes: 15 additions & 6 deletions src/jotai/atoms/productFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@ export const productTagState = atom<string>("");

export const productTagsState = atom<string[]>([]);

export const productForm = atom((get) => ({
name: get(productNameState),
description: get(productDescriptionState),
price: get(productPriceState),
tags: get(productTagsState),
}));
export const productFormState = atom(
(get) => ({
name: get(productNameState),
description: get(productDescriptionState),
price: get(productPriceState),
tags: get(productTagsState),
}),
(get, set) => {
set(productNameState, "");
set(productDescriptionState, "");
set(productPriceState, "");
set(productTagState, "");
set(productTagsState, []);
}
);
10 changes: 10 additions & 0 deletions src/pages/Product.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useParams } from "react-router-dom";

function Product() {
// 구현 예정
const { id } = useParams();

return <div>{id}</div>;
}

export default Product;
5 changes: 5 additions & 0 deletions src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Root from "./Root";
import Home from "./pages/Home";
import Products from "./pages/Products";
import ProductRegistrationComponent from "./pages/ProductRegistration";
import Product from "./pages/Product";

const router = createBrowserRouter([
{
Expand All @@ -21,6 +22,10 @@ const router = createBrowserRouter([
path: "/registration",
element: <ProductRegistrationComponent />,
},
{
path: "/product/:id",
element: <Product />,
},
],
},
]);
Expand Down

0 comments on commit 2bac93d

Please sign in to comment.