Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7e58133
docs: 진행상황 체크용도로 수정
0Hyunn May 5, 2025
382db98
styles: reset css 적용
0Hyunn May 6, 2025
3725fb0
refactor: Header 컴포넌트 라우팅 처리로 코드 수정
0Hyunn May 6, 2025
18630be
chore: 이미지 추가
0Hyunn May 6, 2025
d1b193a
feat: 컴포넌트에서 params 를 받아 호출할 수 있도록 구조 수정
0Hyunn May 6, 2025
1a1659a
styles: 스타일 수정 - 반응형 추가
0Hyunn May 6, 2025
b874864
refactor: 피드백 사항 수정 - updateVisibleCount useCallback 처리
0Hyunn May 6, 2025
587f061
refactor: 피드백 요소 수정 - Array.from 적용하여 일관된 스타일 유지
0Hyunn May 6, 2025
20fe83d
styles: 반응형 디자인 추가
0Hyunn May 6, 2025
1c13910
styles: 메인화면 반응형 디자인 수정 (모바일, 태블릿)
0Hyunn May 6, 2025
f131308
refactor: 피드백 요소 수정 - 상위 컴포넌트에서 api호출 말고 해당 컴포넌트에서 api요청 하여 화면에 뿌려줄 수…
0Hyunn May 6, 2025
6733f3e
styles: 검색창 반응형 디자인 수정
0Hyunn May 6, 2025
7b2c147
refactor: 입력 후 엔터 시 검색 가능하도록 수정
0Hyunn May 6, 2025
73021f6
styles: 반응형 디자인에 따른 구조 css 수정
0Hyunn May 6, 2025
8a8fb18
refactor: 해당 컴포넌트에서 api 호출하지 않도록 수정 (하위 컴포넌트에서 직접 부르게 하도록 수정)
0Hyunn May 6, 2025
09a997c
styles: react dropdown 스타일 구현
0Hyunn May 6, 2025
34b4f25
feat: 피드백 요소 적용 - react dropdown 구현
0Hyunn May 6, 2025
28b2211
styles: 헤더 컴포넌트 분리 및 스타일 적용
0Hyunn May 6, 2025
a3d4cbf
feat: 헤더 컴포넌트 분리
0Hyunn May 6, 2025
102026d
docs: sprint6 미션 진행상황 저장
0Hyunn May 7, 2025
fe05cef
refactor: 불필요한 컴포넌트 삭제
0Hyunn May 7, 2025
f9fe89b
feat: /items 이거나 /additem 일때 스타일 적용
0Hyunn May 7, 2025
a59965e
styles: 전체적인 스타일 구조 수정
0Hyunn May 7, 2025
a7b483c
feat: 모바일 환경에서의 화면 구조 설정 추가
0Hyunn May 7, 2025
276956a
styles: 전체적인 스타일 수정
0Hyunn May 7, 2025
e89d778
feat: 상품 등록하기 컴포넌트 생성
0Hyunn May 7, 2025
83bdaf2
feat: 상품 등록하기 - input 요소 컴포넌트 생성
0Hyunn May 7, 2025
2c332ea
feat: 상품 등록하기 - 이미지 업로드 컴포넌트 구현
0Hyunn May 7, 2025
e10d252
refactor: 불필요한 props 제거
0Hyunn May 10, 2025
4181a4d
refactor: 모바일 반응형 로고 height 수정
0Hyunn May 10, 2025
279bb12
styles: 메인화면 - 전체상품의 상품 그룹 간 margin-bottom 부여
0Hyunn May 10, 2025
eab7d6b
refactor: 불필요한 주석 삭제
0Hyunn May 10, 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
35 changes: 8 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,16 @@
# 스프린트 미션 5
# 스프린트 미션 6

## [기본 요구사항]

- [x] 중고마켓 페이지 주소는 "/items"입니다.
- [x] 페이지 주소가 “/items” 일때 상단네비게이션바의 “중고마켓" 버튼의 색상은 “3692FF”입니다.
- [x] 상단 네비게이션 바는 이전 미션에서 구현한 랜딩 페이지와 동일한 스타일로 만들어 주세요.
- [x] 전체 상품에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다.
- [x] '상품 등록하기’ 버튼을 누르면 “/additem” 로 이동합니다 ( 빈 페이지 )
- [x] 카드 데이터는 제공된 백엔드 API 페이지의 GET 메소드인 “/products”를 사용해주세요
- [x] 미디어 쿼리를 사용하여 반응형 view 마다 물품 개수를 다르게 보여줍니다 (서버로 요청하는 값은 동일)

### 베스트 상품 기준

- [x] 정렬: favorite
- [x] favorite가 가장 높은 상품 4가지

### 베스트 상품

- [x] Desktop : 4개 보이기
- [x] Tablet : 2개 보이기
- [x] Mobile : 1개 보이기

### 전체 상품

- [x] Desktop : 10개 보이기
- [x] Tablet : 6개 보이기
- [x] Mobile : 4개 보이기
- [x] 상품 등록 페이지 주소는 “/additem” 입니다.
- [x] 페이지 주소가 “/additem” 일때 상단네비게이션바의 '중고마켓' 버튼의 색상은 “3692FF”입니다.
- [x] 상품 이미지는 최대 한개 업로드가 가능합니다
- [x] 각 input의 placeholder 값을 정확히 입력해주세요.
- [x] 이미지를 제외하고 input 에 모든 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.

---

## [심화 요구사항]

- [x] 페이지 네이션 기능을 구현합니다.
- [ ] 반응형으로 보여지는 물품들의 개수를 다르게 설정할때 서버에 보내는 pageSize값을 적절하게 설정합니다.
- [x] 이미지 안의 X 버튼을 누르면 이미지가 삭제됩니다.
- [x] 추가된 태그 안의 X 버튼을 누르면 해당 태그는 삭제됩니다.
72 changes: 21 additions & 51 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,61 +1,31 @@
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 16px;
width: 100%;
height: 70px;
}

.header__logo {
display: flex;
align-items: center;
margin-left: 200px;
/* Reset CSS */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

.header__logo-img {
width: 153px;
height: 51px;
}

.header__nav-container {
margin-left: 24px;
html,
body {
width: 100%;
height: 100%;
font-family: Arial, sans-serif;
}

.header__nav {
display: flex;
gap: 16px;
img {
display: block;
max-width: 100%;
height: auto;
}

.header__nav a {
font-size: 18px;
font-weight: 700;
line-height: 26px;
a {
color: inherit;
text-decoration: none;
color: #4b5563;
}

.active {
font-weight: bold;
color: #3692ff;
}

.header__right .header__user-img {
width: 40px;
height: 40px;
margin-right: 40px;
}

@media (max-width: 1199px) {
.header__logo {
display: flex;
margin-left: 0;
}
}

@media (max-width: 767px) {
.header__logo {
display: flex;
margin-left: 0;
}
button {
background: none;
border: none;
cursor: pointer;
font: inherit;
}
27 changes: 3 additions & 24 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { BrowserRouter, Routes, Route, NavLink, Navigate } from "react-router-dom";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import Items from "./pages/Items";
import "./App.css";
import userImage from "./assets/userImage.png";
import logo from "./assets/pandaLogo.png";
import Header from "./components/Header";
import AddItem from "./pages/AddItem";

/**
Expand All @@ -13,27 +12,7 @@ import AddItem from "./pages/AddItem";
function App() {
return (
<BrowserRouter>
<header className="header">
<div className="header__logo">
<div>
<NavLink to="/items">
<img src={logo} alt="logo" className="header__logo-img" />
</NavLink>
</div>

<div className="header__nav-container">
<nav className="header__nav">
<NavLink to="/board">자유게시판</NavLink>
<NavLink to="items">중고마켓</NavLink>
</nav>
</div>
</div>

<div className="header__right">
<img src={userImage} alt="user" className="header__user-img" />
</div>
</header>
<hr style={{ border: "solid 1px #DFDFDF" }} />
<Header />
<Routes>
<Route path="/" element={<Navigate to="/items" replace />} />
<Route path="/board" element={<h1>자유게시판</h1>} />
Expand Down
5 changes: 3 additions & 2 deletions src/api/productApi.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const getProducts = async (query = "") => {
const response = await fetch(`https://panda-market-api.vercel.app/products${query}`);
export const getProducts = async (params = {}) => {
const query = new URLSearchParams(params).toString();
const response = await fetch(`https://panda-market-api.vercel.app/products?${query}`);
const data = await response.json();
return data;
};
Binary file added src/assets/dropdown.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/mobileFilter.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/mobileLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file removed src/components/AddItem.jsx
Empty file.
33 changes: 27 additions & 6 deletions src/components/BestProducts.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
align-items: center;
width: 100%;
max-width: none;
gap: 16px;
}

.best-products__image-wrapper {
Expand All @@ -38,25 +39,24 @@

.best-products__text-group {
display: flex;
margin-top: 8px;
/* margin-top: 8px; */
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청

Suggested change
/* margin-top: 8px; */

flex-direction: column;
align-items: flex-start;
width: 100%;
text-align: left;
align-items: flex-start;
gap: 10px;
}

.best-products__title-text {
font-size: 14px;
font-weight: 500;
margin-bottom: -5px;
color: #1f2937;
}

.best-products__price {
font-size: 16px;
font-weight: 700;
color: #1f2937;
margin-bottom: -3px;
}

.best-products__favorite {
Expand All @@ -68,13 +68,34 @@
/* 태블릿 사이즈*/
@media (max-width: 1199px) {
.best-products__grid {
grid-template-columns: repeat(2, 1fr);
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 16px;
}

.best-products__card {
width: 343px;
}
.best-products__image-wrapper {
width: 343px;
height: 343px;
}
}

/* 모바일 사이즈 */
@media (max-width: 767px) {
.best-products__grid {
grid-template-columns: repeat(1, 1fr);
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 16px;
}

.best-products__card {
width: 343px;
}
.best-products__image-wrapper {
width: 343px;
}
}
34 changes: 15 additions & 19 deletions src/components/BestProducts.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getProducts } from "../api/productApi";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
import "./BestProducts.css";

/**
Expand All @@ -12,32 +12,28 @@ const BestProduct = () => {
const [bestProducts, setBestProducts] = useState([]);
const [visibleCount, setVisibleCount] = useState(4); // default pc

const updateVisibleCount = useCallback(() => {
const width = window.innerWidth;
if (width <= 767) {
setVisibleCount(1);
} else if (width <= 1199) {
setVisibleCount(2);
} else {
setVisibleCount(4);
}
}, []);

useEffect(() => {
getProducts("?page=1&pageSize=4&orderBy=favorite").then((data) => {
getProducts({ page: 1, pageSize: 4, orderBy: "favorite" }).then((data) => {
setBestProducts(data.list);
});
}, []);

useEffect(() => {
const updateVisibleCount = () => {
const width = window.innerWidth;

if (width <= 767) {
// mobile
setVisibleCount(1);
} else if (width <= 1199) {
// tablet
setVisibleCount(2);
} else {
setVisibleCount(4);
}
};

updateVisibleCount();
window.addEventListener("resize", updateVisibleCount); // resize 시 eventListener

window.addEventListener("resize", updateVisibleCount);
return () => window.removeEventListener("resize", updateVisibleCount);
}, []);
}, [updateVisibleCount]);

return (
<section className="best-products">
Expand Down
95 changes: 95 additions & 0 deletions src/components/Dropdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
.dropdown {
position: relative;
display: inline-block;
font-family: inherit;
}

.dropdown__toggle {
width: 130px;
height: 42px;
padding: 0 16px;
background: white;
border: 2px solid #e5e7eb;
border-radius: 16px;
font-size: 16px;
font-weight: 400;
line-height: 26px;
color: #1f2937;
text-align: left;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}

.dropdown__arrow {
width: 24px;
height: 24px;
top: -3px;
left: 66px;
}

.dropdown__menu {
position: absolute;
top: 50px;
right: 20;
min-width: 130px;
width: auto;
background: white;
border: 1px solid #e5e7eb;
border-radius: 16px;
overflow: hidden;
z-index: 10;
}

.dropdown__menu li {
padding: 12px 20px;
font-size: 16px;
font-weight: 400;
line-height: 26px;
color: #1f2937;
text-align: center;
cursor: pointer;
border-bottom: 1px solid #e5e7eb;
white-space: nowrap;
}

.dropdown__menu li:last-child {
border-bottom: none;
}

.dropdown__menu li:hover {
background-color: #f3f4f6;
}

@media (max-width: 1199px) {
.dropdown {
width: 130px;
height: 42px;
}
}

@media (max-width: 767px) {
.dropdown {
width: auto;
position: relative;
}

.dropdown__toggle {
width: 42px;
height: 42px;
padding: 0;
justify-content: center;
}

.dropdown__arrow {
width: 20px;
height: 20px;
}

.dropdown__menu {
top: 48px;
min-width: 120px;
right: 0;
}
}
Loading
Loading