-
Notifications
You must be signed in to change notification settings - Fork 39
React 이관현 sprint5 #185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "React-\uC774\uAD00\uD604-sprint5"
React 이관현 sprint5 #185
Changes from all commits
b2e37bd
6f8bbb0
e11e25f
212e864
4dc5dd0
92561ba
2daa19f
8165b94
872019d
e07dc86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,70 +1,24 @@ | ||
| # Getting Started with Create React App | ||
|
|
||
| This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). | ||
|
|
||
| ## Available Scripts | ||
|
|
||
| In the project directory, you can run: | ||
|
|
||
| ### `npm start` | ||
|
|
||
| Runs the app in the development mode.\ | ||
| Open [http://localhost:3000](http://localhost:3000) to view it in your browser. | ||
|
|
||
| The page will reload when you make changes.\ | ||
| You may also see any lint errors in the console. | ||
|
|
||
| ### `npm test` | ||
|
|
||
| Launches the test runner in the interactive watch mode.\ | ||
| See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. | ||
|
|
||
| ### `npm run build` | ||
|
|
||
| Builds the app for production to the `build` folder.\ | ||
| It correctly bundles React in production mode and optimizes the build for the best performance. | ||
|
|
||
| The build is minified and the filenames include the hashes.\ | ||
| Your app is ready to be deployed! | ||
|
|
||
| See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. | ||
|
|
||
| ### `npm run eject` | ||
|
|
||
| **Note: this is a one-way operation. Once you `eject`, you can't go back!** | ||
|
|
||
| If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. | ||
|
|
||
| Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. | ||
|
|
||
| You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. | ||
|
|
||
| ## Learn More | ||
|
|
||
| You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). | ||
|
|
||
| To learn React, check out the [React documentation](https://reactjs.org/). | ||
|
|
||
| ### Code Splitting | ||
|
|
||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) | ||
|
|
||
| ### Analyzing the Bundle Size | ||
|
|
||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) | ||
|
|
||
| ### Making a Progressive Web App | ||
|
|
||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) | ||
|
|
||
| ### Advanced Configuration | ||
|
|
||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) | ||
|
|
||
| ### Deployment | ||
|
|
||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) | ||
|
|
||
| ### `npm run build` fails to minify | ||
|
|
||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) | ||
| 체크리스트 [기본] | ||
| 중고마켓 | ||
|
|
||
| [x] 중고마켓 페이지 주소는 “/items” 입니다. | ||
| [x] 페이지 주소가 “/items” 일때 상단네비게이션바의 '중고마켓' 버튼의 색상은 “3692FF”입니다. | ||
| [x] 상단 네비게이션 바는 이전 미션에서 구현한 랜딩 페이지와 동일한 스타일로 만들어 주세요. | ||
| [x] 상품 데이터 정보는 https://panda-market-api.vercel.app/docs/#/ 에 명세된 GET 메소드 “/products” 를 사용해주세요. | ||
| [x] '상품 등록하기' 버튼을 누르면 “/additem” 로 이동합니다. ( 빈 페이지 ) | ||
| [x] 전체 상품에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다. | ||
|
|
||
| 중고마켓 반응형 | ||
|
|
||
| [x] 베스트 상품 | ||
| Desktop : 4개 보이기 | ||
| Tablet : 2개 보이기 | ||
| Mobile : 1개 보이기 | ||
|
|
||
| [x] 전체 상품 | ||
| Desktop : 12개 보이기 | ||
| Tablet : 6개 보이기 | ||
| Mobile : 4개 보이기 | ||
|
|
||
| 체크리스트 [심화] | ||
| [x] 페이지 네이션 기능을 구현합니다. |
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .container { | ||
| max-width: 1200px; | ||
| margin: 0 auto; | ||
| padding: 0 100px; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import "./Container.css"; | ||
|
|
||
| const Container = ({ children }) => { | ||
| return <div className="container">{children}</div>; | ||
| }; | ||
|
|
||
| export default Container; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| .footer { | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| gap: 8px; | ||
| margin-top: 24px; | ||
| margin-bottom: 30px; | ||
| } | ||
|
|
||
| .footer__btn { | ||
| width: 40px; | ||
| height: 40px; | ||
| border-radius: 50%; | ||
| border: 1px solid gray; | ||
| background-color: white; | ||
| color: #6b7280; | ||
| font-size: 20px; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .footer__btn:hover:not(:disabled) { | ||
| background-color: #f0f0f0; | ||
| } | ||
|
|
||
| .footer__btn:disabled { | ||
| cursor: not-allowed; | ||
| opacity: 0.4; | ||
| } | ||
|
|
||
| .active { | ||
| background-color: #2f80ed; | ||
| color: white; | ||
| border: none; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import { useEffect, useState } from "react"; | ||
| import "./Footer.css"; | ||
|
|
||
| const Footer = ({ currentPage, setCurrentPage }) => { | ||
| const [totalPages, setTotalPages] = useState(1); | ||
| const pageSize = 10; // 표시할 상품 개수 | ||
| const maxVisible = 5; | ||
|
|
||
| useEffect(() => { | ||
| const fetchTotalPages = async () => { | ||
| try { | ||
| const res = await fetch( | ||
| `https://panda-market-api.vercel.app/products?page=1&pageSize=${pageSize}&orderBy=recent` | ||
| ); | ||
| const data = await res.json(); | ||
| const count = data.totalCount || 1; //보호 처리 | ||
| setTotalPages(Math.ceil(count / pageSize)); | ||
| } catch (err) { | ||
| console.error("페이지 수 불러오기 실패:", err); | ||
| } | ||
| }; | ||
|
|
||
| fetchTotalPages(); | ||
| }, []); | ||
|
|
||
| const currentGroup = Math.floor((currentPage - 1) / maxVisible); | ||
| const maxGroup = Math.floor((totalPages - 1) / maxVisible); | ||
| const startPage = currentGroup * maxVisible + 1; | ||
| const endPage = Math.min(startPage + maxVisible - 1, totalPages); | ||
|
|
||
| const handleClick = (page) => { | ||
| if (page >= 1 && page <= totalPages) { | ||
| setCurrentPage(page); | ||
| } | ||
| }; | ||
|
|
||
| const handlePrevGroup = () => { | ||
| const prevStart = (currentGroup - 1) * maxVisible + 1; | ||
| if (currentGroup > 0) setCurrentPage(prevStart); | ||
| }; | ||
|
|
||
| const handleNextGroup = () => { | ||
| const nextStart = (currentGroup + 1) * maxVisible + 1; | ||
| if (currentGroup < maxGroup) setCurrentPage(nextStart); | ||
| }; | ||
|
|
||
| const pages = []; | ||
| for (let i = startPage; i <= endPage; i++) { | ||
| pages.push(i); // 현재 그룹에 해당하는 페이지만 저장 | ||
| } | ||
|
|
||
| return ( | ||
| <div className="footer"> | ||
| {/*이전 페이지 버튼*/} | ||
| <button | ||
| className="footer__btn" | ||
| onClick={handlePrevGroup} | ||
| disabled={currentGroup === 0} | ||
| > | ||
| < | ||
| </button> | ||
|
|
||
| {/* 페이지 숫자 버튼 */} | ||
| {pages.map((pageNum) => ( | ||
| <button | ||
| key={pageNum} | ||
| onClick={() => handleClick(pageNum)} | ||
| className={`footer__btn ${pageNum === currentPage ? "active" : ""}`} //현재 페이지 조건부 클래스 | ||
| > | ||
| {pageNum} | ||
| </button> | ||
| ))} | ||
|
|
||
| {/* 다음 페이지 버튼 */} | ||
| <button | ||
| className="footer__btn" | ||
| onClick={handleNextGroup} | ||
| disabled={currentGroup === maxGroup} | ||
| > | ||
| > | ||
| </button> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Footer; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| .Header { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| } | ||
|
|
||
| .Header__menu .Header__menu__items.active { | ||
| color: #3692ff; | ||
| font-weight: bold; | ||
| background-color: transparent; | ||
| } | ||
|
|
||
| .Header__logo { | ||
| width: 153px; | ||
| height: 51px; | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| } | ||
|
|
||
| .Header__menu__items { | ||
| color: #000000; | ||
| font-weight: 500; | ||
| text-decoration: none; | ||
| } | ||
|
|
||
| .Header__menu { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 30px; | ||
| flex: 1; | ||
| margin: 0 50px; | ||
| } | ||
|
|
||
| .Header__user { | ||
| width: 30px; | ||
| height: 30px; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import pandaMarket from "../../images/PandaMarket.png"; | ||
| import UserLogo from "../../images/UserLogo.svg"; | ||
| import "./Header.css"; | ||
|
|
||
| const Header = () => { | ||
| const isMarketPage = window.location.pathname === "/items"; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 Header 컴포넌트에서 window.location.pathname을 직접 체크하고있는데, 이는 React의 선언적 방식과 맞지 않습니다. React Router의 NavLink를 사용해서, 현재 경로와 일치할때 클래스이름이 바뀌게끔 개선해보면 어떨까요?
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Header from "./components/Header/Header";
import Main from "./components/Main/Main";
import Container from "./components/Container/Container";
function App() {
return (
<Router>
<Container>
<Header />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/items" element={<Main />} />
{/* 다른 라우트들도 추가 */}
</Routes>
</Container>
</Router>
);
}
export default App;
import { NavLink } from 'react-router-dom';
import pandaMarket from "../../images/PandaMarket.png";
import UserLogo from "../../images/UserLogo.svg";
import "./Header.css";
const Header = () => {
return (
<div className="Header">
<NavLink to="/items">
<img
className="Header__logo"
src={pandaMarket}
alt="판마다켓 홈페이지 로고"
/>
</NavLink>
<div className="Header__menu">
<NavLink
className={({ isActive }) =>
`Header__menu__items ${isActive ? "active" : ""}`
}
to="/board"
>
자유게시판
</NavLink>
<NavLink
className={({ isActive }) =>
`Header__menu__items ${isActive ? "active" : ""}`
}
to="/items"
>
중고마켓
</NavLink>
</div>
<NavLink to="/items">
<img className="Header__user" src={UserLogo} alt="유저 아이콘" />
</NavLink>
</div>
);
};
export default Header;NavLink는 현재 경로와 일치할 때 isActive Prop이 true로 바뀌는데, 이를 활용해 active 클래스를 추가하면 될 것 같습니다. |
||
|
|
||
| return ( | ||
| <div className="Header"> | ||
| <a href="./items" target="_blank" rel="noopener noreferrer"> | ||
| <img | ||
| className="Header__logo" | ||
| src={pandaMarket} | ||
| alt="판마다켓 홈페이지 로고" | ||
| /> | ||
| </a> | ||
| <div className="Header__menu"> | ||
| <a | ||
| className="Header__menu__items" | ||
| href="/items" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| 자유게시판 | ||
| </a> | ||
| <a | ||
| className={`Header__menu__items ${isMarketPage ? "active" : ""}`} | ||
| href="/items" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| 중고마켓 | ||
| </a> | ||
| </div> | ||
| <a href="./items" target="_blank" rel="noopener noreferrer"> | ||
| <img className="Header__user" src={UserLogo} alt="유저 아이콘" /> | ||
| </a> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Header; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 컴포넌트는 Footer보단 Pagination에 가까워보이네요! 네이밍을 바꿔주세요. 그리고 UI를 재사용할뿐만 아니라 로직만 재사용할 일이 있다면 이런 로직들을 커스텀 훅 단위로 분리해주시면 도움이 되겠죠? :)