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
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_ACCESS_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDkzLCJzY29wZSI6ImFjY2VzcyIsImlhdCI6MTczMzU2NzgwMywiZXhwIjoxNzMzNTY5NjAzLCJpc3MiOiJzcC1wYW5kYS1tYXJrZXQifQ.15l2pH_P-kh0tMemBn521wnFwugH38QssNljRzNa3Qo"
Copy link
Collaborator

Choose a reason for hiding this comment

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

이런 .env 파일은 절~대 git에 올리시면 안됩니다. 😸
이 파일에 API KEY 등 보안적인 요소가 적힌 파일이기 때문이에요.
.gitignore에 .env 추가하시면 git에 올라가지 않을거예요~ 

59 changes: 59 additions & 0 deletions api/commentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import axios, { AxiosResponse } from 'axios';
import axiosInstance from './axios';

interface GetCommentResultResponse {
list: Comment[];
nextCursor: string;
}

interface CreateCommentResultResponse {}

// -- Comment API
export const getComment = async (
productId: number,
params: {},
): Promise<AxiosResponse<GetCommentResultResponse>> => {
Comment on lines +12 to +15
Copy link
Collaborator

Choose a reason for hiding this comment

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

이런 함수 파라미터를 작성할 땐 일반적으로 객체로 받는 것이 좋습니다. 객체로 작성하게 되면 아래 장점이 있어요.
앞으로는 모든 함수에 파라미터는 객체로 작성해보세요~

  1. 파라미터 순서에 구애받지 않음
  2. 선택적 파라미터 처리가 용이
  3. 추후 파라미터 추가가 쉬움 (확장성)
  4. TypeScript에서 타입 관리가 더 명확
export const createComments = async ({
  productId,
  content,
}: CreateCommentParams) => {

try {
return await axiosInstance.get(`/products/${productId}/comments`, {
params: {
productId: productId,
limit: 10,
cursor: null,
...params,
},
});
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('Axios Error:', error.response?.data || error.message);
throw new Error('데이터를 불러오는도중 에러가 발생했습니다.');
} else {
console.error('Unknown Error:', error);
throw new Error('기타 에러입니다.');
}
}
};

export const createComments = async (
productId: number,
content: { content: string },
) => {
try {
return await axiosInstance.post(
`/products/${productId}/comments`,
content,
{
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_ACCESS_TOKEN}`,
},
},
);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('Axios Error:', error.response?.data || error.message);
throw new Error('데이터를 불러오는도중 에러가 발생했습니다.');
} else {
console.error('Unknown Error:', error);
throw new Error('기타 에러입니다.');
}
}
};
Comment on lines +25 to +59
Copy link
Collaborator

Choose a reason for hiding this comment

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

지금 보면 아래 에러 처리 코드가 동일하게 반복되는데요. 이런 경우 별도 함수로 분리해볼 수 있는 방법이 있어요.
그렇게 되면 에러처리에 추가 로직이 필요할 때 추가가 용이하고, 공통으로 일관되게 처리할 수 있을거예요.
예를들면 네트워크 에러 (offline) 같은 경우도 공통적인데 이런 부분들은 handleAxiosError 에 추가하면 이제 일괄 처리가 되겠죠?

catch (error) {
    if (axios.isAxiosError(error)) {
      console.error('Axios Error:', error.response?.data || error.message);
      throw new Error('데이터를 불러오는도중 에러가 발생했습니다.');
    } else {
      console.error('Unknown Error:', error);
      throw new Error('기타 에러입니다.');
    }
  }

const handleAxiosError = (error: unknown) => {
  if (axios.isAxiosError(error)) {
    console.error('Axios Error:', error.response?.data || error.message);
    throw new Error('데이터를 불러오는도중 에러가 발생했습니다.');
  }
  console.error('Unknown Error:', error);
  throw new Error('기타 에러입니다.');
};


...

catch (error) {
      handleAxiosError(error);
    }

34 changes: 34 additions & 0 deletions api/imageApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import axios, { AxiosResponse } from 'axios';
import axiosInstance from './axios';

interface UploadImageResponse {
url: string;
}

export const uploadImage = async (
file: File,
): Promise<UploadImageResponse | undefined> => {
const formData = new FormData();
formData.append('image', file);

try {
const response: AxiosResponse = await axiosInstance.post(
'/images/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${process.env.NEXT_PUBLIC_ACCESS_TOKEN}`,
},
},
);
console.log('파일 업로드 성공:', response.data);
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.error('파일 업로드 실패:', error.response?.data || error.message);
} else {
console.error('알 수 없는 오류 발생:', error);
}
}
};
54 changes: 34 additions & 20 deletions api/productApi.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { AxiosResponse } from 'axios';
import axios, { AxiosResponse } from 'axios';
import axiosInstance from './axios';
import { Product } from '@/components/Card';
import { Comment } from '@/components/Comment';

export type OrderType = 'recent' | 'favorite';

export interface Product {
interface ProductResultResponse {
totalCount: number; // 전체 상품 수
list: Product[]; // 상품 목록
}

export interface GetProduct {
page: number;
pageSize: number;
orderBy: OrderType;
orderBy: string;
keyword?: string;
}

export interface ProductResult {
createdAt: string;
favoriteCount: number;
ownerNickname: string;
ownerId: number;
export interface CreateProductProps {
name: string;
description: string;
images: string[];
tags: string[];
price: number;
description: string;
name: string;
id: number;
}

interface ProductResultResponse {
totalCount: number; // 전체 상품 수
list: ProductResult[]; // 상품 목록
}
// -- Product API

// 상품목록가져오기
export const getProducts = (
Expand All @@ -42,12 +40,28 @@ export const getProducts = (
});
};

export const getProductById = (productId: string) => {
return axiosInstance.get(`/products/${productId}`);
export const getProductById = async (productId: number) => {
try {
return await axiosInstance.get(`/products/${productId}`);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('Axios Error:', error.response?.data || error.message);
throw new Error('데이터를 불러오는도중 에러가 발생했습니다.');
} else {
console.error('Unknown Error:', error);
throw new Error('기타 에러입니다.');
}
}
};

export const createProduct = (productData: Product) => {
return axiosInstance.post('/products', productData);
export const createProduct = (
productData: CreateProductProps,
): Promise<AxiosResponse> => {
return axiosInstance.post('/products', productData, {
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_ACCESS_TOKEN}`,
},
});
};

export const updateProduct = (
Expand Down
62 changes: 37 additions & 25 deletions components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@ import Image from 'next/image';
import styles from './Card.module.css';
import heartIcon from '@/public/ic_heart.svg';
import profile from '@/public/profile.svg';
import { ProductResult } from '@/api/productApi';
import Link from 'next/link';

export interface Product {
createdAt: string;
favoriteCount: number;
ownerNickname: string;
ownerId: number;
images: string[];
tags: string[];
price: number;
description: string;
name: string;
id: number;
}

interface CardProps {
products: ProductResult;
products: Product;
}

const Card: React.FC<CardProps> = ({ products }) => {
Expand All @@ -17,8 +29,8 @@ const Card: React.FC<CardProps> = ({ products }) => {

return (
<>
<div className={styles.CardBox}>
<Link href={`boards/${id}`}>
<Link href={`boards/${id}`}>
<div className={styles.CardBox}>
<div className={styles.CardContent}>
<div className={styles.bestContentText}>{name}</div>
<div className={styles.bestContentImage}>
Expand All @@ -30,29 +42,29 @@ const Card: React.FC<CardProps> = ({ products }) => {
></Image>
</div>
</div>
</Link>
<div className={styles.cardInfo}>
<div className={styles.bestContentLeft}>
<Image
src={profile}
width={24}
height={24}
alt="프로파일이미지"
></Image>
<div className={styles.nickName}>{ownerNickname}</div>
<div className={styles.date}>{createdAt}</div>
</div>
<div className={styles.heartCount}>
<Image
src={heartIcon}
width={24}
height={24}
alt="좋아요버튼"
></Image>
<div className={styles.heartCountNum}>{favoriteCount}+</div>
<div className={styles.cardInfo}>
<div className={styles.bestContentLeft}>
<Image
src={profile}
width={24}
height={24}
alt="프로파일이미지"
></Image>
<div className={styles.nickName}>{ownerNickname}</div>
<div className={styles.date}>{createdAt}</div>
</div>
<div className={styles.heartCount}>
<Image
src={heartIcon}
width={24}
height={24}
alt="좋아요버튼"
></Image>
<div className={styles.heartCountNum}>{favoriteCount}+</div>
</div>
</div>
</div>
</div>
</Link>
</>
);
};
Expand Down
44 changes: 44 additions & 0 deletions components/Comment.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.comment {
padding-top: 4rem;
border-bottom: 1px solid #e5e7eb;
background-color: #fcfcfc;
}

.comment-top {
display: flex;
justify-content: space-between;
}

.content {
font-weight: 400;
font-size: 1.4rem;
line-height: 2.4rem;
color: #1f2937;
}

.comment-bottom {
padding: 2.4rem 1.2rem;
display: flex;
align-items: center;
gap: 0.8rem;
}

.profile {
display: flex;
flex-direction: column;
gap: 0.4em;
}

.nickName {
font-weight: 400;
font-size: 1.2rem;
line-height: 1.8rem;
color: #4b5563;
}

.date {
font-weight: 400;
font-size: 1.2rem;
line-height: 1.8rem;
color: #9ca3af;
}
50 changes: 50 additions & 0 deletions components/Comment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Image from 'next/image';
import React from 'react';
import profile from '@/public/profile.svg';
import Iconkebab from '@/public/ic_kebab.svg';
import styles from './Comment.module.css';

interface Writer {
image: string;
nickname: string;
id: number;
}

export interface Comment {
writer: Writer;
updatedAt: string;
createdAt: string;
content: string;
id: number;
}

export interface CommentProps {
comment: Comment;
}

const Comment: React.FC<CommentProps> = ({ comment }) => {
return (
<div className={styles.comment}>
<div className={styles['comment-top']}>
<div className={styles.content}>{comment.content}</div>
<Image src={Iconkebab} alt=""></Image>
</div>
<div className={styles['comment-bottom']}>
<div>
<Image
src={profile}
width={24}
height={24}
alt="프로파일이미지"
></Image>
</div>
<div className={styles.profile}>
<div className={styles.nickName}>{comment.writer.nickname}</div>
<div className={styles.date}>{comment.createdAt}</div>
</div>
</div>
</div>
);
};

Comment on lines +25 to +49
Copy link
Collaborator

Choose a reason for hiding this comment

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

UI 분리되게 잘 작성하셨어요! Image에 alt 값만 넣어주세요~

export default Comment;
7 changes: 3 additions & 4 deletions components/common/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { ReactNode } from 'react';
import styles from './Button.module.css';

interface ButtonProps {
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children?: ReactNode;
addClassName?: string | string[];
handleClick?: () => void;
disabled: boolean;
}

function Button({
children,
addClassName,
handleClick,
disabled,
...props
}: ButtonProps) {
const buttonClass = Array.isArray(addClassName)
? addClassName.join(' ')
Expand All @@ -24,7 +23,7 @@ function Button({
type="button"
className={`${styles.button} ${buttonClass}`}
onClick={handleClick}
disabled={disabled}
{...props}
>
{children}
</button>
Expand Down
Loading
Loading