Skip to content
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
Binary file modified .DS_Store
Binary file not shown.
1,995 changes: 1,748 additions & 247 deletions vite-project/package-lock.json

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions vite-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@
"preview": "vite preview"
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.3.1",
"axios": "^1.11.0",
"file-loader": "^6.2.0",
"install": "^0.13.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.8.2"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^4.7.0",
"eslint": "^9.25.0",
"eslint-plugin-react-hooks": "^5.2.0",
Expand Down
File renamed without changes.
16 changes: 0 additions & 16 deletions vite-project/src/api/api.js

This file was deleted.

39 changes: 39 additions & 0 deletions vite-project/src/api/api.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import instance from "./axiosInstance";

export interface Items {
id: number;
name: string;
price: number;
tags: string[];
images: string[];
favoriteCount: number;
createdAt: string;
updatedAt: string;
}
Comment on lines +3 to +12
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
export interface Items {
id: number;
name: string;
price: number;
tags: string[];
images: string[];
favoriteCount: number;
createdAt: string;
updatedAt: string;
}
export interface Item {
id: number;
name: string;
price: number;
tags: string[];
images: string[];
favoriteCount: number;
createdAt: string;
updatedAt: string;
}

배열 타입인 경우 Items와 같이 표현해볼 수 있겠으나 현재는 객체에 대한 타입이므로 단수형이 더 낫지 않을까 제안드려봅니다 ㅎㅎ

Copy link
Collaborator

Choose a reason for hiding this comment

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

(제안) ProductItem으로 바꿔볼 수도 있겠네요 !

또한, Item은 의미가 넓으므로 ProductItem과 같이 특정 자원에 대한 타입임을 이름에 나타내는 방법도 있겠어요 !

export type OrderBy = "recent" | "favorite";
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으.. 꼼꼼합니다 🥺

OrderBy를 타입 추론으로 string이 되었다면 안정성이 떨어졌을 텐데 정말 꼼꼼하시네요 👍

export interface GetListsParams {
page: number;
pageSize: number;
orderBy: OrderBy;
keyword?: string;
}

export interface GetListsResponse {
list: Items[];
}

export async function getLists({
page = 1,
pageSize = 10,
orderBy = "recent",
keyword = "",
}: GetListsParams): Promise<GetListsResponse> {
const query = `page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&keyword=${keyword}`;

try {
const { data } = await instance.get(`/products?${query}`);
Comment on lines +25 to +34
Copy link
Collaborator

Choose a reason for hiding this comment

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

문자열로 직접 쿼리를 붙이면 휴먼 에러(오타, & 누락, URL 인코딩 누락 등)가 발생하기 보다 쉬울 수 있을 것 같아요. 😊

Suggested change
export async function getLists({
page = 1,
pageSize = 10,
orderBy = "recent",
keyword = "",
}: GetListsParams): Promise<GetListsResponse> {
const query = `page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&keyword=${keyword}`;
try {
const { data } = await instance.get(`/products?${query}`);
export async function getLists({
page = 1,
pageSize = 10,
orderBy = "recent",
keyword = "",
}: GetListsParams): Promise<GetListsResponse> {
try {
const { data } = await instance.get("/products", {
params: { page, pageSize, orderBy, keyword },
});

지금처럼 문자열로 직접 쿼리를 붙이면 휴먼 에러(오타, & 누락, URL 인코딩 누락 등)가 발생하기 쉬워요. 특히 keyword 같은 파라미터에 공백이나 특수 문자가 들어오면 문자열 연결 방식에서는 의도치 않은 동작이 발생할 수 있어요 !

axios는 params 옵션을 제공하기 때문에 이를 활용하는 것이 더 안전하고 가독성도 좋아질 수 있습니다 !

return data;
} catch {
throw new Error("상품 목록을 가져오는데 실패했습니다.");
}
}
10 changes: 10 additions & 0 deletions vite-project/src/api/axiosInstance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import axios from "axios";

const instance = axios.create({
baseURL: "https://panda-market-api.vercel.app",
headers: {
"Content-Type": "application/json",
},
});
Comment on lines +3 to +8
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 ~ 굿굿 ! 👍 axios를 사용해보셨군요 !!


export default instance;
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { useNavigate } from "react-router-dom";
import "./AllItems.css";
import { OrderBy } from "../api/api";

const AllItems = ({ orderBy, setOrderBy, search, setSearch }) => {
interface Props {
orderBy: string;
setOrderBy: React.Dispatch<React.SetStateAction<OrderBy>>; // AllItems 왼쪽 setOrderBy가 any로 떠서 오른쪽 setOrderBy를 hover 해서 뜬걸 그대로 붙여넣었습니다
search: string;
setSearch: React.Dispatch<React.SetStateAction<string>>; // ''
}
const AllItems = ({ orderBy, setOrderBy, search, setSearch }: Props) => {
const navigate = useNavigate();

const navigateToRegister = () => {
navigate("/additem");
};

const SetOrderBySelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
if (e.target.value === "recent" || e.target.value === "favorite")
setOrderBy(e.target.value);
}; //OrderBy가 리터럴 유니온 타입인데, e.target.value가 string 으로 타입오류가 떠서 if 문으로 고쳤습니다

return (
<>
<div className="allitems_menu">
Expand All @@ -29,9 +41,7 @@ const AllItems = ({ orderBy, setOrderBy, search, setSearch }) => {
<select
className="dropped-down"
value={orderBy}
onChange={(e) => {
setOrderBy(e.target.value);
}}
onChange={SetOrderBySelect}
>
<option value="recent">최신순</option>
<option value="favorite">좋아요순</option>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import "./ListItem.css";
import { Items } from "../api/api";
interface Props {
item: Items;
}

function ListItem({ item }) {
function ListItem({ item }: Props) {
return (
<div>
<div className="list-item">
<div className="item-images">
<img src={item.images} />
<img src={item.images?.[0]} />
Copy link
Collaborator

Choose a reason for hiding this comment

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

ListItem.tsx 파일에서 에서 src 가 빨간줄로 'string[]' 형식은 'string' 형식에 할당할 수 없습니다. 라는 오류가 떠서 확인을 해보니 src는 string 하나를 원하는데, item.images가 string[] 이라서 임을 깨달았습니다. 그럼 string[] 을 문자화하는 메소드인 JSON.stringfy(), join() ,toString() 등을 사용하면 되지 않을까 했지만 그렇게하면 유효한 문자열이 될 수 없음을 깨달아서 이렇게 고쳤습니다만 더 효율적으로 고치는 방법이 있는지 궁금합니다. 그리고 [0] 안에 0을 넣는게 관례상으로 맞는방법인지도 궁금합니다.


질문 주신 부분이 여기인 것으로 보여요 😉
의도하신게 이미지의 0번째 이미지를 썸네일로 표현하고자 하셨다면 문제 없습니다 !
다만, 기본 이미지도 고려해보시면 좋을 것 같네요 😉
예를 들어서 다음과 같이 해볼 수 있겠어요:

Suggested change
<img src={item.images?.[0]} />
<img src={item.images?.[0] ?? "/imgs/panda-product-default.jpg"} />

여기서 /imgs/panda-product-default.jpg는 임의로 작성드렸으며, 실제로 존재하고 유효한 path여야 합니다 ☺️

</div>
<div className="item-text">
<p>{item.name}</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import ListItem from "./ListItem";
import "./ListItem.css";

function PageItems({ items }) {
console.log(items);
import { Items } from "../api/api";
interface Props {
items: Items[];
}
function PageItems({ items }: Props) {
return (
<ul className="item">
{items.map((item) => {
Expand Down
4 changes: 4 additions & 0 deletions vite-project/src/components/svg.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "*.svg" {
const value: any;
export default value;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { useEffect, useState } from "react";

export default function useMediaQuery() {
const [device, setDevice] = useState("mobile"); // 'desktop' | 'tablet' | 'mobile'
const [device, setDevice] = useState<string>("mobile"); // 'desktop' | 'tablet' | 'mobile'

useEffect(
() => {
const { matches: isTablet } = window.matchMedia(
"screen and (min-width: 768px) and (max-width: 1023px)"
);
console.log(isTablet, "isTablet");

const { matches: isDesktop } = window.matchMedia(
"screen and (min-width: 1024px)"
);
console.log(isDesktop, "isDesktop");

if (isTablet) {
setDevice("tablet");
Expand Down
2 changes: 1 addition & 1 deletion vite-project/src/main.jsx → vite-project/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";

createRoot(document.getElementById("root")).render(<App />);
createRoot(document.getElementById("root") as HTMLElement).render(<App />);
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import PageItems from "../components/PageItems";
import { getLists } from "../api/api";
import useMediaQuery from "../hooks/mediaquery";
import "./UsedMarketPage.css";

const LIMIT = 12;
import { Items, OrderBy, GetListsParams, GetListsResponse } from "../api/api";

const UsedMarketPage = () => {
const [orderBy, setOrderBy] = useState("recent");
const [items, setItems] = useState([]);
const [bestItems, setBestItems] = useState([]);
const LIMIT = 12;
const [orderBy, setOrderBy] = useState<OrderBy>("recent");
const [items, setItems] = useState<Items[]>([]);
const [bestItems, setBestItems] = useState<Items[]>([]);
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");

type DeviceMap = { [key: string]: number }; // 인덱스 시그니처

const { device } = useMediaQuery();
const deviceMap = {

const deviceMap: DeviceMap = {
mobile: 1,
tablet: 2,
desktop: 4,
Expand All @@ -36,7 +39,7 @@ const UsedMarketPage = () => {
fetchlist();
}, [pageSize]);

const goPage = async (n) => {
const goPage = async (n: number) => {
const { list } = await getLists({
page: n,
pageSize: LIMIT,
Expand All @@ -51,13 +54,8 @@ const UsedMarketPage = () => {
goPage(1);
}, [orderBy, search]);

const getFilteredData = () => items;
const filteredItems = getFilteredData();

const MAXPAGE = 5;

const focusPage = (n) => document.getElementById(`page${n}`).focus();

return (
<>
<main className="main">
Expand All @@ -69,13 +67,13 @@ const UsedMarketPage = () => {
search={search}
setSearch={setSearch}
/>
<PageItems items={filteredItems} />
<PageItems items={items} />
<div>
<div className="page_button">
<button
onClick={() => {
const n = Math.max(1, page - 1);
focusPage(n);

goPage(n);
}}
>
Expand All @@ -99,7 +97,7 @@ const UsedMarketPage = () => {
<button
onClick={() => {
const n = Math.min(MAXPAGE, page + 1);
focusPage(n);

goPage(n);
}}
>
Expand Down
13 changes: 13 additions & 0 deletions vite-project/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"outDir": "dist",
"strict": true,
"moduleDetection": "force",
"jsx": "react-jsx",
"moduleResolution": "bundler",
"traceResolution": true
},
"include": ["src"]
}
Loading