Skip to content
Closed
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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# 스프린트 미션 5
React를 이용해 중고마켓 페이지 만들기
# 스프린트 미션 6

랜딩페이지를 React로 마이그레이션
React로 중고마켓 페이지 & 상품 등록 페이지 만들기
54 changes: 54 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.8.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
Expand Down
2 changes: 1 addition & 1 deletion src/API/ProductService.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ export async function getProduct(productId) {
{ method: "GET" },
"상품 정보를 불러오는데 실패했습니다."
);
}
}
6 changes: 3 additions & 3 deletions src/API/request.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
export const baseURL = new URL('https://panda-market-api.vercel.app/');

export async function requestAwait(url, options = {}) {
export async function requestAwait(url, options = {}, errorMsg = "API 요청 실패.") {
try {
const res = await fetch(url, options);

if (!res.ok) {
throw new Error("API Error");
throw new Error(`${errorMsg} (status: ${res.status})`);
}

return await res.json();
}
catch (e) {
throw new Error(`API Error: ${e.message}`);
throw new Error(`${errorMsg}: ${e.message}`);
}
}
20 changes: 20 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./Components/Home";
import ErrorBoundary from "./Components/ErrorBoundary";

function App() {
return (
<BrowserRouter>
<ErrorBoundary>
<Routes>
<Route path="/" element={<Home />} />

<Route path="*" element={<h1>Page Not Found</h1>} />
</Routes>
</ErrorBoundary>
</BrowserRouter>
);
}

export default App;
2 changes: 1 addition & 1 deletion src/Components/DropDown/DropDown.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react';
import ic_arrow from './ic_DownArrow.svg';
import ic_arrow from '../../images/ic_DownArrow.svg';
import style from './DropDown.module.css';

function DropDown({ options = [], onChange }) {
Expand Down
37 changes: 37 additions & 0 deletions src/Components/ErrorBoundary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError(error) {
return { hasError: true, error };
}

componentDidCatch(error, info) {
// logErrorToMyService(
// error,
// // Example "componentStack":
// // in ComponentThatThrows (created by App)
// // in ErrorBoundary (created by App)
// // in div (created by App)
// // in App
// info.componentStack,
// // Warning: `captureOwnerStack` is not available in production.
// React.captureOwnerStack(),
// );
console.error("ErrorBoundary caught an error:", error, info);
}

render() {
if (this.state.hasError) {
return <h1>Something went wrong: {this.state.error?.message}</h1>;
}

return this.props.children;
}
}

export default ErrorBoundary;
8 changes: 4 additions & 4 deletions src/Components/Footer/Footer.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import facebook from './images/ic_facebook.svg';
import twitter from './images/ic_twitter.svg';
import youtube from './images/ic_YouTube.svg';
import instagram from './images/ic_instagram.svg';
import facebook from '../../images/ic_facebook.svg';
import twitter from '../../images/ic_twitter.svg';
import youtube from '../../images/ic_YouTube.svg';
import instagram from '../../images/ic_instagram.svg';
import style from './Footer.module.css';

function Footer() {
Expand Down
5 changes: 4 additions & 1 deletion src/Components/Footer/Footer.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
justify-content: space-between;
align-items: center;
align-self: stretch;
flex-wrap: wrap;

p {
text-align: center;
Expand All @@ -28,6 +29,7 @@
display: flex;
align-items: flex-start;
gap: 30px;
flex-wrap: nowrap;

a {
text-decoration: none;
Expand All @@ -48,6 +50,7 @@
justify-content: center;
align-items: center;
flex-shrink: 0;
flex-wrap: nowrap;
gap: 10px;
width: 20px;
height: 20px;
Expand All @@ -60,7 +63,7 @@
}

a:hover {
transform: scale(1.1);
transform: scale(1.1);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Components/Header/Header.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
border-bottom: 1px solid var(--header-bottom-border-color);
background-color: var(--header-bg-color);
height: 70px;
padding: 0 200px;
}

.navBox {
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Header/Logo/logo.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import logo_img from './logo_img.svg';
import logo_img from '../../../images/logo_img.svg';
import style from './Logo.module.css';

function Logo() {
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Header/Navigator/Nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ function Nav() {
return (
<nav className={style.navigator}>
<a href="/">자유게시판</a>
<a href="/">중고마켓</a>
<a href="/items">중고마켓</a>
</nav>
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/Components/Header/Navigator/Navigator.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@
text-decoration: none;
cursor: pointer;
}

a:hover {
text-decoration: underline;
}
}
47 changes: 39 additions & 8 deletions src/Components/App.jsx → src/Components/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import PageButton from './PageButton/PageButton';
import Footer from './Footer/Footer';
import '../style/style.css';

function App() {
function Home() {
const [productListQuery, setProductListQuery] = useState({
page: 1,
pageSize: 10,
Expand All @@ -16,36 +16,67 @@ function App() {
});
const [totalCount, setTotalCount] = useState(50);

const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const handleLoad = (data) => {
setLoading(false);
setError(null);
try {
setTotalCount(data.totalCount);
return data;
} catch (error) {
setError(error);
return null;
}
};

return (
<>
<Header />

<main>
<section>
<BestProductList />
<BestProductList
page={productListQuery.page}
pageSize={productListQuery.pageSize}
orderBy={productListQuery.orderBy}
keyword={productListQuery.keyword}


/>
</section>

<section>
<ProductListController
option={{ search: true, upload: true, orderBy: true }}
setQuery={setProductListQuery}
/>
<ProductList
query={productListQuery}
itemsPerRow={5}
onLoad={(data) => setTotalCount(data.totalCount)}
/>

{loading && <p>상품 목록 로딩중...</p>}
{error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
{!loading && !error && (
<ProductList
query={productListQuery}
itemsPerRow={5}
onLoad={handleLoad}
/>
)}
</section>

<section>
<PageButton
nowPage={productListQuery.page}
totalCount={totalCount}
onChange={(newPage) => setProductListQuery(prev => ({ ...prev, page: newPage }))}
/>
</section>

</main>

<Footer />
</>
);
}

export default App;
export default Home;
Empty file.
Empty file.
4 changes: 2 additions & 2 deletions src/Components/PageButton/PageButton.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// 여기가 페이지네이션 기능

import style from './PageButton.module.css';
import ic_arrow_left from './ic_PageButton_left.svg';
import ic_arrow_right from './ic_PageButton_right.svg';
import ic_arrow_left from '../../images/ic_PageButton_left.svg';
import ic_arrow_right from '../../images/ic_PageButton_right.svg';

function PageButton({ nowPage = 1, buttonLength = 5, pageSize = 10, totalCount = 50, onChange = null }) {
const startPage = Math.floor((nowPage - 1) / buttonLength) * buttonLength + 1;
Expand Down
17 changes: 12 additions & 5 deletions src/Components/ProductList/BestProductList/BestProductList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,27 @@ import ProductCard from '../ProductCard/ProductCard';
import { getProductList } from '../../../API/ProductService';
import style from './BestProductList.module.css';

function BestProductList({ query ={}, onLoad }) {
function BestProductList({
page = 1,
pageSize = 10,
orderBy = "favorite",
keyword = "",
onLoad,
}) {
const [products, setProducts] = useState([]);

useEffect(() => {
const bestQuery = { ...query, orderBy: "favorite" };
const bestQuery = { page, pageSize, orderBy: "favorite", keyword };

getProductList(bestQuery).then((data) => {
getProductList(bestQuery)
.then((data) => {
setProducts(data.list);
onLoad(data);
if (onLoad) onLoad(data);
})
.catch((error) => {
console.error("베스트 상품 목록을 불러오는 중 오류 발생:", error);
})
}, [query, onLoad]);
}, [page, pageSize, orderBy, keyword, onLoad]);

return (
<section className={style.bestProduct}>
Expand Down
Loading