Skip to content

Conversation

@yunrap
Copy link

@yunrap yunrap commented Nov 30, 2024

요구사항

기본

상품 등록 페이지

  • 상품 등록 페이지 주소는 "/addboard" 입니다.
  • 게시판 이미지는 최대 한개 업로드가 가능합니다.
  • 각 input의 placeholder 값을 정확히 입력해주세요.
  • 이미지를 제외하고 input 에 모든 값을 입력하면 '등록' 버튼이 활성화 됩니다.

상품 상세 페이지

  • 상품 상세 페이지 주소는 "/board/{id}" 입니다.
  • 댓글 input 값을 입력하면 '등록' 버튼이 활성화 됩니다.
  • 활성화된 '등록' 버튼을 누르면 댓글이 등록됩니다

심화

  • 회원가입, 로그인 api를 사용하여 받은accessToken을 사용하여 게시물 등록을 합니다.
  • '등록' 버튼을 누르면 게시물 상세 페이지로 이동합니다.

주요 변경사항

  • �토요일기준 퍼블작업을 하였습니다. 한기능씩 추가해서 커밋 하겠습니다.

스크린샷

image

멘토에게

  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.
  • module.css 에서는 추가적으로 줄수있는 addclassNames 부분을 다르게 줄 수 있는 방법이있을까요 ??
    현재는 추가 클래스 props를 받아서 CSS에서 .컴포넌트css .addClass css 이렇게 주고있습니다!
.button .addbutton{
  // css 속성 
} 

추가적인 컴포넌트 css없이도 한 deps로만 줄수있는 방법이있을까요??

@yunrap yunrap requested a review from arthurkimdev November 30, 2024 17:09
@yunrap yunrap added 미완성🫠 죄송합니다.. 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. labels Nov 30, 2024
@yunrap yunrap changed the title [윤예지] Sprint11 [윤예지] Sprint10 Dec 3, 2024
@yunrap yunrap removed the 미완성🫠 죄송합니다.. label Dec 7, 2024
@arthurkimdev
Copy link
Collaborator

arthurkimdev commented Dec 10, 2024

module.css 에서는 추가적으로 줄수있는 addclassNames 부분을 다르게 줄 수 있는 방법이있을까요 ??
현재는 추가 클래스 props를 받아서 CSS에서 .컴포넌트css .addClass css 이렇게 주고있습니다!
.button .addbutton{
  // css 속성 
} 
추가적인 컴포넌트 css없이도 한 deps로만 줄수있는 방법이있을까요??

clsx나 classnames 라이브러리를 사용하는 방법이 있을 것 같아요!
아래는 예시 코드입니다.

clsx 는 현업에서도 정말 많이 사용하는 라이브러리입니다.
https://www.npmjs.com/package/clsx

import clsx from 'clsx';
import styles from './Button.module.css';

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'small' | 'medium' | 'large';
  className?: string;
}

function Button({ variant = 'primary', size = 'medium', className }: ButtonProps) {
  return (
    <button
      className={clsx(
        styles.button,
        styles[variant],
        styles[size],
        className
      )}
    >
      Click me
    </button>
  );
}
/* Button.module.css */
.button {
  /* 기본 스타일 */
}

.primary {
  background: blue;
}

.secondary {
  background: gray;
}

.small {
  padding: 4px 8px;
}

.medium {
  padding: 8px 16px;
}

.large {
  padding: 12px 24px;
}

Copy link
Collaborator

@arthurkimdev arthurkimdev left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~ 👍

Comment on lines +12 to +15
export const getComment = async (
productId: number,
params: {},
): Promise<AxiosResponse<GetCommentResultResponse>> => {
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) => {

Comment on lines +25 to +59
} 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('기타 에러입니다.');
}
}
};
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);
    }

@@ -0,0 +1 @@
NEXT_PUBLIC_ACCESS_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDkzLCJzY29wZSI6ImFjY2VzcyIsImlhdCI6MTczMzU2NzgwMywiZXhwIjoxNzMzNTY5NjAzLCJpc3MiOiJzcC1wYW5kYS1tYXJrZXQifQ.15l2pH_P-kh0tMemBn521wnFwugH38QssNljRzNa3Qo" No newline at end of file
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에 올라가지 않을거예요~ 

Comment on lines +25 to +49
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>
);
};

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 값만 넣어주세요~

Comment on lines +19 to +29
const handleResize = debounce(() => {
const currentWidth = window.innerWidth;

if (currentWidth >= 1024) {
if (currentWidth >= BREAKPOINTS.desktop) {
setScreenType('desktop');
} else if (currentWidth >= 768) {
} else if (currentWidth >= BREAKPOINTS.tablet) {
setScreenType('tablet');
} else {
setScreenType('mobile');
}
};
}, 250);
Copy link
Collaborator

Choose a reason for hiding this comment

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

리뷰 반영 굳 입니다. 👍

Comment on lines +12 to +13
const [image, setImage] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
Copy link
Collaborator

Choose a reason for hiding this comment

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

상태 변수를 하나로 좀 더 단순하게 할 수 있겠어요.

 const [imageState, setImageState] = useState<{
   file: File | null;
   preview: string | null;
 }>({
   file: null,
   preview: null
 });

Comment on lines +47 to +50
<div
className={styles.image}
onClick={() => fileInputRef.current?.click()}
>
Copy link
Collaborator

Choose a reason for hiding this comment

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

일반적으로 키보드 접근성 때문에 div 태그에 onClick 은 브라우저가 지원하지 않는 특수한 경우 말고는 사용하지 않아요~ button 태그를 사용해주세요.

Comment on lines +21 to +56
export async function getServerSideProps(context: GetServerSidePropsContext) {
try {
const bestProductsResponse = await getProducts({
orderBy: 'favorite',
page: 1,
pageSize: 1,
});
const allProductsResponse = await getProducts({
orderBy: 'recent',
page: 1,
pageSize: 10,
});

return {
props: {
initialBestProducts: bestProductsResponse.data.list,
initialAllProducts: allProductsResponse.data.list,
},
};
} catch (error) {
console.error('데이터를 불러오는 데 실패했습니다:', error);
return {
props: {
initialBestProducts: [],
initialAllProducts: [],
},
};
}
}

const Board = ({
initialBestProducts,
initialAllProducts,
}: {
initialBestProducts: Product[];
initialAllProducts: Product[];
Copy link
Collaborator

Choose a reason for hiding this comment

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

일반적으로 getServerSideProps 부분은 아래에 위치하고 Board 컴포넌트가 상위로 올라오는 구조로 작성합니다!

const Board = ({
  initialBestProducts,
  initialAllProducts,
}: {
  initialBestProducts: Product[];
  initialAllProducts: Product[];

...


export async function getServerSideProps(context: GetServerSidePropsContext) {
  try {
    const bestProductsResponse = await getProducts({
      orderBy: 'favorite',
      page: 1,
      pageSize: 1,
    });
    const allProductsResponse = await getProducts({
      orderBy: 'recent',
      page: 1,
      pageSize: 10,
    });

    return {
      props: {
        initialBestProducts: bestProductsResponse.data.list,
        initialAllProducts: allProductsResponse.data.list,
      },
    };
  } catch (error) {
    console.error('데이터를 불러오는 데 실패했습니다:', error);
    return {
      props: {
        initialBestProducts: [],
        initialAllProducts: [],
      },
    };
  }
}

@arthurkimdev arthurkimdev merged commit d10ae3c into codeit-bootcamp-frontend:Next-윤예지 Dec 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants