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

✨ #85 홈피드 구현 및 플레이리스트 타입 정리 #122

Merged
merged 6 commits into from
Sep 9, 2024

Conversation

seoyoonyi
Copy link
Contributor

@seoyoonyi seoyoonyi commented Sep 8, 2024

📋 풀리퀘스트 관련 코멘트

작업하면서 공통컴포넌트도 쪼개고, 파일이동 대거 했습니다.
플레이리스트 타입이 중복으로 되어 있어서 정리했고
홈피드 구현했습니다.
물론 무한스크롤링 적용을 했지만, api 양이 적기때문에... 늘려서 다시 테스트 해봐야될것 같습니다.

Sourcery에 의한 요약

팔로우한 사용자의 재생 목록을 표시하기 위해 무한 스크롤이 가능한 홈 피드를 구현하고, 일관성을 높이기 위해 재생 목록 유형을 리팩토링합니다. 사용자 정보와 재생 목록 썸네일을 표시하기 위한 새로운 구성 요소를 도입하고, 이러한 새로운 기능을 활용하기 위해 기존 구성 요소를 개선합니다.

새로운 기능:

  • 팔로우한 사용자의 재생 목록을 표시하기 위해 무한 스크롤이 가능한 홈 피드를 구현합니다.
  • 이미지를 그리드 형식으로 표시하기 위한 새로운 ImageGrid 구성 요소를 추가합니다.
  • 재생 목록 썸네일을 그리드 형식으로 표시하기 위한 PlaylistThumbnailFeed 구성 요소를 도입합니다.
  • 재생 목록과 함께 사용자 정보를 표시하기 위한 UserInfo 구성 요소를 추가합니다.

개선 사항:

  • 중복을 제거하고 유형 일관성을 개선하기 위해 재생 목록 유형을 리팩토링합니다.
  • 썸네일을 표시하기 위해 새로운 ImageGrid 구성 요소를 사용하도록 MusicItem 구성 요소를 업데이트합니다.
  • 재생 목록을 가져오기 위해 promise 기반 접근 방식을 사용하도록 getFollowingUsersPlaylists 함수를 수정합니다.
Original summary in English

Summary by Sourcery

Implement the home feed with infinite scrolling to display playlists from followed users, and refactor playlist types for better consistency. Introduce new components for displaying user information and playlist thumbnails, and enhance existing components to utilize these new features.

New Features:

  • Implement the home feed with infinite scrolling to display playlists from followed users.
  • Add a new ImageGrid component for displaying a grid of images.
  • Introduce a PlaylistThumbnailFeed component to display playlist thumbnails in a grid format.
  • Add a UserInfo component to display user information alongside playlists.

Enhancements:

  • Refactor playlist types to remove redundancy and improve type consistency.
  • Update the MusicItem component to use the new ImageGrid component for displaying thumbnails.
  • Modify the getFollowingUsersPlaylists function to use a promise-based approach for fetching playlists.

@seoyoonyi seoyoonyi added the ✨ Feature 기능 개발 label Sep 8, 2024
@seoyoonyi seoyoonyi self-assigned this Sep 8, 2024
Copy link

sourcery-ai bot commented Sep 8, 2024

리뷰어 가이드 by Sourcery

이 풀 리퀘스트는 홈 피드 기능을 구현하고 재생목록 유형을 재구성합니다. 홈페이지, 재생목록 구성 요소 및 관련 서비스에 대한 중요한 변경 사항이 포함되어 있습니다. 구현은 팔로우한 사용자의 재생목록을 가져오고 표시하는 데 중점을 두며, 무한 스크롤링과 개선된 UI 구성 요소를 제공합니다.

파일 수준 변경 사항

변경 사항 세부 사항 파일
무한 스크롤링이 가능한 홈 피드 구현
  • 페이지네이션 데이터 가져오기를 위한 useInfiniteQuery 훅 추가
  • 무한 스크롤링을 위한 스크롤 이벤트 리스너 구현
  • 피드에서 재생목록을 표시하기 위한 UI 구성 요소 생성
  • 로딩 상태 및 빈 상태 처리 추가
src/pages/HomePage.tsx
src/service/playlist/getPaginatedFollowingUsersPlaylists.ts
재생목록 유형 및 구성 요소 리팩토링
  • PlaylistBaseProps 및 관련 인터페이스 업데이트
  • 새로운 ImageGrid 및 PlaylistThumbnailFeed 구성 요소 생성
  • MusicItem 구성 요소를 새로운 ImageGrid를 사용하도록 수정
  • 새로운 유형에 맞게 재생목록 가져오기 함수 업데이트
src/types/playlistType.d.ts
src/components/playlist/MusicItem.tsx
src/components/common/ImageGrid.tsx
src/components/playlist/PlaylistThumbnailFeed.tsx
src/service/playlist/getUserPlaylists.ts
재생목록 썸네일 표시 개선
  • 유연한 썸네일 레이아웃을 위한 ImageGrid 구성 요소 생성
  • PlaylistThumbnails 구성 요소를 ImageGrid를 사용하도록 업데이트
  • 재생목록 썸네일에 재생 버튼 오버레이 추가
src/components/common/ImageGrid.tsx
src/components/search/PlaylistThumbnails.tsx
src/components/playlist/PlaylistThumbnailFeed.tsx
사용자 경험 및 UI 구성 요소 향상
  • 사용자 정보를 표시하기 위한 UserInfo 구성 요소 생성
  • LikeButton 구성 요소 및 사용법 업데이트
  • 다양한 구성 요소의 스타일링 및 레이아웃 개선
src/components/playlist/UserInfo.tsx
src/components/common/Button/LikeButton.tsx
src/pages/CreatePlaylistPage.tsx
src/pages/ProfilePage.tsx
src/components/layout/Navbar.tsx

  • 풀 리퀘스트에 @sourcery-ai review라고 댓글을 달아 새로운 Sourcery 리뷰를 트리거하세요.
  • 리뷰 댓글에 직접 답변하여 Sourcery와의 논의를 계속할 수 있습니다.
  • 대시보드에 접속하여 언제든지 리뷰 설정을 변경할 수 있습니다:
    • Sourcery가 생성한 풀 리퀘스트 요약 또는 리뷰어 가이드 활성화 또는 비활성화;
    • 리뷰 언어 변경;
  • 질문이나 피드백이 있으면 언제든지 문의하세요.
Original review guide in English

Reviewer's Guide by Sourcery

This pull request implements the home feed functionality and reorganizes playlist types. It includes significant changes to the homepage, playlist components, and related services. The implementation focuses on fetching and displaying playlists from followed users, with infinite scrolling and improved UI components.

File-Level Changes

Change Details Files
Implemented home feed with infinite scrolling
  • Added useInfiniteQuery hook for paginated data fetching
  • Implemented scroll event listener for infinite scrolling
  • Created UI components for displaying playlists in the feed
  • Added loading states and empty state handling
src/pages/HomePage.tsx
src/service/playlist/getPaginatedFollowingUsersPlaylists.ts
Refactored playlist types and components
  • Updated PlaylistBaseProps and related interfaces
  • Created new ImageGrid and PlaylistThumbnailFeed components
  • Modified MusicItem component to use new ImageGrid
  • Updated playlist fetching functions to match new types
src/types/playlistType.d.ts
src/components/playlist/MusicItem.tsx
src/components/common/ImageGrid.tsx
src/components/playlist/PlaylistThumbnailFeed.tsx
src/service/playlist/getUserPlaylists.ts
Improved playlist thumbnail display
  • Created ImageGrid component for flexible thumbnail layouts
  • Updated PlaylistThumbnails component to use ImageGrid
  • Added play button overlay to playlist thumbnails
src/components/common/ImageGrid.tsx
src/components/search/PlaylistThumbnails.tsx
src/components/playlist/PlaylistThumbnailFeed.tsx
Enhanced user experience and UI components
  • Created UserInfo component for displaying user information
  • Updated LikeButton component and its usage
  • Improved styling and layout of various components
src/components/playlist/UserInfo.tsx
src/components/common/Button/LikeButton.tsx
src/pages/CreatePlaylistPage.tsx
src/pages/ProfilePage.tsx
src/components/layout/Navbar.tsx

Tips
  • Trigger a new Sourcery review by commenting @sourcery-ai review on the pull request.
  • Continue your discussion with Sourcery by replying directly to review comments.
  • You can change your review settings at any time by accessing your dashboard:
    • Enable or disable the Sourcery-generated pull request summary or reviewer's guide;
    • Change the review language;
  • You can always contact us if you have any questions or feedback.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

안녕하세요 @seoyoonyi - 변경 사항을 검토했으며 훌륭합니다!

검토 중에 살펴본 내용입니다
  • 🟡 일반 문제: 3개의 문제가 발견되었습니다
  • 🟢 보안: 모두 양호합니다
  • 🟢 테스트: 모두 양호합니다
  • 🟡 복잡성: 1개의 문제가 발견되었습니다
  • 🟢 문서화: 모두 양호합니다

Sourcery는 오픈 소스에 무료입니다 - 리뷰가 마음에 드셨다면 공유를 고려해 주세요 ✨
더 유용하게 도와주세요! 각 댓글에 대해 👍 또는 👎를 클릭하여 도움이 되었는지 알려주세요.
Original comment in English

Hey @seoyoonyi - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 3 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.

const HomePage = () => {
return <div>Home</div>
const navigate = useNavigate()
const [allPlaylists, setAllPlaylists] = useState<FollowedPlaylist[]>([])
Copy link

Choose a reason for hiding this comment

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

suggestion (performance): 대규모 데이터셋에서 성능을 향상시키기 위해 가상화된 리스트를 사용하는 것을 고려하세요

플레이리스트 목록이 증가함에 따라 모든 항목을 한 번에 렌더링하면 성능 문제가 발생할 수 있습니다. react-window 또는 react-virtualized와 같은 가상화된 리스트 컴포넌트를 사용하여 보이는 항목만 렌더링하는 것을 고려하세요.

import { useState } from 'react'
import { FixedSizeList as List } from 'react-window'
import { FollowedPlaylist } from '../types'

const HomePage = () => {
  const [allPlaylists, setAllPlaylists] = useState<FollowedPlaylist[]>([])
  const [virtualizedPlaylists, setVirtualizedPlaylists] = useState<FollowedPlaylist[]>([])
Original comment in English

suggestion (performance): Consider using a virtualized list for better performance with large datasets

As the list of playlists grows, rendering all items at once can lead to performance issues. Consider using a virtualized list component like react-window or react-virtualized to render only the visible items.

import { useState } from 'react'
import { FixedSizeList as List } from 'react-window'
import { FollowedPlaylist } from '../types'

const HomePage = () => {
  const [allPlaylists, setAllPlaylists] = useState<FollowedPlaylist[]>([])
  const [virtualizedPlaylists, setVirtualizedPlaylists] = useState<FollowedPlaylist[]>([])

Comment on lines +33 to +34
const handleScroll = useCallback(() => {
if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100) {
Copy link

Choose a reason for hiding this comment

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

suggestion (performance): 고정 픽셀 값 대신 뷰포트 높이의 백분율을 스크롤 트리거로 사용하세요

고정 픽셀 값(100)을 사용하는 것은 다양한 장치와 화면 크기에서 잘 작동하지 않을 수 있습니다. 대신 뷰포트 높이의 백분율을 사용하는 것을 고려하세요, 예를 들어 '90%'의 document.documentElement.scrollHeight.

const handleScroll = useCallback(() => {
  const scrollThreshold = 0.9; // 문서 높이의 90%
  if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight * scrollThreshold) {
    if (hasNextPage && !isFetchingNextPage) {
      fetchNextPage();
    }
  }
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
Original comment in English

suggestion (performance): Use percentage of viewport height for scroll trigger instead of fixed pixel value

Using a fixed pixel value (100) might not work well across different devices and screen sizes. Consider using a percentage of the viewport height instead, e.g., '90%' of document.documentElement.scrollHeight.

const handleScroll = useCallback(() => {
  const scrollThreshold = 0.9; // 90% of the document height
  if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight * scrollThreshold) {
    if (hasNextPage && !isFetchingNextPage) {
      fetchNextPage();
    }
  }
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);

Comment on lines +14 to +15
if (thumbnails.length === 3) {
displayThumbnails.push(defaultThumbnail)
Copy link

Choose a reason for hiding this comment

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

suggestion: 세 개의 썸네일을 처리하는 접근 방식을 재고하세요

정확히 세 개의 썸네일이 있을 때 기본 썸네일을 추가하는 것은 임의로 보입니다. 썸네일 수에 따라 그리드 레이아웃을 조정하거나 이 경우 사용자 정의 동작을 위한 prop을 허용하는 등 더 유연한 접근 방식을 고려하세요.

const gridTemplateAreas = {
  1: '"a"',
  2: '"a b"',
  3: '"a b" "c ."',
  4: '"a b" "c d"'
};

const gridStyle = {
  display: 'grid',
  gridTemplateAreas: gridTemplateAreas[thumbnails.length] || gridTemplateAreas[4]
};
Original comment in English

suggestion: Reconsider the approach for handling three thumbnails

Adding a default thumbnail when there are exactly three thumbnails seems arbitrary. Consider a more flexible approach, such as adjusting the grid layout based on the number of thumbnails, or allowing the component to accept a prop for custom behavior in this case.

const gridTemplateAreas = {
  1: '"a"',
  2: '"a b"',
  3: '"a b" "c ."',
  4: '"a b" "c d"'
};

const gridStyle = {
  display: 'grid',
  gridTemplateAreas: gridTemplateAreas[thumbnails.length] || gridTemplateAreas[4]
};

import { fontSize, fontWeight } from '@/constants/font'
import { useNavigate } from 'react-router-dom'
import { FollowedPlaylist, FollowedUserPlaylists } from '@/types/playlistType'

const HomePage = () => {
Copy link

Choose a reason for hiding this comment

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

issue (complexity): 코드 조직 및 재사용성을 개선하기 위해 HomePage 컴포넌트를 리팩토링하는 것을 고려하세요.

변경 사항은 중요한 새로운 기능을 도입하지만 복잡성을 줄일 수 있는 기회가 있습니다:

  1. 무한 스크롤 로직을 사용자 정의 훅으로 추출:
// useInfiniteScroll.js
import { useCallback, useEffect } from 'react';

const useInfiniteScroll = (hasNextPage, isFetchingNextPage, fetchNextPage) => {
  const handleScroll = useCallback(() => {
    if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100) {
      if (hasNextPage && !isFetchingNextPage) {
        fetchNextPage();
      }
    }
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);
};

export default useInfiniteScroll;

HomePage에서 사용:

useInfiniteScroll(hasNextPage, isFetchingNextPage, fetchNextPage);
  1. 플레이리스트 렌더링을 별도의 컴포넌트로 이동:
const PlaylistItem = ({ playlist, onUserClick, onPlaylistClick }) => (
  <li className="playlist-container">
    <UserInfo
      authorName={playlist.authorName}
      authorImg={playlist.authorImg}
      createdAt={playlist.createdAt}
      className="user-info-container"
      onClick={() => onUserClick(playlist.authorId)}
    />
    <div onClick={() => onPlaylistClick(playlist.playlistId)} className="playlist-detail-container">
      <h3 className="playlist-title">{truncateText(playlist.title, 60)}</h3>
      <PlaylistThumbnailFeed thumbnails={playlist.thumbnails} />
    </div>
    <LikeButton playlistId={playlist.playlistId || ''} />
  </li>
);
  1. 데이터 변환을 단순화:
useEffect(() => {
  if (data?.pages) {
    const mergedPlaylists = data.pages
      .flatMap(page => page.flatMap(user => user.playlists))
      .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
    setAllPlaylists(mergedPlaylists);
  }
}, [data]);
  1. 대규모 데이터셋에서 성능 문제가 발생할 경우 react-window를 사용하여 효율적인 리스트 렌더링을 고려하세요.

이러한 변경 사항은 기능을 유지하면서 코드 조직 및 유지 보수성을 향상시킬 것입니다.

Original comment in English

issue (complexity): Consider refactoring the HomePage component to improve code organization and reusability.

While the changes introduce important new features, there are opportunities to reduce complexity:

  1. Extract infinite scrolling logic into a custom hook:
// useInfiniteScroll.js
import { useCallback, useEffect } from 'react';

const useInfiniteScroll = (hasNextPage, isFetchingNextPage, fetchNextPage) => {
  const handleScroll = useCallback(() => {
    if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100) {
      if (hasNextPage && !isFetchingNextPage) {
        fetchNextPage();
      }
    }
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);
};

export default useInfiniteScroll;

Use it in HomePage:

useInfiniteScroll(hasNextPage, isFetchingNextPage, fetchNextPage);
  1. Move playlist rendering to a separate component:
const PlaylistItem = ({ playlist, onUserClick, onPlaylistClick }) => (
  <li className="playlist-container">
    <UserInfo
      authorName={playlist.authorName}
      authorImg={playlist.authorImg}
      createdAt={playlist.createdAt}
      className="user-info-container"
      onClick={() => onUserClick(playlist.authorId)}
    />
    <div onClick={() => onPlaylistClick(playlist.playlistId)} className="playlist-detail-container">
      <h3 className="playlist-title">{truncateText(playlist.title, 60)}</h3>
      <PlaylistThumbnailFeed thumbnails={playlist.thumbnails} />
    </div>
    <LikeButton playlistId={playlist.playlistId || ''} />
  </li>
);
  1. Simplify data transformation:
useEffect(() => {
  if (data?.pages) {
    const mergedPlaylists = data.pages
      .flatMap(page => page.flatMap(user => user.playlists))
      .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
    setAllPlaylists(mergedPlaylists);
  }
}, [data]);
  1. Consider using react-window for efficient list rendering if performance becomes an issue with large datasets.

These changes will improve code organization and maintainability while preserving functionality.

Comment on lines +34 to +38
if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100) {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
}
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): 중첩된 if 조건을 병합하세요 (merge-nested-ifs)

Suggested change
if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100) {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
}
if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100 && (hasNextPage && !isFetchingNextPage)) {
fetchNextPage()
}


설명깊게 중첩된 조건부 코드를 읽는 것은 혼란스러울 수 있습니다. 어떤 조건이 어떤 수준과 관련이 있는지 추적해야 하기 때문입니다. 따라서 가능한 경우 중첩을 줄이려고 노력하며, 두 if 조건을 and로 결합할 수 있는 상황은 쉽게 개선할 수 있는 부분입니다.

Original comment in English

suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

Suggested change
if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100) {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
}
if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100 && (hasNextPage && !isFetchingNextPage)) {
fetchNextPage()
}


ExplanationReading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two if conditions can be combined using
and is an easy win.

}, [hasNextPage, isFetchingNextPage, fetchNextPage])

const truncateText = (text: string | undefined, maxLength: number) => {
if (!text) return ''
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): if, while 등에서 블록 중괄호를 사용하세요 (use-braces)

Suggested change
if (!text) return ''
if (!text) {


설명항상 중괄호를 사용하고 명시적인 문장 블록을 만드는 것이 권장됩니다.

단일 문장을 작성하기 위해 허용된 구문을 사용하는 것은 매우 혼란스러운 상황을 초래할 수 있습니다. 특히 이후에 개발자가 중괄호를 추가하는 것을 잊고 다른 문장을 추가할 경우(이 경우 조건에 포함되지 않음) 더욱 그렇습니다.

Original comment in English

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!text) return ''
if (!text) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

const unsubscribe = auth.onAuthStateChanged(async (user) => {
if (user) {
try {
const uid = user.uid
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): 속성을 접근하고 사용할 때 객체 구조 분해를 선호하세요 (use-object-destructuring)

Suggested change
const uid = user.uid
const {uid} = user


설명객체 구조 분해는 불필요한 임시 참조를 제거할 수 있으며, 코드를 더 간결하게 만듭니다.

Airbnb Javascript Style Guide에서 발췌

Original comment in English

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const uid = user.uid
const {uid} = user


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

const unsubscribe = auth.onAuthStateChanged(async (user) => {
if (user) {
try {
const uid = user.uid
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): 속성을 접근하고 사용할 때 객체 구조 분해를 선호하세요 (use-object-destructuring)

Suggested change
const uid = user.uid
const {uid} = user


설명객체 구조 분해는 불필요한 임시 참조를 제거할 수 있으며, 코드를 더 간결하게 만듭니다.

Airbnb Javascript Style Guide에서 발췌

Original comment in English

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const uid = user.uid
const {uid} = user


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

Copy link
Contributor

@jizerozz jizerozz left a comment

Choose a reason for hiding this comment

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

승인 승인~!

@jizerozz jizerozz merged commit d685e21 into main Sep 9, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ Feature 기능 개발
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants