Skip to content
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

[김경기] sprint8 #282

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
2,284 changes: 1,208 additions & 1,076 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 19 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
{
"name": "1-weekly-mission",
"name": "typescript",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.28.0",
"react-scripts": "^5.0.1",
"web-vitals": "^2.1.4"
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.10.5",
"@types/react": "^19.0.3",
"@types/react-dom": "^19.0.2",
"@types/react-router-dom": "^5.3.3",
"cra-template-typescript": "1.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^6.28.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5"
},
"scripts": {
"start": "react-scripts start",
Expand All @@ -36,5 +40,10 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.26.3",
"babel-loader": "^9.2.1"
}
}
11 changes: 6 additions & 5 deletions src/App.jsx → src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Items from "./components/Items/index";
import Board from "./components/Board";
import Home from "./components/Home";
import AddItem from "components/AddItem/index";
import React from "react";
import Items from "./components/Items/index.tsx";
import Board from "./components/Board/index.tsx";
import Home from "./components/Home/index.tsx";
import AddItem from "./components/AddItem/index.tsx";
import { Routes, Route } from "react-router-dom";
import ProductDetail from "components/product";
import ProductDetail from "./components/product/index.tsx";

function App() {
return (
Expand Down
42 changes: 28 additions & 14 deletions src/api.jsx → src/api.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import { Product, ProductResponse, Comment } from "./types";

interface GetProductsParams {
page?: number;
pageSize?: number;
orderBy?: string;
keyword?: string;
totalItems?: number;
}

interface GetProductCommentsParams {
productId: string;
limit?: number;
}

export async function getProducts({
page = 1,
pageSize = 10,
orderBy,
keyword = "",
totalItems,
}) {
}: GetProductsParams): Promise<ProductResponse> {
const params = new URLSearchParams({
page,
pageSize,
// keyword가 비어있지 않은 경우에만 추가
page: page.toString(),
pageSize: pageSize.toString(),
...(keyword && { keyword }),
});

// orderBy가 있을 때만 추가
if (orderBy) {
params.append("orderBy", orderBy);
}
Expand All @@ -34,37 +47,38 @@ export async function getProducts({
}
}

// Fetch product details
export async function getProductDetails(productId) {
export async function getProductDetails(productId: string): Promise<Product> {
try {
const response = await fetch(
`https://panda-market-api.vercel.app/products/${productId}`
);
if (!response.ok) {
throw new Error("Product not found");
throw new Error("상품을 찾을 수 없습니다.");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching product details:", error);
console.error("상품 세부 정보를 가져오는 중 오류 발생:", error);
throw error;
}
}

// Fetch product comments
export async function getProductComments(productId, limit = 10) {
const params = new URLSearchParams({ limit: limit.toString() }); // limit 값 추가
export async function getProductComments({
productId,
limit = 10,
}: GetProductCommentsParams): Promise<Comment[]> {
const params = new URLSearchParams({ limit: limit.toString() });
try {
const response = await fetch(
`https://panda-market-api.vercel.app/products/${productId}/comments?${params}`
);
if (!response.ok) {
throw new Error("Comments not found");
throw new Error("댓글을 찾을 수 없습니다.");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching product comments:", error);
console.error("상품 댓글을 가져오는 중 오류 발생:", error);
throw error;
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
import React, { useState } from "react";
import Header from "components/Header/index";
import React, { ChangeEvent, useState } from "react";
import Header from "../Header/index.tsx";
import styles from "./addItem.module.css";

function AddItems() {
const [productImg, setProductImg] = useState(null);
const [title, setTitle] = useState("");
const [info, setInfo] = useState("");
const [price, setPrice] = useState("");
const [tagInput, setTagInput] = useState("");
const [tags, setTags] = useState([]);
const [productImg, setProductImg] = useState<string | null>(null); // 이미지 URL 또는 null
const [title, setTitle] = useState<string>(""); // 상품명
const [info, setInfo] = useState<string>(""); // 상품 소개
const [price, setPrice] = useState<string>(""); // 판매 가격
const [tagInput, setTagInput] = useState<string>(""); // 태그 입력
const [tags, setTags] = useState<string[]>([]); // 태그 배열

const handleImageUpload = (e) => {
if (e.target.files.length > 0) {
// 이미지 업로드 핸들러
const handleImageUpload = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
setProductImg(URL.createObjectURL(e.target.files[0]));
}
};

// 이미지 삭제 핸들러
const handleImageDelete = () => {
setProductImg(null);
};

// 태그 추가 핸들러
const handleTagAdd = () => {
if (tagInput.trim() && !tags.includes(tagInput.trim())) {
setTags([...tags, tagInput.trim()]);
const tag = tagInput.trim();
if (tag && !tags.includes(tag)) {
setTags([...tags, tag]);
setTagInput("");
}
};

const handleTagDelete = (tagToDelete) => {
// 태그 삭제 핸들러
const handleTagDelete = (tagToDelete: string) => {
setTags(tags.filter((tag) => tag !== tagToDelete));
};

// 제출 가능 여부 체크
const isSubmitEnabled =
title.trim() && info.trim() && price.trim() && tags.length > 0;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import React, { useState, useEffect } from "react";
import { getProducts } from "api";
import NavBar from "./navBar";
import ProductList from "./products";
import Pagination from "./pagination";
import { getProducts } from "../../api.ts";
import NavBar from "./navBar.tsx";
import ProductList from "./products.tsx";
import Pagination from "./pagination.tsx";
import styles from "./allProduct.module.css";

function AllProducts() {
const [products, setProducts] = useState([]); // 상품 데이터 상태
const [pageSize, setPageSize] = useState(getPageSize(window.innerWidth)); // 페이지 크기 상태 (화면 크기에 따라 동적으로 변경됨)
const [currentPage, setCurrentPage] = useState(1); // 현재 페이지 상태
const [orderBy, setOrderBy] = useState("recent"); // 정렬 기준 (최신순)
const [totalItems, setTotalItems] = useState(0); // 전체 상품 개수 상태 (페이징에 사용)
const [keyword, setKeyword] = useState(""); // 검색어 상태
// Product 타입 정의
interface Product {
id: string; // 상품 ID
name: string; // 상품 이름
price: number; // 상품 가격
favoriteCount: number; // 좋아요 개수
images: string[]; // 상품 이미지 URL 배열
}

// API에서 반환되는 데이터 타입 정의
interface ProductResponse {
list: Product[];
totalCount: number;
}

// 화면 크기 기반으로 페이지 크기 결정 함수
function getPageSize(width) {
if (width <= 767) {
return 4; // 화면 너비가 768px 미만이면 한 페이지에 4개
} else if (width <= 1199) {
return 6; // 화면 너비가 768px 이상이면 한 페이지에 6개
} else {
return 10; // 화면 너비가 1200px 이상이면 한 페이지에 10개
}
// 페이지 크기 계산 함수의 인수 타입
function getPageSize(width: number): number {
if (width <= 767) {
return 4; // 화면 너비가 768px 미만이면 한 페이지에 4개
} else if (width <= 1199) {
return 6; // 화면 너비가 768px 이상이면 한 페이지에 6개
} else {
return 10; // 화면 너비가 1200px 이상이면 한 페이지에 10개
Comment on lines +24 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

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

요기 사용하는 수들을 묶어서 object 로 만들어도 좋을 거 같아요!

}
}

function AllProducts() {
const [products, setProducts] = useState<Product[]>([]); // 상품 데이터 상태
const [pageSize, setPageSize] = useState<number>(
getPageSize(window.innerWidth)
); // 페이지 크기 상태
const [currentPage, setCurrentPage] = useState<number>(1); // 현재 페이지 상태
const [orderBy, setOrderBy] = useState<string>("recent"); // 정렬 기준 (최신순)
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기는 그냥 string 이 아닌 "recent" 등과 같이 정해진 string 만 올 수 있기 때문에 그 string 들을 묶은 타입을 만드는 걸 추천드려요!
type SortCategory = "recent" as const | "name" as const

const [totalItems, setTotalItems] = useState<number>(0); // 전체 상품 개수 상태
const [keyword, setKeyword] = useState<string>(""); // 검색어 상태

// 화면 크기 변경 시 페이지 크기 재조정 (반응형 디자인 처리)
useEffect(() => {
Expand All @@ -41,7 +58,7 @@ function AllProducts() {
useEffect(() => {
const fetchProducts = async () => {
try {
const result = await getProducts({
const result: ProductResponse = await getProducts({
page: currentPage, // 현재 페이지
pageSize, // 페이지 크기 (한 페이지당 표시할 상품 개수)
orderBy, // 정렬 기준 (최신순 등)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
import React, { useState } from "react";
import React, { useState, ChangeEvent } from "react";
import searchIcon from "../../assets/img/logo/searchIcon.svg";
import styles from "./allProduct.module.css";
import { Link } from "react-router-dom"; // Link import

function NavBar({ orderBy, setOrderBy, keyword, setKeyword }) {
const [isOpen, setIsOpen] = useState(false); // 드롭다운 메뉴 열림 상태 관리
// Props 타입 정의
interface NavBarProps {
orderBy: string;
setOrderBy: (order: string) => void;
keyword: string;
setKeyword: (keyword: string) => void;
}

function NavBar({ orderBy, setOrderBy, keyword, setKeyword }: NavBarProps) {
const [isOpen, setIsOpen] = useState<boolean>(false); // 드롭다운 메뉴 열림 상태 관리

// 드롭다운 메뉴의 열림/닫힘 상태를 토글하는 함수
const toggleDropdown = () => setIsOpen((prev) => !prev);

// 드롭다운에서 옵션을 선택했을 때 정렬 기준을 변경하고 드롭다운을 닫는 함수
const selectOption = (option) => {
const selectOption = (option: string) => {
const order = option === "최신순" ? "recent" : "favorite";
setOrderBy(order); // 정렬 기준을 선택한 값으로 업데이트
setIsOpen(false);
};

// 검색어 입력 시 부모 컴포넌트로 전달
const handleSearchChange = (e) => {
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setKeyword(e.target.value); // 검색어 상태 업데이트
};

Expand Down
69 changes: 0 additions & 69 deletions src/components/AllProducts/pagination.jsx

This file was deleted.

Loading
Loading