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
1,255 changes: 1,252 additions & 3 deletions vite-project/package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions vite-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.1",
"autoprefixer": "^10.4.21",
"eslint": "^9.25.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"vite": "^6.3.5"
}
}
6 changes: 6 additions & 0 deletions vite-project/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
2 changes: 2 additions & 0 deletions vite-project/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import Nav from "./components/Nav";
import ItemsPage from "./pages/ItemsPage/ItemsPage";
import AddItemPage from "./pages/AddItemPage/AddItemPage";
import ItemDetailPage from "./pages/ItemDetailPage/ItemDetailPage";

function App() {
return (
Expand All @@ -11,6 +12,7 @@ function App() {
<Route path="/" element={<Navigate to="/items" />} />
<Route path="/items" element={<ItemsPage />} />
<Route path="/additem" element={<AddItemPage />} />
<Route path="/items/:productId" element={<ItemDetailPage />} />
</Routes>
</BrowserRouter>
);
Expand Down
22 changes: 22 additions & 0 deletions vite-project/src/api.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,25 @@ export async function getProducts({
throw new Error(`상품을 불러오는데 실패했습니다 : ${error.message}`);
}
}

export async function getProductDetails(productId) {
try {
const response = await instance.get(`/products/${productId}`);
return response.data;
} catch (error) {
throw new Error(`상품을 불러오는데 실패했습니다 : ${error.message}`);
}
}

export async function getProductComments(productId, limit = 5, cursor) {
try {
const params = { limit };
if (cursor) params.cursor = cursor;
const response = await instance.get(`/products/${productId}/comments`, {
params,
});
return response.data;
} catch (error) {
throw new Error(`상품을 불러오는데 실패했습니다 : ${error.message}`);
}
}
17 changes: 10 additions & 7 deletions vite-project/src/axiosInstance.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,26 @@ const instance = axios.create({
},
});

export const ERROR_STATUSCODE_MESSAGES = {
401: "인증이 필요합니다. 로그인 해주세요.",
403: "접근 권한이 없습니다.",
404: "요청하신 리소스를 찾을 수 없습니다.",
500: "서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.",
};

instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response) {
const status = error.response.status;

if (status === 401) {
alert("인증이 필요합니다. 로그인 해주세요.");
} else if (status === 500) {
alert("서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.");
}
alert(
ERROR_STATUSCODE_MESSAGES[status] || "알 수 없는 오류가 발생했습니다."
);
} else {
alert("네트워크 오류가 발생했습니다.");
}

return Promise.reject(error);
}
);
Expand Down
11 changes: 6 additions & 5 deletions vite-project/src/components/SelectDropdown.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useEffect, useRef, useState } from "react";
import selectIcon from "../assets/ic-sort.svg";

const options = [
{ label: "최신순", value: "recent" },
{ label: "좋아요순", value: "favorite" },
];

export default function SelectDropdown({ onChange, value }) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);

const options = [
{ label: "최신순", value: "recent" },
{ label: "좋아요순", value: "favorite" },
];

const selected = options.find((opt) => opt.value === value);

const handleSelect = (option) => {
Expand All @@ -32,6 +32,7 @@ export default function SelectDropdown({ onChange, value }) {
<div ref={dropdownRef} className="select-dropdown">
<div className="selected-option" onClick={() => setIsOpen(!isOpen)}>
<img src={selectIcon} alt="드롭다운 아이콘" />
<span>{selected ? selected.label : "선택"}</span>
</div>

{isOpen && (
Expand Down
3 changes: 3 additions & 0 deletions vite-project/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
1 change: 1 addition & 0 deletions vite-project/src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import App from "./App";
import ReactDOM from "react-dom/client";
import "./index.css";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
20 changes: 10 additions & 10 deletions vite-project/src/pages/AddItemPage/ProductAddImg.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,20 @@ export default function ProductAddImg() {
const inputRef = useRef();

const handleFileChange = (e) => {
const file = e.target.files[0];
const file = e.target.files?.[0];
if (!file) return;

if (previewImg) {
SetShowError(true);
inputRef.current.value = "";
if (!file.type.startsWith("image/")) {
SetShowError("이미지 파일만 업로드할 수 있습니다.");
e.target.value = "";
return;
}

const reader = new FileReader();
reader.onloadend = () => {
setPreviewImg(reader.result);
};
reader.readAsDataURL(file);
SetShowError(null);
inputRef.current = file;
if (previewImg) URL.revokeObjectURL(previewImg);
const url = URL.createObjectURL(file);
setPreviewImg(url);
e.target.value = "";
};

const handleClearClick = () => {
Expand Down
11 changes: 8 additions & 3 deletions vite-project/src/pages/AddItemPage/ProducutAddHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ const ProductAddHeaderButton = styled.button`
font-weight: 600;
font-size: 1.6rem;
color: var(--gray-100);
background-color: ${({ disabled }) =>
disabled ? "var(--gray-400)" : "var(--blue)"};
border-radius: 0.8rem;
width: 7.4rem;
height: 4.2rem;
border: 0.1rem solid var(--gray-400);
cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")};
&:enabled {
background-color: var(--blue);
cursor: pointer;
}
&:disabled {
background-color: var(--gray-400);
cursor: not-allowed;
}
`;

export default function ProductAddHeader({ isFormValid }) {
Expand Down
42 changes: 42 additions & 0 deletions vite-project/src/pages/ItemDetailPage/ItemDetailPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useEffect, useState } from "react";
import { getProductComments, getProductDetails } from "../../api";
import { useParams } from "react-router-dom";
import ItemInfo from "./ItemInfo";

export default function ItemDetailPage() {
const [itemInfo, setItemInfo] = useState({});
const [commentInfo, setCommentInfo] = useState({});
const { productId } = useParams();
useEffect(() => {
async function fetchData() {
if (productId) {
try {
const [itemRes, commentRes] = await Promise.all([
getProductDetails(productId),
getProductComments(productId),
]);
setItemInfo(itemRes);
setCommentInfo(commentRes);
} catch (error) {
throw new Error(`상품을 불러오는데 실패했습니다 : ${error.message}`);
}
}
}
fetchData();
}, [productId]);
console.log(itemInfo);
console.log(commentInfo);
return (
<>
<ItemInfo
images={itemInfo.images}
name={itemInfo.name}
price={itemInfo.price}
description={itemInfo.description}
favoriteCount={itemInfo.favoriteCount}
tags={itemInfo.tags}
nickname={itemInfo.ownerNickname}
/>
</>
);
}
46 changes: 46 additions & 0 deletions vite-project/src/pages/ItemDetailPage/ItemInfo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export default function ItemInfo({
images,
name,
price,
description,
favoriteCount,
tags,
}) {
return (
<>
<section className="flex w-[1200px] mt-9 mx-auto space-x-7">
<img
className="w-[486px] h-[486px] rounded-2xl"
src={images}
alt="상품 이미지"
/>
<div className="h-[496px]">
<div className="pb-2 mb-10 border-b-2 border-black">
<h2 className="text-2xl font-semibold text-[#1f2937]">{name}</h2>
<h1 className="text-[40px] font-semibold text-[#1f2937]">
{price}원
</h1>
</div>
<div>
<div className="h-[146px]">
<span className="mb-7 block text-base font-semibold text-[#4b5563]">
상품 소개
</span>
<p className="text-base font-normal text-[#4b5563]">
{description}
</p>
</div>
<div className="h-[78px]">
<span className="mb-7 block text-base font-semibold text-[#4b5563]">
상품 태그
</span>
<ul>
<li>{tags}</li>
</ul>
</div>
</div>
</div>
</section>
</>
);
}
9 changes: 7 additions & 2 deletions vite-project/src/pages/ItemsPage/BestProducts.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Link } from "react-router-dom";
import likeIcon from "../../assets/ic-heart.svg";

function BestProducts({ bestProducts }) {
Expand All @@ -8,7 +9,11 @@ function BestProducts({ bestProducts }) {
</div>
<div className="best-products-card">
{bestProducts.map((product) => (
<div className="best-product-card" key={product.id}>
<Link
to={`/items/${product.id}`}
className="best-product-card"
key={product.id}
>
<img
className="best-product-card-img"
src={product.images[0]}
Expand All @@ -24,7 +29,7 @@ function BestProducts({ bestProducts }) {
</button>
{product.favoriteCount}
</span>
</div>
</Link>
))}
</div>
</>
Expand Down
2 changes: 2 additions & 0 deletions vite-project/src/pages/ItemsPage/ItemsPage.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
display: flex;
flex-direction: column;
gap: 1.6rem;
text-decoration: none;
}

.total-product-card-img {
Expand Down Expand Up @@ -106,6 +107,7 @@
display: flex;
flex-direction: column;
gap: 1.6rem;
text-decoration: none;
}

.best-product-card-img {
Expand Down
8 changes: 6 additions & 2 deletions vite-project/src/pages/ItemsPage/TotalProducts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ function TotalProducts({

<div className="total-products-card">
{totalProducts.map((product) => (
<div className="total-product-card" key={product.id}>
<Link
to={`/items/${product.id}`}
className="total-product-card"
key={product.id}
>
<img
className="total-product-card-img"
src={product.images[0]}
Expand All @@ -79,7 +83,7 @@ function TotalProducts({
</button>
{product.favoriteCount}
</span>
</div>
</Link>
))}
</div>
</>
Expand Down
8 changes: 8 additions & 0 deletions vite-project/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
Loading