Skip to content
Open
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
60 changes: 59 additions & 1 deletion react-app/package-lock.json

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

3 changes: 2 additions & 1 deletion react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"dependencies": {
"axios": "^1.18.0",
"react": "^19.2.6",
"react-dom": "^19.2.6"
"react-dom": "^19.2.6",
"react-router-dom": "^7.18.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
Expand Down
17 changes: 15 additions & 2 deletions react-app/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import MarketPage from './pages/MarketPage'
import { BrowserRouter, Navigate, Routes, Route } from 'react-router-dom'
import MarketPage from './pages/MarketPage.jsx'
import LandingPage from './pages/LandingPage.jsx'
import RegistrationPage from './pages/RegistrationPage.jsx'
import ProductDetailPage from './pages/ProductDetailPage.jsx'

function App() {
return <MarketPage />
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/items" element={<MarketPage />} />
<Route path="/registration" element={<RegistrationPage />} />
<Route path="/items/:id" element={<ProductDetailPage />} />
</Routes>
</BrowserRouter>
)
}

export default App
40 changes: 40 additions & 0 deletions react-app/src/api/products.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const BASE_URL = 'http://localhost:3000'


export async function getProducts({
offset = 0,
limit = 10,
keyword = '',
orderBy = 'recent',
}) {
const query = new URLSearchParams({
offset,
limit,
keyword,
orderBy,
})

const response = await fetch(`${BASE_URL}/products?${query}`)

if (!response.ok) {
throw new Error('상품 목록 조회 실패')
}

return response.json()
}

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

if (!response.ok) {
throw new Error('상품 등록 실패')
}

return response.json()
}
Binary file added react-app/src/assets/Img_home_01.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 react-app/src/assets/Img_home_02.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 react-app/src/assets/Img_home_03.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 react-app/src/assets/Img_home_bottom.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 react-app/src/assets/Img_home_top.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 react-app/src/assets/img_default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion react-app/src/components/Footer.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.footer {
background: var(--Secondary-900, #111827);
margin-top: 120px;
margin-top: 0px;
}

.inner {
Expand Down
27 changes: 19 additions & 8 deletions react-app/src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NavLink } from 'react-router-dom'
import styles from './Header.module.css'
import logoImage from '../assets/logo.png'
import logoText from '../assets/logo-text.png'
Expand All @@ -7,28 +8,38 @@ function Header() {
<header className={styles.header}>
<div className={styles.inner}>
<div className={styles.left}>
<a href="/" className={styles.logo}>
<NavLink to="/" className={styles.logo}>
<img
src={logoImage}
alt="판다마켓"
className={styles.logoDesktop}
/>
<img src={logoText} alt="판다마켓" className={styles.logoMobile} />
</a>
</NavLink>

<nav className={styles.nav}>
<a href="/articles" className={styles.navLink}>
<NavLink
to="/articles"
className={({ isActive }) =>
`${styles.navLink} ${isActive ? styles.active : ''}`
}
>
자유게시판
</a>
<a href="/" className={styles.navLink}>
</NavLink>
<NavLink
to="/items"
className={({ isActive }) =>
`${styles.navLink} ${isActive ? styles.active : ''}`
}
>
중고마켓
</a>
</NavLink>
</nav>
</div>

<a href="/login" className={styles.loginButton}>
<NavLink to="/login" className={styles.loginButton}>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🚩 Check Point

React App Route에서 /login이 등록되지 않아 있네요.

로그인
</a>
</NavLink>
</div>
</header>
)
Expand Down
5 changes: 5 additions & 0 deletions react-app/src/components/Header.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@
white-space: nowrap;
}

.active {
color: #3692ff;
}


/* Tablet */
@media (max-width: 1199px) {
.inner {
Expand Down
4 changes: 2 additions & 2 deletions react-app/src/components/Pagination.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ function Pagination({ page, totalPages, onPageChange }) {
//시작 페이지 계산 / 현재 페이지를 기준으로 왼쪽으로 2칸
// ex) page = 5, startPage = 3, 화면 : 3 4 5 6 7
// -1 방지를 위해 startPage가 무조건 1보다 큰 수일 수 있도록 max 사용
const startPage = Math.max(page - 2, 1)
let startPage = Math.max(page - 2, 1)
// 끝 페이지 계산
// startPage부터 총 5개의 페이지 버튼을 보여주기 위해 + (5 - 1)
// ex) startPage = 3이면 endPage = 7 → 화면 : 3 4 5 6 7
// 마지막 페이지(totalPages)를 넘지 않도록 min 사용
const endPage = Math.min(startPage + maxVisiblePages - 1, totalPages)
let endPage = Math.min(startPage + maxVisiblePages - 1, totalPages)
// 현재 화면에 보이는 페이지 버튼 개수 계산
// 공식: 끝 페이지 - 시작 페이지 + 1
// ex) startPage = 8, endPage = 10 → 10 - 8 + 1 = 3개 (8 9 10)
Expand Down
11 changes: 8 additions & 3 deletions react-app/src/components/ProductCard.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import styles from './ProductCard.module.css'
import defaultImg from '../assets/img_default.png'

//상품 카드를 하나씩 띄워주는 역할

Expand All @@ -16,9 +17,13 @@ function ProductCard({ product, variant = 'default' }) {
{imageUrl ? (
<img src={imageUrl} alt={product.name} className={styles.image} />
) : (
<div className={styles.placeholder}>이미지 없음</div>
<img
src={imageUrl || defaultImg}
alt={product.name}
className={styles.image}
/>
// 디폴트 이미지 출력
)}
{/* 이미지가 있으면 이미지를 출력하고 : 없으면 이미지 없음 출력 */}
</div>

<div className={styles.info}>
Expand All @@ -41,7 +46,7 @@ function ProductCard({ product, variant = 'default' }) {
/>
</svg>

<span>{product.favoriteCount}</span>
<span>{product.favoriteCount ?? 0}</span>
</div>
</div>
</article>
Expand Down
49 changes: 49 additions & 0 deletions react-app/src/hooks/useProductFormValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
function useProductFormValidation({ name, description, price, tags, tagInput }) {
const errors = {}

if (!name.trim()) {
errors.name = '상품명을 입력해주세요.'
} else if (name.trim().length > 10) {
errors.name = '상품명은 10자 이내로 입력해주세요.'
}

if (!description.trim()) {
errors.description = '상품 소개를 입력해주세요.'
} else if (description.trim().length < 10) {
errors.description = '상품 소개는 10자 이상 입력해주세요.'
} else if (description.trim().length > 100) {
errors.description = '상품 소개는 100자 이내로 입력해주세요.'
}

if (!price.trim()) {
errors.price = '판매 가격을 입력해주세요.'
} else if (!/^\d+$/.test(price)) {
errors.price = '판매 가격은 숫자로 입력해주세요.'
}

const hasInvalidTag = tags.some((tag) => tag.length > 5)

if (tagInput.trim().length > 5) {
errors.tag = '태그는 5글자 이내로 입력해주세요.'
} else if (hasInvalidTag) {
errors.tag = '태그는 5글자 이내로 입력해주세요.'
}

const isValid =
!errors.name &&
!errors.description &&
!errors.price &&
!errors.tag &&
tags.length > 0

if (tags.length === 0) {
errors.tag = errors.tag || '태그를 1개 이상 입력해주세요.'
}

return {
errors,
isValid: isValid && tags.length > 0,
}
}

export default useProductFormValidation
10 changes: 8 additions & 2 deletions react-app/src/hooks/useProducts.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ function useProducts({ orderBy, keyword, page, pageSize }) {
setError(null);
//API를 요청 시도하기
// /products에 GET 요청을 보낸다

//추가한부분//
const offset = (page - 1) * pageSize

try {
const response = await axios.get('/products', {
params: {
orderBy,
keyword,
page,
pageSize,
// page,
// pageSize,
offset,
limit: pageSize,
},
});

Expand Down
Loading