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
7 changes: 5 additions & 2 deletions components/AddItemForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export default function AddItemForm({
values.tags.length > 0;

const handleChange = (name: string, value: any) => {
setValues((initialValues) => ({
...initialValues,
setValues((prevState) => ({
Copy link
Collaborator

Choose a reason for hiding this comment

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

좀 더 직관적인 이름으로 잘 수정해주셨네요~

...prevState,
[name]: value,
}));
};
Expand Down Expand Up @@ -78,16 +78,19 @@ export default function AddItemForm({

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

const formData = new FormData();
formData.append("name", values.name);
formData.append("favorite", values.favoriteCount.toString());
formData.append("description", values.description);
formData.append("price", values.price.toString());

if (values.images) {
values.images.forEach((image, index) => {
formData.append(`images[${index}]`, image);
});
}

Comment on lines +81 to +93
Copy link
Collaborator

Choose a reason for hiding this comment

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

개행 잘해주셔서 가독성이 더 좋아진 것 같습니다.

formData.append("tags", JSON.stringify(values.tags));

const result = await onSubmit(formData);
Expand Down
23 changes: 8 additions & 15 deletions components/AllItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { getProducts } from "@/lib/api";
import Pagination from "@/components/Pagination";
import { Product } from "@/types/commontypes";
import styles from "@/styles/items.module.css";
import searchIcon from "@/public/svgs/ic_search.svg";
import Image from "next/image";
import SearchIcon from "@/public/svgs/ic_search.svg";
import getPageSize from "@/lib/utils/getPageSize";

function AllItems() {
const [orderBy, setOrderBy] = useState<string>("recent");
type OrderBy = "recent" | "favorite";

export default function AllItems() {
const [orderBy, setOrderBy] = useState<OrderBy>("recent");
const [page, setPage] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(getPageSize());
const [items, setItems] = useState<Product[]>([]);
Expand Down Expand Up @@ -62,7 +63,7 @@ function AllItems() {
setIsDropdown(!isDropdown);
};

const handleOrderByChange = (newOrderBy: string) => {
const handleOrderByChange = (newOrderBy: OrderBy) => {
setOrderBy(newOrderBy);
setPage(1);
setIsDropdown(false);
Expand All @@ -85,13 +86,7 @@ function AllItems() {
onClick={handleSearchSubmit}
className={styles.search_button}
>
<Image
className={styles.all_item_search_icon}
src={searchIcon}
alt="돋보기 아이콘"
width={24}
height={24}
/>
<SearchIcon className={styles.all_item_search_icon} />
</button>
<input
className={styles.all_item_search_input}
Expand Down Expand Up @@ -129,7 +124,7 @@ function AllItems() {
</div>
</div>
<div className={styles.all_item_card_container}>
{items?.map((item) => (
{items.map((item) => (
<ItemCard item={item} key={`all_item_${item.id}`} />
))}
</div>
Expand All @@ -142,5 +137,3 @@ function AllItems() {
</div>
);
}

export default AllItems;
4 changes: 3 additions & 1 deletion components/BestArticles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import getPageSize from "@/lib/utils/getPageSize";

export default function BestArticles() {
const [articles, setArticles] = useState<Article[]>([]);
const [pageSize, setPageSize] = useState<number>(getPageSize("article"));
const [pageSize, setPageSize] = useState<number>(() =>
getPageSize("article")
);
Comment on lines +14 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

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

useState 내부에 콜백 함수 잘 사용해주셨네요.


useEffect(() => {
const fetchArticles = async ({
Expand Down
4 changes: 1 addition & 3 deletions components/BestItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import BestItemCard from "./BestItemCard";
import getPageSize from "@/lib/utils/getPageSize";
import debounce from "@/lib/utils/debounce";

function BestItem() {
export default function BestItem() {
const [items, setItems] = useState<Product[]>([]);
const [pageSize, setPageSize] = useState<number>(getPageSize("item"));

Expand Down Expand Up @@ -50,5 +50,3 @@ function BestItem() {
</div>
);
}

export default BestItem;
4 changes: 1 addition & 3 deletions components/BestItemCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import styles from "@/styles/bestitem.module.css";
import heartIcon from "@/public/svgs/ic_heart (1).svg";
import Image from "next/image";

function BestItemCard({ item }: AllItemCardProps) {
export default function BestItemCard({ item }: AllItemCardProps) {
return (
<Link href={`/items/${item.id}`} className={styles.link}>
<div className={styles.item_card}>
Expand All @@ -30,5 +30,3 @@ function BestItemCard({ item }: AllItemCardProps) {
</Link>
);
}

export default BestItemCard;
2 changes: 1 addition & 1 deletion components/ItemCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import heartIcon from "@/public/svgs/ic_heart (1).svg";
import Image from "next/image";
import defaultImg from "@/public/pngs/noImage.png";

export default function AllItemCard({ item }: AllItemCardProps) {
export default function ItemCard({ item }: AllItemCardProps) {
const [imageSrc, setImageSrc] = useState(item.images[0] || defaultImg.src);

const handleImageError = () => {
Expand Down
60 changes: 57 additions & 3 deletions components/ItemComment.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getProductComments } from "@/lib/api";
import { getProductComments, addComment, deleteComment } from "@/lib/api";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import plusBtn from "@/public/svgs/Group 33735 (1).svg";
Expand All @@ -24,6 +24,11 @@ export default function ItemComments() {
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
const [showMore, setShowMore] = useState<Record<number, boolean>>({});
const [newComment, setNewComment] = useState<string>("");

const handleCommentChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setNewComment(event.target.value);
};

useEffect(() => {
const fetchComments = async () => {
Expand Down Expand Up @@ -52,6 +57,48 @@ export default function ItemComments() {
}));
};

const handleAddComment = async () => {
if (!productId || typeof productId !== "string") {
alert("상품 ID가 없습니다.");
return;
}

if (!inputValue.trim()) {
alert("댓글 내용을 입력해주세요.");
return;
}

try {
await addComment(productId, inputValue); // 댓글 등록 API 호출
alert("댓글이 등록되었습니다.");
setInputValue("");
const updatedComments = await getProductComments(productId);
setComments(updatedComments.list);
} catch (error: any) {
alert(error.message);
console.error(error);
}
};

const handleDeleteComment = async (commentId: number) => {
if (!productId || typeof productId !== "string") {
alert("상품 ID가 없습니다.");
return;
}

if (window.confirm("정말로 이 댓글을 삭제하시겠습니까?")) {
try {
await deleteComment(commentId); // 삭제 API 호출
alert("댓글이 삭제되었습니다.");
const updatedComments = await getProductComments(productId); // 삭제 후 댓글 목록 갱신
setComments(updatedComments.list);
} catch (error: any) {
alert(error.message || "댓글 삭제 중 오류가 발생했습니다.");
console.error(error);
}
}
};

if (loading) return <div>댓글을 불러오는 중입니다...</div>;
if (error) return <div>{error}</div>;

Expand All @@ -65,7 +112,11 @@ export default function ItemComments() {
onChange={handleInputChange}
/>
<div className={styles.item_inquiry_button_container}>
<button className={styles.item_inquiry_button} disabled={!inputValue}>
<button
className={styles.item_inquiry_button}
disabled={!inputValue}
onClick={handleAddComment}
>
등록
</button>
</div>
Expand All @@ -88,7 +139,10 @@ export default function ItemComments() {
<button className={styles.comment_edit_button}>
수정하기
</button>
<button className={styles.comment_delete_button}>
<button
className={styles.comment_delete_button}
onClick={() => handleDeleteComment(comment.id)}
>
삭제하기
</button>
</div>
Expand Down
63 changes: 62 additions & 1 deletion lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function getProductDetail(
/**
* 특정 상품의 댓글 목록을 가져옵니다.
* @param {string} productId - 상품의 ID
* @param {number} [limit=10] - 가져올 댓글의 개수 (기본값: 10)
* @param {number} [limit=9999] - 가져올 댓글의 개수
* @param {string|null} [cursor=null] - 페이지네이션을 위한 커서 (기본값: null)
* @returns {Promise<Object>} 댓글 데이터를 반환합니다.
* @throws {Error} 댓글 정보 불러오기 실패 시 에러를 발생시킵니다.
Expand Down Expand Up @@ -230,3 +230,64 @@ export async function addItem(itemData: {
throw new Error("상품 등록 요청 중 문제가 발생했습니다.");
}
}

/**
* 특정 상품을 삭제합니다.
* @param {string} productId - 삭제할 상품의 ID
* @returns {Promise<void>} 삭제 성공 시 아무것도 반환하지 않습니다.
* @throws {Error} 삭제 실패 시 에러를 발생시킵니다.
*/
export async function deleteProduct(productId: string): Promise<void> {
try {
await axiosInstance.delete(`/products/${productId}`);
} catch (error: any) {
if (error.response) {
throw new Error(
error.response.data.message || "상품 삭제에 실패했습니다."
);
}
throw new Error("상품 삭제 요청 중 문제가 발생했습니다.");
}
}

/**
* 댓글 등록 요청을 보냅니다.
* @param {string} productId - 댓글을 등록할 상품 ID
* @param {string} content - 댓글 내용
* @returns {Promise<void>} 성공 시 void를 반환합니다.
* @throws {Error} 댓글 등록 실패 시 에러를 발생시킵니다.
*/
export async function addComment(
productId: string,
content: string
): Promise<void> {
try {
await axiosInstance.post(`/products/${productId}/comments`, { content });
} catch (error: any) {
if (error.response) {
throw new Error(
error.response.data.message || "댓글 등록에 실패했습니다."
);
}
throw new Error("댓글 등록 요청 중 문제가 발생했습니다.");
}
}

/**
* 댓글을 삭제합니다.
* @param {number} commentId - 삭제할 댓글의 ID
* @returns {Promise<void>} 삭제 성공 시 아무것도 반환하지 않습니다.
* @throws {Error} 삭제 실패 시 에러를 발생시킵니다.
*/
export async function deleteComment(commentId: number): Promise<void> {
try {
await axiosInstance.delete(`/comments/${commentId}`);
} catch (error: any) {
if (error.response) {
throw new Error(
error.response.data.message || "댓글 삭제에 실패했습니다."
);
}
throw new Error("댓글 삭제 요청 중 문제가 발생했습니다.");
}
}
21 changes: 21 additions & 0 deletions lib/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,25 @@ instance.interceptors.request.use(
(error) => Promise.reject(error)
);

instance.interceptors.response.use(
Copy link
Collaborator

Choose a reason for hiding this comment

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

interceptor 기능을 잘 활용해주셨네요!

(response) => {
return response;
},
(error) => {
if (error.response) {
const { status, data } = error.response;

if (status === 401) {
// 로컬 스토리지에서 토큰 제거
localStorage.removeItem("accessToken");
Copy link
Collaborator

Choose a reason for hiding this comment

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

localStorage에서 사용하는 키 값도 상수로 관리해주셔도 좋을 것 같습니다.

// 로그인 페이지로 리디렉션
if (typeof window !== "undefined") {
window.location.href = "/login";
}
}
}
return Promise.reject(error);
}
);

export default instance;
32 changes: 17 additions & 15 deletions lib/utils/getPageSize.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
export default function getPageSize(context = "default") {
if (typeof window === "undefined") return context === "default" ? 3 : 10;
type Context = "default" | "article" | "item";

const width = window.innerWidth;
const pageSize: Record<
Context,
{ small: number; medium: number; large: number }
> = {
article: { small: 1, medium: 2, large: 3 },
item: { small: 1, medium: 2, large: 4 },
default: { small: 4, medium: 6, large: 10 },
};

if (context === "article") {
if (width < 768) return 1;
if (width < 1280) return 2;
return 3;
export default function getPageSize(context: Context = "default"): number {
if (typeof window === "undefined") {
return context === "default" ? 3 : 10;
}

if (context === "item") {
if (width < 768) return 1;
if (width < 1280) return 2;
return 4;
}
const width = window.innerWidth;
const size = pageSize[context];

if (width < 768) return 4;
if (width < 1280) return 6;
return 10;
if (width < 768) return size.small;
if (width < 1280) return size.medium;
return size.large;
Comment on lines +13 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

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

상수를 잘 활용해서 코드가 많이 줄어든 것 같습니다.

}
2 changes: 1 addition & 1 deletion lib/utils/logout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useRouter } from "next/router";

export function Logout() {
export default function Logout() {
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");

Expand Down
7 changes: 7 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
});
return config;
},
reactStrictMode: false,
images: {
remotePatterns: [
Expand Down
Loading
Loading