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
63 changes: 62 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
"lodash": "^4.17.21",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0"
"react-icons": "^5.5.0",
"react-router-dom": "^7.6.0"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
Expand Down
3 changes: 3 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:root {
background-color: var(--content-bg);
}
110 changes: 11 additions & 99 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,106 +1,18 @@
import Nav from "./components/Nav";
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Items from './Pages/Items/Items.jsx';
import AddItem from './Pages/AddItem/AddItem.jsx';

import styles from "./css/App.module.css";
import Header from "./components/Header";
import DropDown from "./components/DropDown";
import ProductList from "./components/ProductList";
import Button from "./components/Button";
import SearchItem from "./components/SearchItem";
import Pagination from "./components/Pagination";

import { getProducts } from "./api/ProductApi";
import { useScreenSize } from "./utils/useScreenSize";
import { useState, useEffect } from "react";
import './App.css'

function App() {
const [search, setSearch] = useState("");
const [order, setOrder] = useState("recent");
const [currPage, setCurrPage] = useState(1);
const [totalProducts, setTotalProducts] = useState(0);
const [bestProduct, setBestProduct] = useState(4);
const [allProduct, setAllProduct] = useState(10);
const [isBestProduct, setIsBestProduct] = useState(true);

const screenSize = useScreenSize();

const handleSearch = (e) => {
setSearch(e.target.value);
};

const handleOrder = (selectedOrder) => {
setOrder(selectedOrder);
};

const handlePage = (newPage) => {
setCurrPage(newPage);
};

const getTotalProducts = async () => {
try {
const { totalCount } = await getProducts({
orderBy: order,
keyword: search,
});
setTotalProducts(totalCount);
} catch (error) {
console.error("전체 상품 수를 불러오는 중 오류:", error);
}
};

useEffect(() => {
let newPageSize;
if (screenSize === "lg") {
setBestProduct(4);
newPageSize = 10;
} else if (screenSize === "md") {
setBestProduct(2);
newPageSize = 6;
} else {
setBestProduct(1);
newPageSize = 4;
}
if (newPageSize !== allProduct) {
const firstItemOfCurrentPage = (currPage - 1) * allProduct;
const newPage = Math.floor(firstItemOfCurrentPage / newPageSize) + 1;
setCurrPage(newPage);
setAllProduct(newPageSize);
}
}, [screenSize, currPage, allProduct]);

useEffect(() => {
getTotalProducts();
}, []);
return (
<>
<Nav />
<div className={styles.content}>
<Header text={"베스트 상품"} />
<ProductList
orderBy={"favorite"}
pageSize={bestProduct}
isBestProduct={isBestProduct}
/>
<div className={styles.headers}>
<Header text={"전체 상품"} />
<SearchItem value={search} onChange={handleSearch} />
<Button href={"#"} buttonText={"상품등록하기"} />
<DropDown onChangeOrder={handleOrder} />
</div>
<ProductList
orderBy={order}
pageSize={allProduct}
keyword={search}
page={currPage}
/>
<Pagination
currPage={currPage}
totalProducts={totalProducts}
pageSize={allProduct}
handlePage={handlePage}
/>
</div>
</>
<BrowserRouter>
<Routes>
<Route path="/" element={<Items />} />
<Route path='additem' element={<AddItem />}/>
</Routes>
</BrowserRouter>
);
}

export default App;
export default App
144 changes: 144 additions & 0 deletions src/Pages/AddItem/AddItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import DeleteImg from '../../assets/input/delete.svg'

import { useState } from "react";

import Nav from "../../components/Nav/Nav";
import Content from "../../components/Content/Content";
import Header from '../../components/Header/Header';
import Button from '../../components/Button/Button';
import FormImg from "../../components/Form/FormImg";
import FormInput from "../../components/Form/FormInput";

import styles from './AddItem.module.css';

const INITIAL_VALUES = {
name: '',
description: '',
price: '',
tags:[],
tagInput:'',
images: null,
};


function AddItem() {
const [values, setValues] = useState(INITIAL_VALUES)
Comment on lines +14 to +25
Copy link
Collaborator

Choose a reason for hiding this comment

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

상태를 객체로 관리하게되면 값 하나만 바뀌어도 전체 객체가 새로운 참조로 변경되기때문에 불필요한 리렌더링이 발생해요. 개별적으로 state를 만들어서 관리하시는게 좋습니다 :)

const [isComposing, setIsComposing] = useState(false);

const handleChange = (name, value) => {
setValues((prevValues) => ({
...prevValues,
[name]: value,
}));
};


const handleTagInputKeyDown = (e) => {
if (e.key === 'Enter' && !isComposing) {
e.preventDefault();

const newTag = values.tagInput.trim();

if (newTag !== '') {

if (!values.tags.includes(`#${newTag}`)) {
setValues((prev) => ({
...prev,
tags: [...prev.tags, `#${newTag}`],
tagInput:'',
}))
} else {
setValues((prev) => ({
...prev,
tagInput:'',
}))
}
}}
};


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

const handleCompositionStart = () => {
setIsComposing(true);
};


const handleCompositionEnd = () => {
setIsComposing(false);
};

const isSubmitDisabled = !values.name || !values.description || values.price <= 0 || !values.images;

return(
<>
<Nav />
<Content>
<form className={styles.form}>
<div className={styles.headers}>
<Header type={'h1'} text={'상품 등록하기'}/>
<Button type={'submit'} href={'#'} buttonText={'등록'} disabled={isSubmitDisabled}/>
</div>
<FormImg label={'상품 이미지'} name="images" value={values.images}
initialPreview={''} onChange={handleChange}/>
<FormInput
label={'상품명'}
name="name"
type={'text'}
placeholder={'상품명을 입력해주세요'}
value={values.name}
onChange={handleChange} />
<FormInput
label={'상품 소개'}
name="description"
type={'textarea'}
placeholder={'상품 소개를 입력해주세요'}
value={values.description}
onChange={handleChange} />
<FormInput
label={'판매가격'}
name="price"
type={'number'}
placeholder={'판매 가격을 입력해주세요'}
value={values.price}
onChange={handleChange} />
<FormInput
label={'태그'}
name="tagInput"
type={'text'}
placeholder={'태그를 입력해주세요'}
value={values.tagInput}
onChange={handleChange}
onKeyDown={handleTagInputKeyDown}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}

/>
{values.tags.length > 0 && (
<div className={styles.taglistContainer}>
{values.tags.map((tag, index) => (
<div key={index} className={styles.tagItem}>
<span>{tag}</span>
<button
type="button"
onClick={() => handleRemoveTag(tag)}
className={styles.removeTagButton}
>
<img src={DeleteImg} alt="선택해제" />
</button>
</div>
))}
</div>
)}
</form>
</Content>
</>
);
}

export default AddItem
Loading
Loading