Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b2e37bd
reset
hanseulhee Oct 10, 2023
6f8bbb0
Merge branch 'codeit-bootcamp-frontend:main' into main
hanseulhee Oct 10, 2023
e11e25f
fix: 머지 후 브랜치 삭제 github action 수정
hanseulhee Oct 10, 2023
212e864
env: workflows 폴더로 이동
hanseulhee Oct 10, 2023
4dc5dd0
Merge pull request #237 from hanseulhee/fix-github-actions
withyj-codeit Nov 6, 2023
f568de3
mission 파일 추가
Apr 1, 2025
e202fe8
사이즈 수정
Apr 1, 2025
32395de
PR 수정 일부 반영
Apr 2, 2025
a2b32ef
Delete .DS_Store
canofmato Apr 3, 2025
762bcb6
Merge pull request #5 from canofmato/Basic-박다인-sprint1
addiescode-sj Apr 4, 2025
e08d232
Sprint2 미션 코드 반영 및 충돌 해결
canofmato Apr 5, 2025
42384a1
Sprint2 PR전 최종 수정
canofmato Apr 5, 2025
52fa9db
Merge pull request #29 from canofmato/Basic-박다인-sprint2
addiescode-sj Apr 10, 2025
68128e9
sprint2 코드 리뷰에 따라 수정 완료
canofmato Apr 10, 2025
5a993ed
sprint3 미션 코드 완성
canofmato Apr 15, 2025
07a4ea8
모바일 미디어쿼리 수정
canofmato Apr 15, 2025
0ae353e
Merge pull request #84 from canofmato/Basic-박다인-sprint3
addiescode-sj Apr 23, 2025
52a0b57
sprint3수정1차
canofmato Apr 29, 2025
b6f844b
오류 메세지 1차 구현
canofmato Apr 30, 2025
c3b6c62
비밀번호 표 아이콘 활성화
canofmato Apr 30, 2025
223a60a
회원가입 버튼 로그인페이지 연결
canofmato Apr 30, 2025
ad6561a
아웃라인 구현 완료
canofmato May 22, 2025
96d75dc
헤더 완성, 페이지 기초 구현 완료
canofmato May 22, 2025
5264dba
최종 완성
canofmato May 24, 2025
3e6d3a3
상품등록하기 클릭시 /additem으로 이동 구현
canofmato May 24, 2025
2a27d68
sprint6 완료
canofmato Jun 24, 2025
ad7f921
최종
canofmato Jun 24, 2025
5dfd895
Merge branch 'React-박다인' into React-박다인-sprint6
canofmato Jun 26, 2025
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

node_modules

# dependencies
/node_modules
Expand All @@ -21,3 +22,4 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

2 changes: 2 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Header from "./components/Header";
import HomePage from "./pages/Homepage/HomePage";
import CommunityPage from "./pages/CommunityPage/CommunityPage";
import ProductsPage from "./pages/ProductsPage/ProductsPage";
import AddItemPage from "./pages/AddItemPage/AddItemPage";
import LoginPage from "./pages/LoginPage/LoginPage";

function App() {
Expand All @@ -14,6 +15,7 @@ function App() {
<Route index element={<HomePage />} />
<Route path="community" element={<CommunityPage />} />
<Route path="items" element={<ProductsPage />} />
<Route path="additem" element={<AddItemPage />} />
<Route path="login" element={<LoginPage />} />
</Routes>
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,16 @@ export async function getProducts(params = {}) {
const body = await response.json();
return body;
}


export async function postProducts(data) {
const response = await fetch(`${BASE_URL}/products`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});

if (!response.ok) throw new Error("상품 등록 실패");

return await response.json();
}
Binary file added src/assets/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/plus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/components/Header.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
align-items: center;
}


.logo img {
display: none;
}

.logo {
display: flex;
justify-content: center;
Expand Down Expand Up @@ -42,6 +47,11 @@
}

@media (min-width: 768px) {

.logo img {
display: block;
}

.logo a {
font-size: 25.63px;
}
Expand Down
202 changes: 202 additions & 0 deletions src/pages/AddItemPage/AddItemPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { useRef, useState } from "react";
import "./additem.css";
import { postProducts } from "../../api";
import plus from "../../assets/plus.png";
import xbtn from "../../assets/X.png";

function AddItemPage() {
const [newItem, setNewItem] = useState({
images: null,
tagInput: "",
tags: [],
price: "",
description: "",
name: "",
});
Comment on lines +8 to +15
Copy link
Collaborator

Choose a reason for hiding this comment

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

form state를 다루고있네요!
상태 객체가 너무 큰 묶음으로 관리되다보면 불필요한 리렌더링이 자주 발생될거예요.

아래와 같이 관련된 상태끼리 묶어서 분리해볼까요?

const [formData, setFormData] = useState({
  name: "",
  description: "",
  price: "",
});
const [tags, setTags] = useState([]);
const [tagInput, setTagInput] = useState("");
const [images, setImages] = useState(null);


const [imageAlert, setImageAlert] = useState("");
const fileInputRef = useRef(null);

const handleInputChange = (field, value) => {
setNewItem((prev) => ({ ...prev, [field]: value }));
Copy link
Collaborator

Choose a reason for hiding this comment

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

React의 Batch 업데이트 특성을 생각해서 이렇게 처리하신건가요? 굳굳!! 👍

};

const handleTagKeyDown = (e) => {
if (e.key === "Enter" && newItem.tagInput.trim() !== "") {
e.preventDefault();
const newTag = newItem.tagInput.trim();
if (!newItem.tags.includes(newTag)) {
setNewItem((prev) => ({
...prev,
tags: [...prev.tags, newTag],
tagInput: "",
}));
}
}
};

const handleRemoveTag = (tagToRemove) => {
setNewItem((prev) => ({
...prev,
tags: prev.tags.filter((tag) => tag !== tagToRemove),
}));
};

const handleImageClick = () => {
if (newItem.images) {
setImageAlert(`*이미지 등록은 최대 1개까지 가능합니다.`);
return;
}
fileInputRef.current.click();
};

const handleCreateItem = async () => {
const itemData = {
images: newItem.images,
tags: newItem.tags,
price: newItem.price,
description: newItem.description,
name: newItem.name,
};

const result = await postProducts(itemData);

if (result) {
setNewItem({
images: null,
tags: [],
price: "",
description: "",
name: "",
});
}
};

const isFormValid =
newItem.name.trim() !== "" &&
newItem.description.trim() !== "" &&
newItem.price !== "" &&
newItem.tags.length > 0;

return (
<>
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기 fragment가 필요없습니다 :)

<div className="item__container">
<div className="item__register">
<h1>상품 등록하기</h1>
<button
className={`item__register--button ${isFormValid ? "active" : ""} `}
disabled={!isFormValid}
onClick={handleCreateItem}
>
등록
</button>
</div>
<div className="item__info">
<div className="item__image">
<h2>상품 이미지</h2>
<div className="item__image-wrapper">
<button
type="button"
onClick={handleImageClick}
className="item__image-upload"
>
<img className="item__image-plus" src={plus} alt="추가" />
<span className="upload-text">이미지 등록</span>
<input
type="file"
ref={fileInputRef}
accept="image/*"
style={{ display: "none" }}
onChange={(e) => {
if (newItem.images) {
setImageAlert(`*이미지 등록은 최대 1개까지 가능합니다.`);
return;
}
handleInputChange("images", e.target.files[0]);
setImageAlert(""); // 정상 등록이면 에러 제거
}}
/>
Comment on lines +110 to +118
Copy link
Collaborator

Choose a reason for hiding this comment

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

인라인으로 이렇게 작성해주는것보다는 핸들러를 따로 만들어주어 리턴문을 최대한 깔끔히 작성해주는게 좋아요. 그리고 해당 코드블락이 중복되는것같은데 공통 함수로 추출해서 중복을 제거해줄까요?

const validateImageUpload = () => {
  if (newItem.images) {
    setImageAlert(`*이미지 등록은 최대 1개까지 가능합니다.`);
    return;
  }
}

</button>
{newItem.images && (
<div className="item__image-preview">
<img
src={URL.createObjectURL(newItem.images)}
alt="preview"
/>
<button
className="item__delete"
onClick={() => {
handleInputChange("images", null);
setImageAlert("");
Copy link
Collaborator

Choose a reason for hiding this comment

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

에러 상태또한, 아래와 같이 이미지 에러에 관련된 상태 메시지를 공유하지않고 필드마다 각각 다른 에러 상태를 가질수있게끔 개선해보면 좋을것같아요.

const [errors, setErrors] = useState({
  image: "",
  name: "",
  description: "",
  price: "",
  tags: ""
});

}}
>
<img src={xbtn} alt="삭제" />
</button>
</div>
)}
</div>
{imageAlert && <p className="item__alert">{imageAlert}</p>}
</div>
<div className="item__name">
<h2>상품명</h2>
<input
type="text"
value={newItem.name}
onChange={(e) => handleInputChange("name", e.target.value)}
className="item__input"
placeholder="상품명을 입력해주세요"
/>
</div>
<div className="item__description">
<h2>상품 소개</h2>
<textarea
type="text"
value={newItem.description}
onChange={(e) => handleInputChange("description", e.target.value)}
className="item__textarea"
placeholder="상품 소개를 입력해주세요"
/>
</div>
<div className="item__price">
<h2>판매 가격</h2>
<input
type="number"
value={newItem.price}
onChange={(e) => handleInputChange("price", e.target.value)}
className="item__input"
placeholder="판매 가격을 입력해주세요"
/>
</div>
<div className="item__tags">
<h2>태그</h2>
<div className="item__tag">
<input
type="text"
value={newItem.tagInput}
onChange={(e) => handleInputChange("tagInput", e.target.value)}
onKeyDown={handleTagKeyDown}
className="item__input"
placeholder="태그를 입력해주세요"
/>
<div className="item__tag-list">
{newItem.tags.map((tag) => (
<div key={tag} className="item__tag-hash">
<span>#{tag}</span>
<button
className="item__delete"
onClick={() => handleRemoveTag(tag)}
>
<img src={xbtn} alt="삭제" />
</button>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}

export default AddItemPage;
Loading
Loading