Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] feat: 리뷰 목록 페이지에 변경된 API 연동 후, 리액트 쿼리 적용 및 무한 스크롤 구현 #164

Closed
wants to merge 34 commits into from

Conversation

soosoo22
Copy link
Contributor

@soosoo22 soosoo22 commented Jul 31, 2024


🚀 어떤 기능을 구현했나요 ?

리뷰 목록페이지에 변경된 API 연동 후, 리액트 쿼리를 적용하고 무한 스크롤을 구현했어요.

🔥 어떻게 해결했나요 ?

변경된 API 적용

size, lastReviewId가 없어지고 revieweeName, projectName이 추가되었습니다.

export interface ReviewPreviewList {
  revieweeName: string;
  projectName: string;
  reviews: ReviewPreview[];
}

export interface ReviewPreview {
  id: number;
  createdAt: string;
  contentPreview: string;
  keywords: Keyword[];
}

getReviewListApi를 사용할 때 groupAccessCode를 인자로 받고 헤더에 groupAccessCode를 포함시킵니다.

export const getReviewListApi = async (groupAccessCode: string) => {
  const response = await fetch(endPoint.gettingReviewList, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      GroupAccessCode: groupAccessCode,
    },
  });

  if (!response.ok) {
    throw new Error(createApiErrorMessage(response.status));
  }

  const data = await response.json();
  return data as ReviewPreviewList;
};

리액트 쿼리

  • ReviewPreviewListPage에서 리액트 쿼리의 useInfiniteQuery 훅을 사용해서 리뷰 목록 데이터를 비동기적으로 가져오도록 했습니다.
  • 리뷰 목록 API 호출 함수인 getReviewListApi를 사용해서 데이터를 불러왔습니다.

src/index.tsx ⇒ 리액트 쿼리 적용을 위해 QueryClientProvider 추가했습니다.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';


const queryClient = new QueryClient();

// 중간 코드 생략...


root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={theme}>
        <Global styles={globalStyles} />
        <RouterProvider router={router} />
      </ThemeProvider>
    </QueryClientProvider>
  </React.StrictMode>,
);

무한 스크롤

  • useInfiniteQuery 훅을 사용해서 무한 스크롤 기능을 구현했습니다.
  • 페이지의 마지막 리뷰가 화면에 보일 때 자동으로 다음 페이지의 리뷰를 불러오기 위해 IntersectionObserver를 설정했습니다.

📝 어떤 부분에 집중해서 리뷰해야 할까요?

api가 계속 변경될 것 같아서 일단은 msw를 사용해서 목 데이터 기반으로 무한 스크롤 기능을 테스트해 주시기 바랄게요!

// src/index.tsx에 전 코드 주석 처리 후 아래 코드를 추가
async function enableMocking() {
  if (process.env.MSW) {
    const { worker } = await import('./mocks/browser');
    return worker.start();
  }
}

enableMocking().then(() => {
  root.render(
    <React.StrictMode>
      <QueryClientProvider client={queryClient}>
        <ThemeProvider theme={theme}>
          <Global styles={globalStyles} />
          <RouterProvider router={router} />
        </ThemeProvider>
      </QueryClientProvider>
    </React.StrictMode>,
  );
});

📚 참고 자료, 할 말

  • 레벨 2 미션 상품 목록 코드를 참고했습니다.
  • 무한스크롤 정말 어렵네요~~^^

@soosoo22 soosoo22 self-assigned this Jul 31, 2024
@soosoo22 soosoo22 changed the title [FE] feat: 리뷰 목록 페이지에 리액트 쿼리를 적용 및 무한 스크롤 구현 [FE] feat: 리뷰 목록 페이지에 리액트 쿼리 적용 및 무한 스크롤 구현 Jul 31, 2024
soosoo22 and others added 4 commits August 1, 2024 00:09
* feat: PR Request 생성 / Comment 시 디스코드 멘션

* fix: Case-sensitive ID로 해결, ALL webhook 추가

* fix: remove whitespace

* chore: rename workflow

* fix: fix shell script

* fix: step statement

* fix: remove trailing whitespace after equals sign
* feat: 랜덤한 문자열 생성기 구현

Co-authored-by: donghoony <aru0504@naver.com>

* feat: 리뷰 그룹 생성 요청, 응답 형식

Co-authored-by: donghoony <aru0504@naver.com>

* feat: 리뷰 그룹 생성

Co-authored-by: hyeonjilee <skylar12200@gmail.com>
Co-authored-by: nayonsoso <panda0329@naver.com>

* feat: 리뷰 작성 컨트롤러

Co-authored-by: hyeonjilee <skylar12200@gmail.com>
Co-authored-by: nayonsoso <panda0329@naver.com>

---------

Co-authored-by: nayonsoso <panda0329@naver.com>
Co-authored-by: hyeonjilee <skylar12200@gmail.com>
* refactor: 불필요한 update 방지

* feat: controller에 리뷰 생성 요청에 대한 응답 기능 구현

* feat: controller에 리뷰 작성을 위해 필요한 정보를 응답 기능 구현

* feat: service에 리뷰 작성을 위해 필요한 정보 조회
 기능 구현

* chore: 사용하지 않는 dto 삭제

* test: 리뷰 리뷰 작성을 위해 필요한 정보 조회 기능 테스트 작성

* refactor: swagger 설정 일부 수정 및 에러 응답코드 노출되도록 변경

* refactor: service에서 원시타입 long을 반환하도록 변경

* docs: api 문서 dto 항목설명 변경

* test: 사용하지 않는 변수 선언하지 않도록 변경

* docs: api 문서 검증 내용에 대한 어체 변경

* refactor: 트랜잭션 조회 명시 추가

* refactor: dto 이름 변경
Copy link
Contributor

@BadaHertz52 BadaHertz52 left a comment

Choose a reason for hiding this comment

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

일단 무스를 끝낸 것에 박수를 보내요.👏👏👏👏👏👏
쑤쑤 리뷰 남겼어요.

return (
<S.Layout>
<S.Layout data-id={id}>
Copy link
Contributor

Choose a reason for hiding this comment

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

data-id를 사용한 이유가 있을까요?

data-id가 개발자 도구를 켰을때 보이는 거라 서버에서 받은 데이터가 개발자 도구에 바로 보일 거라 보안상 우려가되네요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#164 (comment)

아하.. 보안 이슈도 있군요..
에프이에게 비슷한 질문을 받아서 해당 답변을 읽어보면 될 것 같아요!

@@ -1,40 +1,25 @@
import GithubLogo from '@/assets/githubLogo.svg';
import Lock from '@/assets/lock.svg';
Copy link
Contributor

Choose a reason for hiding this comment

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

svg를 Icon을 뒤에 붙여주세요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

또또또 까먹었네요....😭

Comment on lines +22 to 23
<img src={isPublic ? UnLock : Lock} alt="자물쇠 아이콘" />
<span>{isPublic ? '공개' : '비공개'}</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

alt가 리뷰 공개/비공개에 따라 달라지면 좋겠어요.

리뷰 공개/비공개에 따른 src,alt, span의 text를 객체로 만들어서 관리하면 좋을것 같아요.

@@ -11,10 +11,10 @@ export const Layout = styled.div`

&:hover {
cursor: pointer;
border: 1px solid ${({ theme }) => theme.colors.primaryHover};
border: 1px solid ${({ theme }) => theme.colors.lightPurple};
Copy link
Contributor

Choose a reason for hiding this comment

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

'px' 또 찾았다!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

전체검색에서 px 찾아서 다 고쳤습니다~~~

export const useReviewPreviewList = () => {
const { data, fetchNextPage, hasNextPage, isLoading, error } = useInfiniteQuery({
queryKey: [QUERY_KEYS.reviews],
queryFn: ({ pageParam = 0 }) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

pageParam이 무엇일까요?
pageParam이 lastReviewId로 사용되는데, 제가 알고 있기로는 서버에서 lastReviewId를 넘겨주지 않으면 빈값이여야해요. 그런데 pageParam의 기본값이 0이라서 서버에서 lastReviewId가 없을 때 빈값이 아닌 0이 lastReviewId 값으로 들어가게 돼요. 그렇다면 실제 서버와 api연동 시 리뷰 목록 불러오기에서 오류가 날것 같아요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pageParam이 카드 목록 중 마지막 카드 아이템 id예요. 그래서 pageParam 값을 lastReviewId에 넣었어요.

생각해보니 pageParam이 0이 되면 안되는데 0값을 계속 넣어주고 있었네요ㅠㅠ 고마워요!

const limit = isFirstPage ? PAGE.defaultPageSize : PAGE.additionalPageSize;
const startIndex = isFirstPage
? PAGE.firstPageStartIndex
: REVIEW_PREVIEW_LIST.reviews.findIndex((review) => review.id === lastReviewId) + 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

1이라는 매직넘버 발견!

코드가 많아서 상수를 분리하는 것을 놓칠 수 있죠
이렇게 놓치는 것을 잡아주는데 코드 리뷰의 순기능일지도?
😊!!

{buttons.map((button, index) => (
<S.ButtonContainer key={index}>
{buttons.map((button) => (
<>
Copy link
Contributor

Choose a reason for hiding this comment

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

쑤쑤 ErrorSection의 변경 사항 제꺼에 반영했어요.
ButtonContainer가 삭제되면서 flagment 가 생겼는데 이유는 뭘까요?

Comment on lines +48 to +49
{isLoading && <LoadingPage />}
{error && <p>{error.message}</p>}
Copy link
Contributor

Choose a reason for hiding this comment

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

loading,error 에 Suspense, ErrorBoundary를 아직 적용이 안되는 건가요?

error시에도 error.message를 띄워주니 ErrorSection처럼 피그마에 적용한 오류 시안을 ui로 적용하고 있지않네요.


const lastReviewElementRef = useCallback(
(node: HTMLElement | null) => {
if (isLoading) return console.log('isLoading', isLoading);
Copy link
Contributor

Choose a reason for hiding this comment

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

질문1: console.log는 무한 스크롤을 확인하기 용도인가요? 그렇다면 push에서는 console.log를 지워주어야 하지 않나요?

질문2: 무한 스크롤로 데이터를 불러오는 동안, lastReviewElementRef에서 로딩 중임을 보여주는 UI가 필요해요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

질문1 답변 : 무한스크롤 확인하려고 넣었던 코드인데 지우질 않았네요😅

질문2 답변 : 그 부분을 제가 놓치고 있었네요. LoadingBar 컴포넌트를 넣을게요!

@@ -60,6 +60,12 @@ export interface WritingReviewInfoData {
keywords: Keyword[];
}

export interface ReviewPreviewList {
size: number;
lastReviewId: number;
Copy link
Contributor

Choose a reason for hiding this comment

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

lastReviewId은 타입도 number로 되어있어서 이거를 수정해야겠네요.

Copy link
Contributor

@BadaHertz52 BadaHertz52 left a comment

Choose a reason for hiding this comment

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

쑤쑤 우선 무스 기능 구현을 성공한 것에 박수를 보내요.👏👏👏👏👏👏👏👏 역시 쑤쑤가 해낼 줄 알았지

쑤쑤의 성장을 바라며 코멘트를 남겨뒀어요. 확인 부탁드려요.

Copy link
Contributor

@chysis chysis left a comment

Choose a reason for hiding this comment

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

미션 할 때 무한 스크롤 때문에 고생했던 기억이 눈에 선하네요🥲 정말 고생 많았어요
useInfiniteQuery를 사용한 부분은 함께 이야기 나눈 부분이라 별도로 피드백을 남기진 않았어요. 코멘트 확인 부탁해요!

@@ -11,10 +11,10 @@ export const Layout = styled.div`

&:hover {
cursor: pointer;
border: 1px solid ${({ theme }) => theme.colors.primaryHover};
border: 1px solid ${({ theme }) => theme.colors.lightPurple};
Copy link
Contributor

Choose a reason for hiding this comment

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

rem 단위로 통일시키면 좋을 것 같아요👀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아이고~~~~ 또 px을 썼네요😭
다시는 이런 실수를 반복하지 않겠습니다!!!!!!

Comment on lines 40 to 49
{buttons.map((button) => (
<>
<Button
buttonType={button.buttonType}
text={button.text}
image={button.image}
imageDescription={button.imageDescription}
onClick={button.onClick}
/>
</S.ButtonContainer>
</>
Copy link
Contributor

Choose a reason for hiding this comment

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

<S.ButtonContainer> 내부에서 map을 사용하고, Button 컴포넌트에 key를 추가하는 것은 어떨까요?

<S.Container>
  <S.ButtonContainer>
    {buttons.map((button) => (
      <Button
        // ...
      />
    )}
  </S.ButtonContainer>
</S.Container>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아하 key를 추가하는 게 좋겠네요!
<S.ButtonContainer>는 불필요해서 제거했고 <S.Container>안에서 map을 돌리고 Buttonkey를 추가했어요!

 <S.Container>
        {buttons.map((button, index) => (
          <>
            <Button
              key={index}
              buttonType={button.buttonType}
              text={button.text}
              image={button.image}
              imageDescription={button.imageDescription}
              onClick={button.onClick}
            />
          </>
        ))}
      </S.Container>

return (
<S.Layout>
<S.Layout data-id={id}>
Copy link
Contributor

Choose a reason for hiding this comment

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

data attribute를 사용한 이유가 궁금해요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

export interface ReviewPreview {
  id: number;
  isPublic: boolean;
  reviewerGroup: {
    id: number;
    name: string;
    thumbnailUrl: string;
  };
  createdAt: string;
  contentPreview: string;
  keywords: Keyword[];
}

ReviewPreviewCard 컴포넌트에는 원래 reviewerGroup, createdAt, contentPreview, keywords, isPublic만 필요했지만, 새로운 인터페이스를 정의하지 않고 기존의 ReviewPreview 인터페이스를 재사용하기 위해 id를 추가했어요. id를 사용할 자리가 없어 data-id 속성을 활용했어요.

Copy link
Contributor

Choose a reason for hiding this comment

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

data-id={id}

서버에서 받아온 리뷰의 id를 data-id에 써서, html에서 리뷰 id가 노출될 것 같아요.
data-id가 어디에서 사용되고 있는건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ReviewPreviewCard 컴포넌트에서 사용되고 있어요. id 제거할게요!

Copy link
Contributor

@ImxYJL ImxYJL left a comment

Choose a reason for hiding this comment

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

무한 스크롤 끝까지 붙잡고 있던 쑤쑤에게 박수~
크리티컬한 건 이미 바다가 잡아줘서 저는 비교적 간단한 코멘트만 남기고 어프루브 할게요!

`;

export const HeaderContainer = styled.div`
Copy link
Contributor

Choose a reason for hiding this comment

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

Container에서 Content로 이름을 바꾼 이유가 궁금합니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Layout 안에 Header, Main을 기준으로 Header안에 HeaderContainer가 있는 게 어색한 것 같아서 HeaderContent로 바꿨어요!

<S.Layout>
  <S.Header>
    <S.HeaderContent />
  </S.Header>
  <S.Main />
</S.Layout>

defaultPageSize: 5,
additionalPageSize: 2,
};

const getDetailedReview = () =>
http.get(endPoint.gettingDetailedReview(123456, 123456), async ({ request }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

이렇게 123456을 사용하는 부분도 가능하다면 상수화나 간단한 주석이 있으면 나중에 알아보기 더 편할 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아하! 고마워용!!! 상수처리 너무 중요하죠!

const getReviewPreviewList = () =>
http.get(endPoint.gettingReviewList(1, 3, 1), async ({ request }) => {
return HttpResponse.json(REVIEW_PREVIEW_LIST);
const getReviewPreviewList = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

이렇게 디테일하게 get 핸들러 함수를 작성한 이유는 무한 스크롤 로직(API가 보내주는 데이터를 프론트에서 의도한 대로 처리하고 있는지)이 정상적으로 작동하는지 확인하기 위함이었나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

네 맞아요. 서버에는 리뷰 목록 개수가 많지 않아서 무한스크롤을 제대로 확인하기 힘들어서 get 핸들러 함수에 디폴트 페이지, 추가적으로 불러오는 페이지 개수 등을 구체적으로 명시해서 제대로 기능이 작동하는지 확인했어요

if (isLoading) return console.log('isLoading', isLoading);
if (observer.current) observer.current.disconnect();

observer.current = new IntersectionObserver((entries) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

지금은 바빠서 무리겠지만 ㅎㅎ;
IntersectionObserver를 이용한 무한 스크롤 로직은 별도의 훅으로 분리해보면 좋을 것 같아요.
제가 예전에 무한스크롤 훅에 대한 글을 썼었는데 참고해보세용~~ 이거

Copy link
Contributor Author

Choose a reason for hiding this comment

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

추후 리팩할 부분 중 하나였는데 짚어주셔서 너무 감사해요!!!
참고자료까지 너무 고마워요 올리❤️

soosoo22 and others added 4 commits August 1, 2024 15:01
* fix: src/index.tsx에서 enableMocking 제거

* feat: 리뷰 그룹 생성 시 코멘트 컴포넌트명 변경 및 기본값 설정

- 컴포넌트명 변경: RevewComment -> ReveweeComments
-  reviewGroup의 description이 빈문자열이면 기본값을 보여주는 것으로 변경

* refactor: 서버 DB에 있는 리뷰 데이터를 사용하기 위한 상수화

- 현재 DB에 있는 리뷰 데이터를 목 서버에서도 사용하고, 사이드바 페이지 이동 시에도 활용할 수 있도록 관련 value들을 상수화 함

* feat : QueryClient, QueryClientProvider 적용

- src/index.tsx에 QueryClient, QueryClientProvider 적용

* fix: dependencies에 있는 테스트 패키지들을 devDependencies로 옮김

* feat: 리뷰 상세페이지에 react-query 적용

* feat : msw에 리뷰 상세페이지 404 오류 추가

* ci: react-error-boundary 설치

* feat: Outlet에 QueryErrorResetBoundary,ErrorBoundary, Suspense 적용

* feat: 리뷰 상세페이지에 useSuspenseQuery 적용

* refactor: 리뷰 상세페이지 resource, queryString key 상수화

* refactor:  리뷰 상세페이지 react-query key 상수화

* refactor: 리뷰 상세 페이지 컴포넌트 속에서만 사용하는 상수들 상수화

* refactor: DetailedReviewPage/components에 index.tsx를 추가해 import 경로 간결하게 수정

* feat: error 전파를 위한 QueryClient 옵션 추가

-  react-query의 query, mutation에서 error가 전파되도록 QueryClient 옵션 설정

* fix: ErrorPage의 SideModal에 closeModal props로 줌

* refactor: ErrorSection 위치 변경(src/pages/ErrorPage -> src/components/error)

* feat: ErrorFallback 컴포넌트 생성

* feat: ErrorSuspenseContainer 생성 및 App.tsx에 적용

* chore: constants/index.ts export 경로 변경

- 중복되는 apiErrorMessage 삭제
- queryKeys 추가

* chore: 3차-1주차 핵심 기능 시현 때 필요 없는 코드 주석 처리

* docs: ErrorPage의 ERROR_MESSAGE 수정

* design: formWidth 변경 및 fontSize에 1.4rem 추가

* feat: 리뷰 상세 페이지에 리뷰이 이름 추가

- 리뷰 상세 페이지 목데이터, 데이터 타입에 리뷰이 이름 추가
- 리뷰 상세 페이지 컴포넌트에 리뷰이 이름 추가 및 관련 스타일 추가

* refactor: 불필요한 export 삭제

* chore: type명 수정 (RevieweeCommentProps =>RevieweeCommentsProps)

* refactor: ErrorSection으l Button 수정

* refactor: 리뷰 상세 페이지 데이터 타입 변경에 따른 수정

* refactor: ErrorSuspenseContainer 적용 위치 변경

- App가 아닌 router의 element에서 적용하는 것으로 변경

* refactor: 리뷰 상세 페이지 데이터 타입 강제 방법 변경

* chore: 불필요한 주석 삭제

* refactor: ErrorSection의 buttons 네이밍 변경 및 요소에 key 추가

- buttons -> buttonList

* chore: 스타일 주석에 NOTE 추가
* feat: 리뷰 미리보기

Co-authored-by: nayonsoso <panda0329@naver.com>

* feat: 내가 받은 리뷰 목록 응답 생성

Co-authored-by: donghoony <aru0504@naver.com>

* feat: 리뷰 목록 조회

Co-authored-by: nayonsoso <panda0329@naver.com>

* refactor: Cascade 적용으로 불필요한 save 제거

Co-authored-by: nayonsoso <panda0329@naver.com>

* refactor: 리뷰 미리보기 생성 객체 도출

Co-authored-by: nayonsoso <panda0329@naver.com>

---------

Co-authored-by: nayonsoso <panda0329@naver.com>
@soosoo22 soosoo22 requested review from chysis and BadaHertz52 August 1, 2024 12:20
Copy link

github-actions bot commented Aug 1, 2024

Test Results

24 tests  +12   24 ✅ +12   1s ⏱️ ±0s
10 suites + 4    0 💤 ± 0 
10 files   + 4    0 ❌ ± 0 

Results for commit e237151. ± Comparison against base commit 24e8d94.

Copy link
Contributor

@BadaHertz52 BadaHertz52 left a comment

Choose a reason for hiding this comment

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

쑤쑤 고생 많았어요.

Copy link
Contributor

@chysis chysis left a comment

Choose a reason for hiding this comment

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

저엉말 고생많았어요😵‍💫👍

@soosoo22 soosoo22 requested a review from ImxYJL August 1, 2024 12:42
Copy link
Contributor

@ImxYJL ImxYJL left a comment

Choose a reason for hiding this comment

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

우하하

@soosoo22 soosoo22 requested review from chysis and BadaHertz52 August 1, 2024 12:45
Copy link
Contributor

@BadaHertz52 BadaHertz52 left a comment

Choose a reason for hiding this comment

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

😝

@soosoo22 soosoo22 requested a review from ImxYJL August 1, 2024 13:01
Copy link
Contributor

@ImxYJL ImxYJL left a comment

Choose a reason for hiding this comment

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

돼라!

@donghoony
Copy link
Contributor

수수 최고..

@soosoo22 soosoo22 requested a review from donghoony August 1, 2024 13:04
@soosoo22 soosoo22 requested a review from chysis August 1, 2024 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants