-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/121/liked api #123
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
Feat/121/liked api #123
Conversation
Walkthrough์ด ๋ณ๊ฒฝ ์ฌํญ์ ์ฌ์ฉ์์ ์ข์์ ํญ๋ชฉ์ ๊ด๋ฆฌํ๊ธฐ ์ํ ๋น๋๊ธฐ ํจ์๋ค์ ํฌํจํ๋ Changes
Possibly related PRs
Suggested labels
Suggested reviewers
Warning Rate limit exceeded@HaeJungg has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 13 seconds before requesting another review. โ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. ๐ฆ How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. Warning There were issues while running some tools. Please review the errors and either fix the toolโs configuration or disable the tool if itโs a critical failure. ๐ง eslint
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsxOops! Something went wrong! :( ESLint: 8.57.1 ESLint couldn't determine the plugin "react-hooks" uniquely.
Please remove the "plugins" setting from either config or remove either plugin installation. If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team. Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? ๐ชง TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
๐storybook: https://67206cc6ff9d7a05a3528ff8-zhahhrscaq.chromatic.com/ |
|
๐storybook: https://67206cc6ff9d7a05a3528ff8-sunidxotzo.chromatic.com/ |
|
๐storybook: https://67206cc6ff9d7a05a3528ff8-klmsanctgy.chromatic.com/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
๐งน Outside diff range and nitpick comments (19)
src/app/(crew)/my-favorite/page.tsx (1)
1-1: import ๊ฒฝ๋ก ๋จ์ํ ์ ์ํ์ฌ import ๋ฌธ์์ ์ฌ์ฉ ์ค์ธ
@/src/components๊ฒฝ๋ก๋@/components๋ก ๋จ์ํํ ์ ์์ต๋๋ค. ์ด๋ ์ฝ๋์ ๊ฐ๋ ์ฑ์ ํฅ์์ํค๊ณ ๊ฒฝ๋ก ๊ด๋ฆฌ๋ฅผ ๋ ์ฉ์ดํ๊ฒ ๋ง๋ค ๊ฒ์ ๋๋ค.๋ค์๊ณผ ๊ฐ์ด ๋ณ๊ฒฝ์ ์ ์ํฉ๋๋ค:
-import LikedList from '@/src/components/gathering-list/liked-list-container'; +import LikedList from '@/components/gathering-list/liked-list-container';src/_queries/liked/liked-queries.ts (1)
4-9: ์ฟผ๋ฆฌ ๊ตฌํ์ด ์ ๋์์ผ๋, ๋ช ๊ฐ์ง ๊ฐ์ ์ฌํญ์ ์ ์๋๋ฆฝ๋๋ค.ํ์ฌ ๊ตฌํ์ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋ฅ์ ์ ์ํํ๊ณ ์์ต๋๋ค๋ง, ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ฌํญ๋ค์ ๊ณ ๋ คํด๋ณด์๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค:
- ์๋ฌ ํ์ ์ ์
- ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ staleTime ์ค์
- ํ์ด์ง๋ค์ด์ ๊ด๋ จ ๋ฉํ๋ฐ์ดํฐ ํ์ ์ ์
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํด๋ณด์๋ ๊ฒ์ ์ด๋จ๊น์?:
+import { UseQueryOptions } from '@tanstack/react-query'; + +interface LikedListResponse { + items: Array<any>; // TODO: ์ค์ ์์ดํ ํ์ ์ผ๋ก ๊ต์ฒด + meta: { + currentPage: number; + totalPages: number; + }; +} -export function useGetLikedListQuery(page: number) { +export function useGetLikedListQuery( + page: number, + options?: Omit<UseQueryOptions<LikedListResponse, Error>, 'queryKey' | 'queryFn'> +) { return useQuery({ queryKey: ['likedList', page], queryFn: () => getLikedList(page), + staleTime: 5 * 60 * 1000, // 5๋ถ + ...options, }); }src/_apis/liked/liked-apis.ts (1)
4-15: ํ์ด์ง ํฌ๊ธฐ๋ฅผ ์์๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ด ์ข์ต๋๋ค.ํ์ฌ ํ์ด์ง ํฌ๊ธฐ๊ฐ 6์ผ๋ก ํ๋์ฝ๋ฉ๋์ด ์์ต๋๋ค. ์ด๋ฅผ ์์๋ก ๋ถ๋ฆฌํ๋ฉด ์ ์ง๋ณด์์ฑ์ด ํฅ์๋ ๊ฒ์ ๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ ๊ฒ์ ์ ์ํฉ๋๋ค:
+const LIKED_LIST_PAGE_SIZE = 6; + export async function getLikedList(page: number): Promise<GatheringResponseType> { - const url = `/api/liked/memberLikes?page=${page}&size=6`; + const url = `/api/liked/memberLikes?page=${page}&size=${LIKED_LIST_PAGE_SIZE}`;src/components/gathering-list/liked-list-container.tsx (2)
3-8: import๋ฌธ์ ํ์ ๋ณ๋ก ๊ตฌ์ฑํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค.๋ ๋์ ๊ฐ๋ ์ฑ์ ์ํด import๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด ๊ทธ๋ฃนํํ๋ ๊ฒ์ด ์ข์ต๋๋ค:
- React ๊ด๋ จ
- ๋ด๋ถ API/์ฟผ๋ฆฌ
- ์ ํธ๋ฆฌํฐ
- ์ปดํฌ๋ํธ
๋ค์๊ณผ ๊ฐ์ด ์์ ํด๋ณด์ธ์:
import { useState } from 'react'; +// Internal APIs and queries import { addLike, removeLike } from '@/src/_apis/liked/liked-apis'; import { useGetLikedListQuery } from '@/src/_queries/liked/liked-queries'; +// Utilities import { ApiError } from '@/src/utils/api'; +// Components import Toast from '@/src/components/common/toast'; import LikedListPresenter from './liked-list-presenter';
10-12: ํ์ ์์ ์ฑ ๊ฐ์ ์ด ํ์ํฉ๋๋ค.ํ์ด์ง ์ํ์ ๋ฐ์ดํฐ์ ๋ํ ๋ช ์์ ์ธ ํ์ ์ ์๊ฐ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ํ์ ์ ์ถ๊ฐํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:
-export default function LikedList() { +export default function LikedList(): JSX.Element { - const [page, setPage] = useState(1); + const [page, setPage] = useState<number>(1);src/components/gathering-list/liked-list-presenter.tsx (3)
6-12: ์ธํฐํ์ด์ค ๋ฌธ์ํ ๊ฐ์ ์ ์์ธํฐํ์ด์ค์ ๊ฐ ํ๋กํผํฐ์ ๋ํ JSDoc ๋ฌธ์๋ฅผ ์ถ๊ฐํ๋ฉด ์ปดํฌ๋ํธ์ ์ฌ์ฉ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ด ํฅ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๋ฌธ์ํ๋ฅผ ์ถ๊ฐํด๋ณด์ธ์:
interface LikedListPresenterProps { + /** ํ์ด์ง๋ค์ด์ ๋ ๋ชจ์ ๋ฐ์ดํฐ */ gatheringData: GatheringResponseType; + /** ํ์ด์ง ๋ณ๊ฒฝ ์ ํธ์ถ๋๋ ํธ๋ค๋ฌ */ onPageChange: (page: number) => void; + /** ์ข์์ ์ถ๊ฐ ์ ํธ์ถ๋๋ ํธ๋ค๋ฌ */ onLike: (gatheringId: number) => void; + /** ์ข์์ ์ ๊ฑฐ ์ ํธ์ถ๋๋ ํธ๋ค๋ฌ */ onUnlike: (gatheringId: number) => void; + /** ํ์ฌ ํ์ด์ง ๋ฒํธ */ page: number; }
23-41: ๊ทธ๋ฆฌ๋ ๋ ์ด์์ ๊ด๋ จ ๊ฐ์ ์ฌํญ
min-height๊ฐ์ด ํ๋์ฝ๋ฉ๋์ด ์์ด ์ฝํ ์ธ ์์ ๋ฐ๋ผ ๋ ์ด์์ ์ด์๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.GatheringCard์likedprop์ด ํญ์true๋ก ์ค์ ๋์ด ์๋๋ฐ, ์ด๋ ๋ถํ์ํด ๋ณด์ ๋๋ค. ์ด๋ฏธ ์ข์์ ๋ชฉ๋ก์ด๋ฏ๋ก ์ ๊ฑฐํด๋ ๋ ๊ฒ ๊ฐ์ต๋๋ค.- liked
42-65: ํ์ด์ง๋ค์ด์ ์คํ์ผ ๊ตฌ์ฑ ๊ฐ์ ์ ์ํ์ฌ Tailwind์ CSS-in-JS๊ฐ ํผ์ฉ๋์ด ์์ด ์คํ์ผ ๊ด๋ฆฌ๊ฐ ๋ณต์กํด ๋ณด์ ๋๋ค. ์คํ์ผ ์ ์๋ฅผ ์์๋ก ๋ถ๋ฆฌํ๋ฉด ๊ฐ๋ ์ฑ๊ณผ ์ฌ์ฌ์ฉ์ฑ์ด ํฅ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์คํ์ผ์ ๋ถ๋ฆฌํด๋ณด์ธ์:
const PAGINATION_STYLES = { classNames: { control: cn( 'data-[active="true"]:text-blue-500 data-[active="true"]:font-bold', 'border-none bg-transparent hover:bg-transparent', ), }, styles: { control: { '&[data-active]': { backgroundColor: 'transparent', fontWeight: 'var(--pagination-active-font-weight)', color: 'var(--pagination-active-color)', boxShadow: 'none', }, }, }, } as const; // ์ฌ์ฉ์ <Pagination total={totalPages} value={page} onChange={onPageChange} {...PAGINATION_STYLES} size="sm" />src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (2)
31-39: ์ฝ๋ ์ค๋ณต์ ์ ๊ฑฐํ๊ณ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ํตํฉํด ์ฃผ์ธ์.์ข์์/์ข์์ ์ทจ์ ํธ๋ค๋ฌ์ ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง์ด ์ค๋ณต๋์ด ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ณตํต ์๋ฌ ์ฒ๋ฆฌ ํจ์๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ๋ ๊ฒ์ ์ ์๋๋ฆฝ๋๋ค:
+const handleApiError = (action: string, error: unknown) => { + const baseMessage = action === 'like' ? '์ฐํ๊ธฐ' : '์ฐํ๊ธฐ ํด์ '; + if (error instanceof ApiError) { + Toast({ + message: `${baseMessage}์ ์คํจํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด ์ฃผ์ธ์.`, + type: 'error' + }); + } else { + Toast({ + message: '์๊ธฐ์น ๋ชปํ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.', + type: 'error' + }); + } +}; const handleLike = async (gatheringId: number) => { try { await addLike(gatheringId); } catch (error) { - if (error instanceof ApiError) { - Toast({ message: `์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค: ${error.message}`, type: 'error' }); - } + handleApiError('like', error); } }; const handleUnlike = async (gatheringId: number) => { try { await removeLike(gatheringId); } catch (error) { - if (error instanceof ApiError) { - Toast({ message: `์ฐํ๊ธฐ ํด์ ์ ์คํจํ์ต๋๋ค: ${error.message}`, type: 'error' }); - } + handleApiError('unlike', error); } };
41-44: ํ์ฌ ๊ฒฝ๋ก ๊ฐ์ ธ์ค๊ธฐ ๋ฐฉ์์ ๊ฐ์ ํด ์ฃผ์ธ์.
window.location.href๋ ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์๋ง ์ฌ์ฉ ๊ฐ๋ฅํ๋ฉฐ, Next.js์ ๋ผ์ฐํ ์์คํ ๊ณผ ์๋ฒฝํ๊ฒ ํธํ๋์ง ์์ ์ ์์ต๋๋ค.Next.js์
usePathname์ ์ฌ์ฉํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:+import { usePathname } from 'next/navigation'; export default function GatheringListSection({ id }: GatheringListSectionProps) { const router = useRouter(); + const pathname = usePathname(); const handleLoginRedirect = () => { - const currentPath = window.location.href; + const currentPath = pathname; router.push(`/login?redirect=${encodeURIComponent(currentPath)}`); };src/components/common/gathering-card/scheduled-gathering-card/container.tsx (3)
Line range hint
27-57: ๋๋ฏธ ๋ฐ์ดํฐ์์ ๋ฏผ๊ฐํ ์ ๋ณด ์ ๊ฑฐ ํ์๋๋ฏธ ๋ฐ์ดํฐ์ ์ด๋ฉ์ผ๊ณผ ๊ฐ์ ๋ฏผ๊ฐํ ๊ฐ์ธ์ ๋ณด๊ฐ ํฌํจ๋์ด ์์ต๋๋ค. ์ค์ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์ฌ์ฉํ๋ ๋์ ๊ฐ๋ช ์ ์ด๋ฉ์ผ(์:
[email protected])์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.- email: '[email protected]', + email: '[email protected]',
26-26: API ํตํฉ ๊ตฌํ ์ ์ํ์ฌ TODO ์ฃผ์๋ค์ด API ํตํฉ๊ณผ ๊ด๋ จ๋์ด ์์ต๋๋ค:
- ๋ชจ๋ฌ ๋ฐ์ดํฐ ์ฐ๊ฒฐ
- ์ข์์ ํ ๊ธ ๋ก์ง
PR์ ๋ชฉ์ ์ ๋ง๊ฒ ๋ค์๊ณผ ๊ฐ์ ๊ตฌํ์ ์ ์๋๋ฆฝ๋๋ค:
useGetLikedListQuery๋ฅผ ํ์ฉํ ์ข์์ ์ํ ๊ด๋ฆฌ- ๋ชจ๋ฌ ๋ฐ์ดํฐ๋
useQuery๋ฅผ ํตํ ์ค์๊ฐ ๋ฐ์ดํฐ ํจ์นญAPI ํตํฉ ๊ตฌํ์ ๋ํ ์์ธํ ์ฝ๋ ์์๊ฐ ํ์ํ์๋ค๋ฉด ๋ง์ํด ์ฃผ์ธ์.
Also applies to: 65-65, 75-75
Line range hint
71-89: ์ปดํฌ๋ํธ ๊ตฌ์กฐ ๊ฐ์ ์ ์ํ์ฌ ๊ตฌํ์ ๋ํด ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ฌํญ์ ์ ์๋๋ฆฝ๋๋ค:
- ์๋ฌ ์ฒ๋ฆฌ ์ถ๊ฐ
- ๋ชจ๋ฌ ์ํ ๊ด๋ฆฌ ์ต์ ํ
- ๋ฐ์ดํฐ ๋ก๋ฉ ์ํ ์ฒ๋ฆฌ
๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๋ก ๊ฐ์ ํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:
const handleCardClick = useCallback(() => { try { setIsOpened(true); // ๋ฐ์ดํฐ ํจ์นญ ๋ก์ง } catch (error) { console.error('๋ชจ๋ฌ ๋ฐ์ดํฐ ๋ก๋ฉ ์ค ์ค๋ฅ:', error); // ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง } }, []); // ๋ก๋ฉ ์ํ ํ์ {isLoading ? ( <LoadingSpinner /> ) : dummyModalData && ( <GatheringDetailModalContainer opened={isOpened} close={() => setIsOpened(false)} data={dummyModalData} /> )}src/components/common/gathering-card/container.tsx (1)
48-60: ์ข์์ ํ ๊ธ ํธ๋ค๋ฌ์ ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ ์ด ํ์ํฉ๋๋ค.ํ์ฌ ๊ตฌํ์ ๊ธฐ๋ฅ์ ์ผ๋ก๋ ๋ฌธ์ ๊ฐ ์์ผ๋, ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ฌํญ์ ์ ์๋๋ฆฝ๋๋ค:
- ๋๊ด์ ์ ๋ฐ์ดํธ(Optimistic Update)๋ฅผ ํตํ ์ฆ๊ฐ์ ์ธ UI ๋ฐ์
- ์ค๋ณต ํด๋ฆญ ๋ฐฉ์ง๋ฅผ ์ํ ๋ก๋ฉ ์ํ ์ถ๊ฐ
- ๋ ๊ตฌ์ฒด์ ์ธ ์๋ฌ ๋ฉ์์ง ์ฒ๋ฆฌ
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํด๋ณด์๋ ๊ฒ์ ์ด๋จ๊น์?:
+ const [isLoading, setIsLoading] = useState(false); const handleLikeToggle = async () => { + if (isLoading) return; + setIsLoading(true); + const newLikedState = !isLiked; + setIsLiked(newLikedState); // ๋๊ด์ ์ ๋ฐ์ดํธ try { - if (isLiked) { + if (!newLikedState) { await onUnlike(id); - setIsLiked(false); } else { await onLike(id); - setIsLiked(true); } } catch (error) { + setIsLiked(!newLikedState); // ์คํจ์ ์๋ ์ํ๋ก ๋ณต๊ตฌ Toast({ - message: '์ฐ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์คํจํ์ต๋๋ค.', + message: `์ฐํ๊ธฐ ${newLikedState ? '์ถ๊ฐ' : '์ญ์ '}์ ์คํจํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.`, type: 'error' }); + } finally { + setIsLoading(false); } };src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx (1)
์ข์์ ๊ธฐ๋ฅ ๊ตฌํ์ด ๋ฏธ์์ฑ ์ํ์ ๋๋ค
์ฝ๋๋ฒ ์ด์ค ๋ถ์ ๊ฒฐ๊ณผ, ํ์ฌ ์ข์์ ๊ด๋ จ ๊ธฐ๋ฅ์ด ์์ ํ ๊ตฌํ๋์ง ์์ ๊ฒ์ผ๋ก ํ์ธ๋ฉ๋๋ค:
- ์คํ ๋ฆฌ๋ถ์์๋
liked: false์ํ๊ฐ๋ง ์ ์๋์ด ์์- ์ข์์ ๋ชฉ๋ก์ ์กฐํํ๋ API๋ง ๊ตฌํ๋์ด ์๊ณ (
getLikedList), ์ข์์/์ข์์ ์ทจ์ ๊ธฐ๋ฅ์ ์์ง ๊ตฌํ๋์ง ์์- ๋ชจ๋ฌ ์ปดํฌ๋ํธ์์ ์ข์์ ์ํ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ก์ง์ด ์์
๐ Analysis chain
Line range hint
58-187: ์ข์์ API์์ ์ฐ๋ ํ์ธ์ด ํ์ํฉ๋๋ค.๊ฐ ์คํ ๋ฆฌ์ ๋ฐ์ดํฐ์
liked: false๊ฐ ์ค์ ๋์ด ์๋๋ฐ, ์ด๊ฒ์ด ์๋ก ์ถ๊ฐ๋ ์ข์์ API์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฐ๋๋๋์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
Let me gather more information about the liked functionality implementation.
Let me check one more thing to see if there are any liked-related APIs in the codebase.
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: ์ข์์ API ๊ด๋ จ ์ปดํฌ๋ํธ์์ ์ฐ๋ ํ์ธ # ์ข์์ API ๊ด๋ จ ํ์ผ ๊ฒ์ echo "์ข์์ API ๊ด๋ จ ํ์ผ ๊ฒ์ ๊ฒฐ๊ณผ:" rg -l "liked-apis" # GatheringDetailModalContainer์์ ์ข์์ API ์ฌ์ฉ ์ฌ๋ถ ํ์ธ echo "\nGatheringDetailModalContainer์ ์ข์์ API ์ฌ์ฉ ํ์ธ:" ast-grep --pattern 'import { $_ } from "$_/liked-apis"' ./src/app/\(crew\)/crew/_components/gathering-detail-modal/container.tsxLength of output: 467
Script:
#!/bin/bash # Check the implementation of liked functionality in the modal component and container # Check modal component implementation echo "Modal component liked implementation:" rg -A 5 "liked" ./src/app/\(crew\)/crew/_components/gathering-detail-modal/gathering-detail-modal.tsx # Check container implementation for liked state handling echo "\nContainer liked state handling:" rg -A 5 "liked" ./src/app/\(crew\)/crew/_components/gathering-detail-modal/container.tsx # Check liked API implementation echo "\nLiked API implementation:" rg -A 5 "liked" ./src/_queries/liked/liked-queries.tsLength of output: 812
Script:
#!/bin/bash # Check liked API implementations and usages # Check liked API endpoints echo "Liked API endpoints:" rg -A 5 "liked-apis" ./src/_apis # Check if there are any like/unlike mutation implementations echo "\nLike/Unlike mutations:" rg -A 5 "mutation.*like"Length of output: 169
src/components/gathering-list/gathering-card-carousel.tsx (2)
11-16: ์ธํฐํ์ด์ค ๋ฌธ์ํ ๊ฐ์ ํ์์๋ก ์ถ๊ฐ๋ props์ ๋ํ JSDoc ๋ฌธ์ํ๋ฅผ ์ถ๊ฐํ๋ฉด ์ปดํฌ๋ํธ์ ์ฌ์ฉ์ฑ์ด ํฅ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๋ฌธ์ํ๋ฅผ ์ถ๊ฐํด๋ณด์ธ์:
interface GatheringCardCarouselProps { gatheringData: GatheringType[]; crewId: number; + /** ๋ชจ์ ์ข์์ ์ฒ๋ฆฌ ํธ๋ค๋ฌ */ onLike: (gatheringId: number) => Promise<void>; + /** ๋ชจ์ ์ข์์ ์ทจ์ ์ฒ๋ฆฌ ํธ๋ค๋ฌ */ onUnlike: (gatheringId: number) => Promise<void>; + /** ๋ก๊ทธ์ธ ๋ชจ๋ฌ ํ์ ํธ๋ค๋ฌ */ onShowLoginModal: () => void; }
30-32: ์ธ์ฆ ๋ก์ง ๋ฆฌํฉํ ๋ง ์ ์์ธ์ฆ ์ํ ํ์ธ ๋ก์ง์ ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
์ปค์คํ ํ ์ผ๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์:
// hooks/useAuth.ts export const useAuth = () => { const token = useAuthStore((state) => state.token); return { isLoggedIn: !!token, token }; };์ด๋ ๊ฒ ํ๋ฉด ๋ค๋ฅธ ์ปดํฌ๋ํธ์์๋ ๋์ผํ ์ธ์ฆ ๋ก์ง์ ์ฝ๊ฒ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.
src/components/gathering-list/gathering-list.stories.tsx (2)
1-8: QueryClient ์ค์ ์ ํ์ ์์ ์ฑ ์ถ๊ฐ ๊ถ์ฅQueryClient์ ๊ธฐ๋ณธ ์ค์ ์ด ๋๋ฝ๋์ด ์์ต๋๋ค. ํ์ ์์ ์ฑ๊ณผ ์ผ๊ด๋ ๋์์ ๋ณด์ฅํ๊ธฐ ์ํด defaultOptions์ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด QueryClient๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:
-const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 1, + refetchOnWindowFocus: false, + }, + }, +});
32-227: ํ ์คํธ ๋ฐ์ดํฐ์ ๋ค์์ฑ ๊ฐ์ ํ์๋ชจ๋ ๋ชจ์ ๋ฐ์ดํฐ๊ฐ ๋์ผํ ์ด๋ฏธ์ง URL์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๋ค์ํ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ํด ๋ค์ ์ฌํญ๋ค์ ๊ณ ๋ คํด๋ณด์ธ์:
- ๋ค์ํ ์ด๋ฏธ์ง URL ์ฌ์ฉ
- ๊ณผ๊ฑฐ ๋ ์ง ํฌํจ
- ๋ง๊ฐ๋ ๋ชจ์ ์ผ์ด์ค
- ์ต๋ ์ธ์์ด ๋ค์ํ ์ผ์ด์ค
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
๐ Files selected for processing (15)
src/_apis/liked/liked-apis.ts(1 hunks)src/_queries/liked/liked-queries.ts(1 hunks)src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx(3 hunks)src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx(2 hunks)src/app/(crew)/my-favorite/page.tsx(1 hunks)src/app/api/test-api/route.ts(0 hunks)src/components/common/gathering-card/container.tsx(3 hunks)src/components/common/gathering-card/scheduled-gathering-card/container.tsx(1 hunks)src/components/common/modal/confirm-modal.tsx(2 hunks)src/components/gathering-list/gathering-card-carousel.tsx(3 hunks)src/components/gathering-list/gathering-list.stories.tsx(1 hunks)src/components/gathering-list/gathering-list.tsx(0 hunks)src/components/gathering-list/liked-list-container.tsx(1 hunks)src/components/gathering-list/liked-list-presenter.tsx(1 hunks)src/types/gathering-data.d.ts(2 hunks)
๐ค Files with no reviewable changes (2)
- src/app/api/test-api/route.ts
- src/components/gathering-list/gathering-list.tsx
๐ Additional comments (15)
src/app/(crew)/my-favorite/page.tsx (1)
3-9: ์ปดํฌ๋ํธ ๊ตฌํ ๊ฒ์ฆ ํ์
์ปดํฌ๋ํธ๊ฐ ๋จ์ํ๋์ด ์ข์ต๋๋ค. ํ์ง๋ง LikedList ์ปดํฌ๋ํธ์ props๊ฐ ์ ๋ฌ๋์ง ์๋ ๊ฒ์ด ์๋๋ ๊ฒ์ธ์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ฌ LikedList ์ปดํฌ๋ํธ์ props ์๊ตฌ์ฌํญ์ ํ์ธํ๊ฒ ์ต๋๋ค:
โ Verification successful
LikedList ์ปดํฌ๋ํธ ๊ตฌํ์ด ์ ์ ํจ์ ํ์ธํ์ต๋๋ค
LikedList๋ ์ปจํ
์ด๋ ์ปดํฌ๋ํธ๋ก์ ๋ด๋ถ์ ์ผ๋ก ํ์ํ ์ํ์ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๊ณ ์์ด props๊ฐ ํ์ํ์ง ์์ต๋๋ค. ๋ค์ ์ฌํญ๋ค์ด ํ์ธ๋์์ต๋๋ค:
- ์ปจํ
์ด๋(
LikedList)๊ฐ ํ์ด์ง ์ํ์ ๋ฐ์ดํฐ fetching์ ๊ด๋ฆฌ - Presenter(
LikedListPresenter)์ ํ์ํ props๋ฅผ ์ ๋ฌ - React Query๋ฅผ ํตํ ๋ฐ์ดํฐ ๊ด๋ฆฌ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify LikedList component props requirements
# Check the component implementation
ast-grep --pattern 'interface $props {
$$$
}' components/gathering-list/liked-list-container.tsx
# Check for required props usage
rg -A 5 'LikedList.*=' components/gathering-list/liked-list-container.tsx
Length of output: 365
Script:
#!/bin/bash
# Let's first find the LikedList component file
fd LikedList
# Then check its implementation and props
rg -A 10 "export.*LikedList"
# Also search for any interface or type definitions related to LikedList
ast-grep --pattern 'interface $props {
$$$
}'
# Additional search for any props being used within LikedList
rg -A 5 "props\."
Length of output: 3854
src/_queries/liked/liked-queries.ts (2)
1-2: ํ์ํ ์์กด์ฑ๋ง ์ ํํ๊ฒ ๊ฐ์ ธ์ค๊ณ ์์ต๋๋ค.
ํ์์ ์ธ useQuery์ getLikedList API๋ง importํ์ฌ ๊น๋ํ๊ฒ ๊ตฌํ๋์์ต๋๋ค.
6-6: ์ฟผ๋ฆฌ ํค๊ฐ ์ ์ ํ ๊ตฌ์ฑ๋์ด ์๋์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
ํ์ฌ queryKey๊ฐ ['likedList', page]๋ก ๊ตฌ์ฑ๋์ด ์๋๋ฐ, ๋ค๋ฅธ ๊ณณ์์ ๋์ผํ ํค๋ฅผ ์ฌ์ฉํ๊ณ ์์ง ์์์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
โ Verification successful
Let me check the actual usage of the queryKey by looking at the content of the file.
์ฟผ๋ฆฌ ํค ์ฌ์ฉ์ด ์ ์ ํฉ๋๋ค
'likedList' ์ฟผ๋ฆฌ ํค๋ ํด๋น ํ์ผ์์๋ง ์ ์ผํ๊ฒ ์ฌ์ฉ๋๊ณ ์์ผ๋ฉฐ, ํ์ด์ง ํ๋ผ๋ฏธํฐ์ ํจ๊ป ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ๋ค๋ฅธ ํ์ผ์์ ๋์ผํ ์ฟผ๋ฆฌ ํค๋ฅผ ์ฌ์ฉํ์ง ์์ ์บ์ ์ถฉ๋์ ์ํ์ด ์์ต๋๋ค.
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: ๋์ผํ ์ฟผ๋ฆฌ ํค ์ฌ์ฉ ์ฌ๋ถ ํ์ธ
# likedList ์ฟผ๋ฆฌ ํค ์ฌ์ฉ ๊ฒ์
rg -l "queryKey.*likedList" --type ts
Length of output: 75
Script:
#!/bin/bash
# Check the actual implementation of the query key usage
rg "queryKey.*likedList" -A 5 --type ts
Length of output: 267
src/_apis/liked/liked-apis.ts (1)
1-3: ํ์ํ ์์กด์ฑ๋ค์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ํฌํธ๋์์ต๋๋ค!
ํ์ ๊ณผ ์ ํธ๋ฆฌํฐ ํจ์๊ฐ ์ ์ ํ๊ฒ ์ํฌํธ๋์ด ์์ต๋๋ค.
src/components/common/modal/confirm-modal.tsx (2)
39-39: ๋ซ๊ธฐ ๋ฒํผ ์ ๊ฑฐ๋ก UX ๊ฐ์
ํ์ธ ๋ชจ๋ฌ์ ํน์ฑ์ ์ฌ์ฉ์๊ฐ ๋ช ์์ ์ผ๋ก ์ ํํด์ผ ํ๋ ์ํฉ์์ ๋ซ๊ธฐ ๋ฒํผ์ ์ ๊ฑฐํ ๊ฒ์ ์ ์ ํ ํ๋จ์ ๋๋ค. ์ค์๋ก ๋ชจ๋ฌ์ด ๋ซํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
49-50: ๋ชจ๋ฌ ๋ ์ด์์ ๊ตฌ์กฐ ๊ฐ์
์ผ๊ด๋ ํจ๋ฉ(p-4)๊ณผ ์ค์ ์ ๋ ฌ(text-center)์ ์ ์ฉํ๊ณ , ์ ์ ํ ๊ฐ๊ฒฉ(space-y-8)์ ์ถ๊ฐํ ๊ฒ์ ์ข์ ๊ฐ์ ์
๋๋ค. ๋ ์ด์์์ด ๋ ๊น๋ํ๊ณ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํด์ก์ต๋๋ค.
src/types/gathering-data.d.ts (1)
15-15: LGTM! crewId ํ๋ ์ถ๊ฐ
GatheringDetailType์ crewId ํ๋๊ฐ ์ ์ ํ๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.
src/components/gathering-list/liked-list-presenter.tsx (1)
14-21: ๊ตฌํ์ด ๊น๋ํด ๋ณด์
๋๋ค!
์ปดํฌ๋ํธ ์ ์ธ๊ณผ props ๊ตฌ์กฐ๋ถํด๊ฐ React ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ ๋ฐ๋ฅด๊ณ ์์ต๋๋ค.
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (3)
3-9: ๊ฐ์ ธ์ค๊ธฐ(import) ๊ตฌ๋ฌธ์ด ์ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค!
ํ์ํ ๋ชจ๋ ์์กด์ฑ์ด ๋ช ํํ๊ฒ ์ ์๋์ด ์์ผ๋ฉฐ, ๊ด๋ จ ๊ธฐ๋ฅ๋ณ๋ก ์ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
18-19: ์ํ ๊ด๋ฆฌ๊ฐ ์ ์ ํ๊ฒ ๊ตฌํ๋์์ต๋๋ค!
๋ชจ๋ฌ ์ํ์ ๋ผ์ฐํฐ๊ฐ ๋ช ํํ๊ฒ ์ ์๋์ด ์์ต๋๋ค.
51-70: ๋ ๋๋ง ๋ก์ง์ด ๊น๋ํ๊ฒ ๊ตฌํ๋์์ต๋๋ค!
๋ชจ๋ฌ๊ณผ ์บ๋ฌ์ ์ปดํฌ๋ํธ์ ํตํฉ์ด ์ ๋์ด์๊ณ , props ์ ๋ฌ์ด ๋ช ํํฉ๋๋ค.
src/components/common/gathering-card/container.tsx (2)
15-16: ์ข์์ ๊ธฐ๋ฅ์ ์ํ ์ธํฐํ์ด์ค ํ์ฅ์ด ์ ์ ํ ๊ตฌํ๋์์ต๋๋ค.
๋น๋๊ธฐ ํจ์ ํ์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ ์๋์ด ์์ผ๋ฉฐ, ๋งค๊ฐ๋ณ์ ํ์ ๋ ์ ์ ํฉ๋๋ค.
30-31: ์ปดํฌ๋ํธ props ํตํฉ์ด ์ ๋์ด์์ต๋๋ค.
์ธํฐํ์ด์ค์์ ์ ์๋ props๊ฐ ์ปดํฌ๋ํธ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ฌ๋๊ณ ์์ต๋๋ค.
src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx (1)
58-58: ๋ชจ๋ ์คํ ๋ฆฌ์ crewId๊ฐ ์ผ๊ด๋๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.
๊ฐ ์คํ ๋ฆฌ์ ๋ฐ์ดํฐ ๊ฐ์ฒด์ crewId: 1์ด ์ ์ ํ๊ฒ ์ถ๊ฐ๋์์ต๋๋ค. ์ด๋ ์ข์์ API ์ฐ๋์ ์ํ ํ์ ๋ฐ์ดํฐ๋ก ๋ณด์
๋๋ค.
Also applies to: 101-101, 144-144
src/components/gathering-list/gathering-list.stories.tsx (1)
9-23: ๋ฉํ ๊ตฌ์ฑ์ด ์ ์ ํฉ๋๋ค
์คํ ๋ฆฌ๋ถ ๋ฉํ ๊ตฌ์ฑ์ด ์ ๋์ด์์ผ๋ฉฐ, QueryClientProvider ๋ฐ์ฝ๋ ์ดํฐ๊ฐ ์ ์ ํ ์ค์ ๋์ด ์์ต๋๋ค.
| if (isLoading) return <div>๋ก๋ฉ์ค...</div>; | ||
|
|
||
| // ์๋ฌ ์ฒ๋ฆฌ: error ๋๋ gatheringData๊ฐ undefined์ผ ๊ฒฝ์ฐ | ||
| if (error || gatheringData === undefined) { | ||
| Toast({ message: '์ข์์ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ ์คํจ', type: 'error' }); | ||
| return <div>์ข์์ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ ์คํจ</div>; | ||
| } | ||
|
|
||
| // ๋น ๋ฐฐ์ด ์ฒ๋ฆฌ(์ข์์ ๋ชฉ๋ก์ด ์๋ ๊ฒฝ์ฐ) | ||
| if (gatheringData.content.length === 0) { | ||
| return <div>์ข์์ ๋๋ฅธ ๋ชฉ๋ก์ด ์์ต๋๋ค</div>; | ||
| } | ||
|
|
||
| return ( | ||
| <LikedListPresenter | ||
| gatheringData={gatheringData} | ||
| onPageChange={handlePageChange} | ||
| onLike={handleLike} | ||
| onUnlike={handleUnlike} | ||
| page={page} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ ์ด ํ์ํฉ๋๋ค.
ํ์ฌ ๋ก๋ฉ ๋ฐ ์๋ฌ ์ํ์ UI๊ฐ ๋๋ฌด ๋จ์ํฉ๋๋ค. ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ํด ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ด ํ์ํฉ๋๋ค:
- ๋ก๋ฉ ์ํ์ ์ค์ผ๋ ํค UI ๋๋ ์คํผ๋ ์ถ๊ฐ
- ์๋ฌ ์ํ์ ์ฌ์๋ ๋ฒํผ ์ถ๊ฐ
- ๋น ์ํ์ ๋ํ ๋ ์น๊ทผํ ๋ฉ์์ง์ ๊ฐ์ด๋ ์ ๊ณต
๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:
- if (isLoading) return <div>๋ก๋ฉ์ค...</div>;
+ if (isLoading) return <LoadingSpinner />;
if (error || gatheringData === undefined) {
Toast({ message: '์ข์์ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ ์คํจ', type: 'error' });
- return <div>์ข์์ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ ์คํจ</div>;
+ return (
+ <ErrorState
+ message="์ข์์ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค"
+ onRetry={() => refetch()}
+ />
+ );
}
if (gatheringData.content.length === 0) {
- return <div>์ข์์ ๋๋ฅธ ๋ชฉ๋ก์ด ์์ต๋๋ค</div>;
+ return (
+ <EmptyState
+ message="์์ง ์ข์์๋ฅผ ๋๋ฅธ ๋ชจ์์ด ์์ต๋๋ค"
+ description="๊ด์ฌ์๋ ๋ชจ์์ ์ฐพ์๋ณด์ธ์!"
+ actionButton={
+ <Link href="/gatherings">๋ชจ์ ๋๋ฌ๋ณด๊ธฐ</Link>
+ }
+ />
+ );
}Committable suggestion skipped: line range outside the PR's diff.
| <GatheringCard | ||
| crewId={crewId} | ||
| {...card} | ||
| className="w-full" | ||
| onLike={() => { | ||
| if (isLoggedIn) { | ||
| return onLike(card.id); | ||
| } | ||
| onShowLoginModal(); // ๋ก๊ทธ์ธ์ด ์ ๋์ด ์์ผ๋ฉด ๋ชจ๋ฌ ํ์ | ||
| return Promise.resolve(); | ||
| }} | ||
| onUnlike={() => { | ||
| if (isLoggedIn) { | ||
| return onUnlike(card.id); | ||
| } | ||
| onShowLoginModal(); // ๋ก๊ทธ์ธ์ด ์ ๋์ด ์์ผ๋ฉด ๋ชจ๋ฌ ํ์ | ||
| return Promise.resolve(); | ||
| }} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
์ข์์/์ข์์ ์ทจ์ ํธ๋ค๋ฌ ์ค๋ณต ์ฝ๋ ๊ฐ์ ํ์
onLike์ onUnlike ํธ๋ค๋ฌ์ ๊ตฌ์กฐ๊ฐ ๋งค์ฐ ์ ์ฌํ์ฌ ์ค๋ณต ์ฝ๋๊ฐ ๋ฐ์ํ์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ณตํต ๋ก์ง์ ์ถ์ถํ์ฌ ๋ฆฌํฉํ ๋งํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:
const createAuthenticatedHandler = (
handler: (id: number) => Promise<void>
) => (id: number) => {
if (isLoggedIn) {
return handler(id);
}
onShowLoginModal();
return Promise.resolve();
};
<GatheringCard
crewId={crewId}
{...card}
className="w-full"
onLike={createAuthenticatedHandler((id) => onLike(id))}
onUnlike={createAuthenticatedHandler((id) => onUnlike(id))}
/>์ด๋ ๊ฒ ํ๋ฉด:
- ์ฝ๋ ์ค๋ณต์ด ์ ๊ฑฐ๋ฉ๋๋ค
- ์ธ์ฆ ๋ก์ง์ด ํ ๊ณณ์์ ๊ด๋ฆฌ๋ฉ๋๋ค
- ํฅํ ์ธ์ฆ ๊ด๋ จ ๋ก์ง ๋ณ๊ฒฝ ์ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํฉ๋๋ค
yulrang
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ ๋ผ๊ฐ ์ถ์ฒํด์ค๋ฐฉ๋ฒ๋ ๊ณ ๋ฏผํด์ ์๊ฐ๋ ๋ ์ ์ฉํด๋ณด์๋ฉด ์ข์ ๊ฒ ๊ฐ์์~~~
์๊ณ ๋ง์ผ์
จ์ต๋๋ค~!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
๐งน Outside diff range and nitpick comments (14)
src/_queries/liked/liked-queries.ts (1)
4-9: ์๋ฌ ์ฒ๋ฆฌ์ ์ฌ์๋ ์ค์ ์ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข๊ฒ ์ต๋๋ค.์ฌ์ฉ์ ๊ฒฝํ ํฅ์์ ์ํด ๋ค์๊ณผ ๊ฐ์ ์ค์ ์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์:
- ์๋ฌ ๋ฐ์ ์ ์ฌ์๋ ํ์
- ์บ์ ์ ํจ ์๊ฐ
- ์๋ฌ ๋ฐ์ ์ ํด๋ฐฑ UI๋ฅผ ์ํ ์๋ฌ ์ฒ๋ฆฌ
๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ ๊ฒ์ ์ ์ํฉ๋๋ค:
export function useGetLikedListQuery(page: number) { return useQuery({ queryKey: ['likedList', page], queryFn: () => getLikedList(page), + retry: 1, + staleTime: 5 * 60 * 1000, // 5๋ถ + onError: (error) => { + console.error('์ข์์ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค:', error); + } }); }src/components/gathering-list/liked-list-container.tsx (1)
1-9: ๊ฒฝ๋ก ๋ณ์นญ(@/src)์ ์ค๋ณต ์ฌ์ฉ์ ํ์ธํด์ฃผ์ธ์.import ๊ตฌ๋ฌธ์์
@/src๊ฐ ์ค๋ณต์ ์ผ๋ก ์ฌ์ฉ๋๊ณ ์์ต๋๋ค. ์ด๋ ์ ์ง๋ณด์์ฑ์ ์ ํ์ํฌ ์ ์์ต๋๋ค.๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์:
-import { addLike, removeLike } from '@/src/_apis/liked/liked-apis'; -import { useGetLikedListQuery } from '@/src/_queries/liked/liked-queries'; -import { ApiError } from '@/src/utils/api'; -import Toast from '@/src/components/common/toast'; +import { addLike, removeLike } from '@/_apis/liked/liked-apis'; +import { useGetLikedListQuery } from '@/_queries/liked/liked-queries'; +import { ApiError } from '@/utils/api'; +import Toast from '@/components/common/toast';src/components/gathering-list/liked-list-presenter.tsx (3)
21-21: ์ฌ์ฉํ์ง ์๋ ๋ณ์๊ฐ ์์ต๋๋ค
pageSize๋ณ์๋ฅผ ๊ตฌ์กฐ ๋ถํดํ์ง๋ง ์ปดํฌ๋ํธ ๋ด์์ ์ฌ์ฉ๋์ง ์๊ณ ์์ต๋๋ค. ํ์ํ์ง ์๋ค๋ฉด ์ ๊ฑฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.- const { content, pageSize, totalPages } = gatheringData; + const { content, totalPages } = gatheringData;
47-62: ์คํ์ผ๋ง ๋ฐฉ์์ ์ผ๊ด์ฑ์ด ํ์ํฉ๋๋คPagination ์ปดํฌ๋ํธ์ ์คํ์ผ๋ง์ Tailwind(
classNames)์ CSS-in-JS(styles)๊ฐ ํผ์ฉ๋์ด ์์ต๋๋ค. ์ ์ง๋ณด์์ฑ์ ์ํด ํ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ํต์ผํ๋ ๊ฒ์ด ์ข์ต๋๋ค.์๋ฅผ ๋ค์ด, Tailwind๋ง ์ฌ์ฉํ๋ ๋ฐฉ์:
classNames={{ control: cn( 'data-[active="true"]:text-blue-500 data-[active="true"]:font-bold', 'border-none bg-transparent hover:bg-transparent', + 'shadow-none' ), }} - styles={{ - control: { - '&[data-active]': { - backgroundColor: 'transparent', - fontWeight: 'var(--pagination-active-font-weight)', - color: 'var(--pagination-active-color)', - boxShadow: 'none', - }, - }, - }}
29-29: min-height ๊ฐ์ ๊ทผ๊ฑฐ๊ฐ ํ์ํฉ๋๋ค
md:min-h-[962px]์lg:min-h-[636px]๊ฐ์ด ๋งค์ง ๋๋ฒ์ฒ๋ผ ๋ณด์ ๋๋ค. ์ด ๊ฐ๋ค์ ๊ณ์ฐ ๊ทผ๊ฑฐ๋ฅผ ์ฃผ์์ผ๋ก ์ถ๊ฐํ๊ฑฐ๋, ์๋ฏธ ์๋ ๋ณ์๋ก ์ถ์ถํ๋ ๊ฒ์ด ์ข์ต๋๋ค.'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3', 'mx-auto justify-items-center gap-4', - 'md:min-h-[962px] lg:min-h-[636px]', + // ์นด๋ ๋์ด(300px) * ํ ์(2) + ๊ฐ๊ฒฉ(16px) * (ํ ์ - 1) = 616px + 'md:min-h-[616px]', + // ์นด๋ ๋์ด(300px) * ํ ์(2) + ๊ฐ๊ฒฉ(16px) * (ํ ์ - 1) = 616px + 'lg:min-h-[616px]',src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (2)
46-48: TODO ์ฃผ์์ ํด๊ฒฐํด์ฃผ์ธ์.๋ก๋ฉ๊ณผ ์๋ฌ ์ํ์ ๋ํ UI ๊ฐ์ ์ด ํ์ํฉ๋๋ค.
๋ ๋์ ๋ก๋ฉ ๋ฐ ์๋ฌ UI ๊ตฌํ์ ๋์๋๋ฆด๊น์? ์ค์ผ๋ ํค ๋ก๋ฉ์ด๋ ์๋ฌ ๋ฐ์ด๋๋ฆฌ ๊ตฌํ์ ์ ์๋๋ฆด ์ ์์ต๋๋ค.
51-70: ๋ชจ๋ฌ ์ํ ๊ด๋ฆฌ ๋ก์ง์ด ๊ฐ์ ๋ ์ ์์ต๋๋ค.๋ชจ๋ฌ์ ์ด๋ฆผ/๋ซํ ์ํ๊ฐ ์ค๋ณต๋์ด ๊ด๋ฆฌ๋๊ณ ์์ต๋๋ค.
openedprop์ด ๋ถํ์ํด ๋ณด์ ๋๋ค.๋ค์๊ณผ ๊ฐ์ด ์์ ์ ์ ์ํฉ๋๋ค:
{showLoginModal && ( <ConfirmModal - opened={showLoginModal} onClose={() => setShowLoginModal(false)} onConfirm={handleLoginRedirect} > ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค! </ConfirmModal> )}src/components/common/gathering-card/scheduled-gathering-card/container.tsx (3)
Line range hint
27-65: ๋๋ฏธ ๋ฐ์ดํฐ๋ฅผ ์ค์ API ์๋ต ํ์ ์ผ๋ก ๊ต์ฒดํด์ฃผ์ธ์.๋๋ฏธ ๋ฐ์ดํฐ๋ ๊ฐ๋ฐ ๋จ๊ณ์์๋ ์ ์ฉํ์ง๋ง, ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ค์ API ์๋ต์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋ค์ ์ฌํญ๋ค์ ๊ณ ๋ คํด์ฃผ์ธ์:
- API ์๋ต ํ์ ์ ์ ์ํ๊ณ ๋๋ฏธ ๋ฐ์ดํฐ๋ฅผ ํด๋น ํ์ ์ผ๋ก ๊ฒ์ฆ
- ์ค์ API ์ฐ๋ ์ ๋ง์ด๊ทธ๋ ์ด์ ์ด ์ฉ์ดํ๋๋ก ์ธํฐํ์ด์ค ๊ตฌ์กฐํ
interface Participant { id: number; profileImageUrl: string; nickname: string; email: string; } interface GatheringDetailType { crewId: number; id: number; title: string; // ... ๋๋จธ์ง ํ๋๋ค participants: Participant[]; }
26-26: TODO ํญ๋ชฉ๋ค์ ๊ตฌํ์ด ํ์ํฉ๋๋ค.ํ์ฌ ๋ค์๊ณผ ๊ฐ์ ๊ตฌํ์ด ๋๋ฝ๋์ด ์์ต๋๋ค:
- ๋ชจ๋ฌ ๋ฐ์ดํฐ ์ฐ๊ฒฐ
- useQuery ์ฌ์๋ ๋ก์ง
- ์ข์์ ํ ๊ธ ๊ธฐ๋ฅ
์ด๋ฌํ ๊ธฐ๋ฅ๋ค์ ๊ตฌํ์ ๋์๋๋ฆด๊น์? GitHub ์ด์๋ฅผ ์์ฑํ์ฌ ๊ฐ TODO ํญ๋ชฉ์ ์ถ์ ํ๊ณ ๊ตฌํ ๊ฐ์ด๋๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค.
Also applies to: 73-73, 78-78
Line range hint
16-24: ์ปดํฌ๋ํธ ๊ตฌ์กฐ ๊ฐ์ ์ ์ํ ์ ์์ฌํญํ์ฌ ๊ตฌ์กฐ๋ ์ ์์ฑ๋์ด ์์ง๋ง, ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ฌํญ์ ๊ณ ๋ คํด๋ณด์ธ์:
- ์๋ฌ ๋ฐ์ด๋๋ฆฌ ์ถ๊ฐ
- ๋ก๋ฉ ์ํ ์ฒ๋ฆฌ
- ๋ชจ๋ฌ ์ํ ๊ด๋ฆฌ๋ฅผ ์ ์ญ ์ํ๋ก ์ด๋ ๊ณ ๋ ค
// ์๋ฌ ๋ฐ์ด๋๋ฆฌ ์์ try { // ๊ธฐ์กด ๋ ๋๋ง ๋ก์ง } catch (error) { return <ErrorComponent error={error} />; } // ๋ก๋ฉ ์ํ ์ฒ๋ฆฌ ์์ {isLoading ? ( <LoadingSpinner /> ) : ( <ScheduledGatheringCardPresenter data={data} onClick={handleCardClick} onLikeToggle={handleLikeToggle} /> )}Also applies to: 69-91
src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx (1)
Line range hint
58-187: ํ ์คํธ ๋ฐ์ดํฐ ์ค๋ณต ๊ฐ์ ์ ์ํ์ฌ ์ธ ๊ฐ์ ์คํ ๋ฆฌ๊ฐ ๋งค์ฐ ์ ์ฌํ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ฝ๋์ ์ ์ง๋ณด์์ฑ์ ๋์ด๊ธฐ ์ํด ๊ณตํต ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฆฌํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํ ์ ์์ต๋๋ค:
+const BASE_GATHERING_DATA = { + crewId: 1, + introduce: '๊ณต์ง์ฌํญ์ ๋๋ค. ๋ค๋ค ์ด๋ฒ ์ฝ์ ์์ง ์์ผ์ จ์ฃ ? ๊ผญ ์ฐธ์ฌ ๋ถํ๋๋ฆฝ๋๋ค~', + location: '์์ธ์ ๊ฐ๋จ๊ตฌ ์ญ์ผ๋ ์คํผ์คํ์ 3์ธต', + currentCount: 3, + totalCount: 10, + imageUrl: 'https://www.dabur.com/Blogs/Doshas/Importance%20and%20Benefits%20of%20Yoga%201020x450.jpg', + participants: [ + // ... ๊ธฐ์กด ์ฐธ๊ฐ์ ๋ฐ์ดํฐ + ], +}; export const ModalWithUser = Template.bind({}); ModalWithUser.args = { opened: false, data: { - crewId: 1, - // ... ๊ธฐ์กด ๋ฐ์ดํฐ + ...BASE_GATHERING_DATA, + id: 1, + title: '์ ๋๋ ์ด๋...์ฆ๊ฑฐ์ด..์ฝ๋ฉ..', + liked: false, + gatheringCaptain: false, + participant: false, }, };src/components/gathering-list/gathering-card-carousel.tsx (1)
30-32: ๋ก๊ทธ์ธ ์ํ ํ์ธ ๋ก์ง์ ์ปค์คํ ํ ์ผ๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.ํ์ฌ ๊ตฌํ์ ์ ์๋ํ์ง๋ง, ์ฌ์ฌ์ฉ์ฑ๊ณผ ๊ด๋ฆฌ ์ฉ์ด์ฑ์ ์ํด ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ ์ ์ํฉ๋๋ค:
+// src/hooks/use-auth.ts +export const useAuth = () => { + const token = useAuthStore((state) => state.token); + return { + isLoggedIn: !!token, + token + }; +}; -const token = useAuthStore((state) => state.token); -const isLoggedIn = !!token; +const { isLoggedIn } = useAuth();src/components/gathering-list/gathering-list.stories.tsx (2)
7-22: QueryClient ์ค์ ์ต์ ํ ํ์ํ์ฌ QueryClient๊ฐ ๊ธฐ๋ณธ ์ค์ ์ผ๋ก๋ง ์์ฑ๋์ด ์์ต๋๋ค. Storybook ํ๊ฒฝ์ ๋ง๋ ์บ์ฑ ๋ฐ ์ฌ์๋ ์ค์ ์ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด QueryClient ์ค์ ์ ์ต์ ํํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:
-const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + cacheTime: 0, + staleTime: Infinity, + }, + }, +});
28-30: ์ก์ ํธ๋ค๋ฌ์ ํ์ ์์ ์ฑ ์ถ๊ฐ ํ์ํ์ฌ ์ก์ ํธ๋ค๋ฌ๋ค์ด ํ์ ์ ์ ์์ด ๊ตฌํ๋์ด ์์ต๋๋ค. ํ์ ์์ ์ฑ์ ์ํด ๋งค๊ฐ๋ณ์ ํ์ ์ ๋ช ์ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ํ์ ์ ์ถ๊ฐํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:
-const onLike = action('onLike'); -const onUnlike = action('onUnlike'); -const onPageChange = action('onPageChange'); +const onLike = action<(id: number) => void>('onLike'); +const onUnlike = action<(id: number) => void>('onUnlike'); +const onPageChange = action<(page: number) => void>('onPageChange');
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
๐ Files selected for processing (15)
src/_apis/liked/liked-apis.ts(1 hunks)src/_queries/liked/liked-queries.ts(1 hunks)src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx(3 hunks)src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx(2 hunks)src/app/(crew)/my-favorite/page.tsx(1 hunks)src/app/api/test-api/route.ts(0 hunks)src/components/common/gathering-card/container.tsx(3 hunks)src/components/common/gathering-card/scheduled-gathering-card/container.tsx(1 hunks)src/components/common/modal/confirm-modal.tsx(2 hunks)src/components/gathering-list/gathering-card-carousel.tsx(3 hunks)src/components/gathering-list/gathering-list.stories.tsx(1 hunks)src/components/gathering-list/gathering-list.tsx(0 hunks)src/components/gathering-list/liked-list-container.tsx(1 hunks)src/components/gathering-list/liked-list-presenter.tsx(1 hunks)src/types/gathering-data.d.ts(2 hunks)
๐ค Files with no reviewable changes (2)
- src/app/api/test-api/route.ts
- src/components/gathering-list/gathering-list.tsx
๐ Additional comments (15)
src/app/(crew)/my-favorite/page.tsx (2)
6-6: ๊ตฌํ์ด ๊น๋ํด ๋ณด์
๋๋ค!
LikedList ์ปดํฌ๋ํธ๋ก์ ๊ต์ฒด๊ฐ ์ ์ด๋ฃจ์ด์ก์ต๋๋ค. ์ปดํฌ๋ํธ๊ฐ props ์์ด ๋
๋ฆฝ์ ์ผ๋ก ๋์ํ๋๋ก ์ค๊ณ๋ ๊ฒ์ด ์ข์ ์ ๊ทผ ๋ฐฉ์์ผ๋ก ๋ณด์
๋๋ค.
1-1: ์ปดํฌ๋ํธ ๊ฒฝ๋ก ํ์ธ์ด ํ์ํฉ๋๋ค.
์๋ก ์ถ๊ฐ๋ LikedList ์ปดํฌ๋ํธ์ ๊ฒฝ๋ก๊ฐ ์ฌ๋ฐ๋ฅธ์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ฌ ์ปดํฌ๋ํธ ํ์ผ์ ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ์ธํ๊ฒ ์ต๋๋ค:
โ Verification successful
์ปดํฌ๋ํธ ๊ฒฝ๋ก๊ฐ ์ ํํ๊ฒ ํ์ธ๋์์ต๋๋ค.
LikedList ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ฐ๋ฅธ ๊ฒฝ๋ก์ ์กด์ฌํ๋ฉฐ, ์ ์ ํ๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค. ์ปดํฌ๋ํธ๋ ์ข์์ ๋ชฉ๋ก์ ๊ด๋ฆฌํ๋ ๊ธฐ๋ฅ์ ํฌํจํ๊ณ ์์ด ์๋๋ ์ฉ๋์ ๋ง๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค.
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the existence of the LikedList component
# Check if the component file exists
fd "liked-list-container.tsx" src/components/gathering-list
# Verify the component export
ast-grep --pattern 'export default function LikedList'
Length of output: 171
Script:
#!/bin/bash
# Let's verify the content of the LikedList component to ensure it's properly implemented
cat src/components/gathering-list/liked-list-container.tsx
Length of output: 1758
src/_queries/liked/liked-queries.ts (2)
1-9: ๊ตฌํ์ด ๊น๋ํ๊ณ ๋ช
ํํฉ๋๋ค!
React Query๋ฅผ ์ฌ์ฉํ ๊ตฌํ์ด ๊ฐ๋จํ๋ฉด์๋ ํจ๊ณผ์ ์ ๋๋ค.
6-6: queryKey๊ฐ ์ ์ ํ ๊ตฌ์ฑ๋์ด ์๋์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
๋ค๋ฅธ ๊ณณ์์ ๋์ผํ ํค๋ฅผ ์ฌ์ฉํ๊ณ ์๋์ง ํ์ธํ์ฌ ์บ์ ์ถฉ๋์ ๋ฐฉ์งํด์ผ ํฉ๋๋ค.
โ Verification successful
queryKey ๊ตฌ์ฑ์ด ์ ์ ํฉ๋๋ค
'likedList' queryKey๋ ํด๋น ํ์ผ์์๋ง ์ฌ์ฉ๋๊ณ ์์ผ๋ฉฐ, page ํ๋ผ๋ฏธํฐ์ ํจ๊ป ์ ์ ํ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ๋ค๋ฅธ ํ์ผ์์ ๋์ผํ ํค๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ์์ด ์บ์ ์ถฉ๋ ์ํ์ด ์์ต๋๋ค.
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for other usages of the 'likedList' query key
rg -l "queryKey.*['\"]likedList['\"]" --type ts
Length of output: 89
Script:
#!/bin/bash
# Let's check the content of the file to understand the context better
cat src/_queries/liked/liked-queries.ts
Length of output: 308
Script:
#!/bin/bash
# Let's check if there are any other files that might be using similar query patterns or mutations related to liked functionality
rg -l "queryKey.*liked" --type ts
# Also check for any mutations that might be affecting this query
rg -l "useMutation.*liked" --type ts
Length of output: 110
src/_apis/liked/liked-apis.ts (2)
1-3: LGTM! ํ์ํ ์์กด์ฑ๋ค์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ํฌํธ๋์์ต๋๋ค.
ํ์ํ ์ ํธ๋ฆฌํฐ์ ํ์ ์ด ์ ์ ํ๊ฒ ์ํฌํธ๋์์ต๋๋ค.
6-6: API ์๋ํฌ์ธํธ ๊ฒ์ฆ์ด ํ์ํฉ๋๋ค.
๋ฐฑ์๋ API ๋ฌธ์์ ์ผ์นํ๋์ง ๋ค์ ์๋ํฌ์ธํธ๋ค์ ํ์ธํด์ฃผ์ธ์:
- GET
/api/liked/memberLikes - POST
/api/liked/${gatheringId} - DELETE
/api/liked/${gatheringId}
Also applies to: 19-19, 31-31
โ Verification successful
API ์๋ํฌ์ธํธ ๊ตฌํ์ด ์ผ๊ด๋๊ฒ ๋์ด์์ต๋๋ค
์ฝ๋๋ฒ ์ด์ค ๋ถ์ ๊ฒฐ๊ณผ, /api/liked ๊ด๋ จ ์๋ํฌ์ธํธ๋ค์ด ๋ค์๊ณผ ๊ฐ์ด ์ผ๊ด๋๊ฒ ๊ตฌํ๋์ด ์์์ ํ์ธํ์ต๋๋ค:
- GET
/api/liked/memberLikes- ์ฐ ๋ชฉ๋ก ์กฐํ (ํ์ด์ง๋ค์ด์ ์ง์) - POST
/api/liked/${gatheringId}- ์ฐ ์ถ๊ฐ - DELETE
/api/liked/${gatheringId}- ์ฐ ์ ๊ฑฐ
๊ฐ ์๋ํฌ์ธํธ๋ fetchApi ์ ํธ๋ฆฌํฐ๋ฅผ ํตํด ์ ์ ํ HTTP ๋ฉ์๋์ ํค๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํ๋์ด ์์ต๋๋ค.
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# API ๋ฌธ์๋ ์คํ์ ์ฐพ์ ์๋ํฌ์ธํธ ๊ฒ์ฆ
fd -e yaml -e json . | xargs grep -l "/api/liked"
Length of output: 49
Script:
#!/bin/bash
# API ์๋ํฌ์ธํธ ์ฌ์ฉ ํจํด ๊ฒ์
rg -A 2 "/api/liked" --type ts
# API ๊ด๋ จ ์ค์ ํ์ผ ๊ฒ์
fd -e ts -e js "api|config|axios" | xargs cat
# Swagger/OpenAPI ๋ฌธ์ ๊ฒ์
fd "swagger|openapi|api" -e yaml -e json
Length of output: 10689
src/components/common/modal/confirm-modal.tsx (2)
39-39: ๋ช
์์ ์ธ ๋ซ๊ธฐ ๋ฒํผ ์ ๊ฑฐ๊ฐ ์ ์ ํด ๋ณด์
๋๋ค.
ํ์ธ ๋ชจ๋ฌ์ ํน์ฑ์ ์ฌ์ฉ์๊ฐ ๋ช ํํ ์ ํ์ ํด์ผ ํ๋ฏ๋ก, ๋ซ๊ธฐ ๋ฒํผ์ ์ ๊ฑฐํ ๊ฒ์ด UX ๊ด์ ์์ ์ ์ ํฉ๋๋ค. ์ค์๋ก ๋ชจ๋ฌ์ด ๋ซํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
49-50: ๋ชจ๋ฌ ๋ด๋ถ ๊ตฌ์กฐ ๊ฐ์ ์ด ์ ๋์์ต๋๋ค.
ํจ๋ฉ๊ณผ ์๋ก์ด div ๋ํผ ์ถ๊ฐ๋ก ๋ ์ด์์์ด ๋ ์ผ๊ด๋๊ณ ๊น๋ํด์ก์ต๋๋ค. space-y-8 ํด๋์ค์ ํจ๊ป ๋ด๋ถ ์์๋ค์ ๊ฐ๊ฒฉ์ด ์ ์กฐ์ ๋์์ต๋๋ค.
src/types/gathering-data.d.ts (2)
15-15: LGTM: crewId ์์ฑ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.
๋ชจ์ ์์ธ ์ ๋ณด์ crew ์๋ณ์๋ฅผ ํฌํจํ๋ ๊ฒ์ด ์ ์ ํฉ๋๋ค.
63-69:
GatheringContentType์ด ์ ์๋์ง ์์ ๋ฌธ์ ํด๊ฒฐ ํ์
GatheringResponseType ์ธํฐํ์ด์ค์์ ์ฌ์ฉํ๋ GatheringContentType์ด ์ ์๋์ด ์์ง ์์ต๋๋ค. ์ด๋ ํ์ ์ค๋ฅ๋ฅผ ๋ฐ์์ํฌ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ ์ ์ ์๋๋ฆฝ๋๋ค:
export interface GatheringResponseType {
- content: GatheringContentType[];
+ content: LikeGatheringType[];
pageNumber: number;
pageSize: number;
totalElements: number;
totalPages: number;
}Likely invalid or redundant comment.
src/components/gathering-list/liked-list-presenter.tsx (1)
1-12: ์ธํฐํ์ด์ค์ ์ํฌํธ ์ ์ธ์ด ์ ์ ์๋์ด ์์ต๋๋ค!
props ์ธํฐํ์ด์ค๊ฐ ๋ช ํํ๊ฒ ํ์ ์ด ์ง์ ๋์ด ์์ผ๋ฉฐ, ํ์ํ ๋ชจ๋ ์์กด์ฑ์ด ์ ์ ํ๊ฒ ์ํฌํธ๋์ด ์์ต๋๋ค.
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (1)
41-44: ๋ก๊ทธ์ธ ๋ฆฌ๋ค์ด๋ ์
๋ก์ง์ด ์ ์ ํฉ๋๋ค.
ํ์ฌ ๊ฒฝ๋ก๋ฅผ ํฌํจํ ๋ฆฌ๋ค์ด๋ ์ ์ฒ๋ฆฌ๊ฐ ์ ๊ตฌํ๋์ด ์์ต๋๋ค.
src/components/common/gathering-card/container.tsx (2)
15-16: ์ข์์ ๊ธฐ๋ฅ์ ์ํ Props ํ์
์ ์๊ฐ ์ ์ ํฉ๋๋ค.
Promise๋ฅผ ๋ฐํํ๋ ๋น๋๊ธฐ ํจ์๋ก ์ ์๋์ด ์์ด API ํธ์ถ์ ์ ํฉํฉ๋๋ค.
30-31: Props ๊ตฌ์กฐ ๋ถํด๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
์ปดํฌ๋ํธ์ ๋งค๊ฐ๋ณ์๋ก ์๋ก์ด props๊ฐ ์ ์ ํ๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.
src/components/gathering-list/gathering-card-carousel.tsx (1)
11-16: ์ธํฐํ์ด์ค ๋ณ๊ฒฝ์ด ์ ๊ตฌํ๋์์ต๋๋ค!
์ข์์ ๊ด๋ จ ๊ธฐ๋ฅ์ ์ํ ์ฝ๋ฐฑ ํจ์๋ค์ด ๋ช ํํ๊ฒ ํ์ ์ด ์ง์ ๋์ด ์์ต๋๋ค.
| export default function LikedList() { | ||
| const [page, setPage] = useState(1); | ||
| const { data: gatheringData, isLoading, error, refetch } = useGetLikedListQuery(page); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
ํ์ ์์ ์ฑ ๊ฐ์ ์ด ํ์ํฉ๋๋ค.
์ปดํฌ๋ํธ์ ์ํ์ ๋ช ์์ ์ธ ํ์ ์ ์๊ฐ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ํ์ ์ ์ถ๊ฐํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค:
-export default function LikedList() {
+export default function LikedList(): JSX.Element {
- const [page, setPage] = useState(1);
+ const [page, setPage] = useState<number>(1);๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export default function LikedList() { | |
| const [page, setPage] = useState(1); | |
| const { data: gatheringData, isLoading, error, refetch } = useGetLikedListQuery(page); | |
| export default function LikedList(): JSX.Element { | |
| const [page, setPage] = useState<number>(1); | |
| const { data: gatheringData, isLoading, error, refetch } = useGetLikedListQuery(page); | |
| import { useState } from 'react'; | ||
| import { useRouter } from 'next/navigation'; | ||
| import { addLike, removeLike } from '@/src/_apis/liked/liked-apis'; | ||
| import { useGetGatheringListQuery } from '@/src/_queries/detail/gathering-list-queries'; | ||
| import { ApiError } from '@/src/utils/api'; | ||
| import ConfirmModal from '@/src/components/common/modal/confirm-modal'; | ||
| import Toast from '@/src/components/common/toast'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ก Codebase verification
Toast ์ปดํฌ๋ํธ๊ฐ ์๋ชป๋ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ๋๊ณ ์์ต๋๋ค
Toast ์ปดํฌ๋ํธ๋ React ์ปดํฌ๋ํธ๋ก ๊ตฌํ๋์ด ์์ผ๋, ํ์ฌ ํจ์์ฒ๋ผ ํธ์ถ๋๊ณ ์์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์์ ์ด ํ์ํฉ๋๋ค:
gathering-list-section.tsx์ Toast ํธ์ถ ๋ถ๋ถ์ JSX ๋ฌธ๋ฒ์ผ๋ก ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค:
{apiError instanceof ApiError && (
<Toast message={`์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค: ${apiError.message}`} type="error" />
)}- ToastContainer๊ฐ ํ์ด์ง์์ ํ ๋ฒ๋ง ๋ ๋๋ง๋๋๋ก ์์ ๋ ์ด์์ ์ปดํฌ๋ํธ๋ก ์ด๋ํด์ผ ํฉ๋๋ค.
๐ Analysis chain
๋ถํ์ํ import ๊ตฌ๋ฌธ์ด ์๋์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
Toast ์ปดํฌ๋ํธ๊ฐ ์ค์ ๋ก ์ฌ์ฉ๋๋ ๋ฐฉ์์ด ์ฌ๋ฐ๋ฅธ์ง ํ์ธํด์ฃผ์ธ์. React ์ปดํฌ๋ํธ์ฒ๋ผ ์ฌ์ฉ๋๊ณ ์์ง๋ง, ํจ์์ฒ๋ผ ํธ์ถ๋๊ณ ์์ต๋๋ค.
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Toast ์ปดํฌ๋ํธ์ ์ ์๋ฅผ ํ์ธ
ast-grep --pattern 'export $_ function Toast'
Length of output: 47
Script:
#!/bin/bash
# Toast ์ปดํฌ๋ํธ์ ์ ์์ ์ฌ์ฉ ๋ฐฉ์์ ํ์ธ
rg -A 5 "export.*Toast"
Length of output: 1582
Script:
#!/bin/bash
# gathering-list-section.tsx ํ์ผ์์ Toast์ ์ฌ์ฉ ๋ฐฉ์์ ํ์ธ
rg -A 3 "Toast\." src/app/\(crew\)/crew/detail/\[id\]/_components/gathering-list-section.tsx
Length of output: 92
Script:
#!/bin/bash
# Toast ์ปดํฌ๋ํธ ํ์ผ ์ฐพ๊ธฐ
fd -t f "toast" -I
Length of output: 64
Script:
#!/bin/bash
# gathering-list-section.tsx ํ์ผ์ ์ ์ฒด ๋ด์ฉ์ ํ์ธํ์ฌ Toast ์ฌ์ฉ ๋ฐฉ์ ํ์
rg -l "Toast" src/app/\(crew\)/crew/detail/\[id\]/_components/gathering-list-section.tsx && cat src/app/\(crew\)/crew/detail/\[id\]/_components/gathering-list-section.tsx
Length of output: 2435
Script:
#!/bin/bash
# Toast ์ปดํฌ๋ํธ์ ์ ์ฒด ๊ตฌํ ํ์ธ
cat src/components/common/toast/index.tsx
Length of output: 762
| // ์ฐํ๊ธฐ ์ํ ์ ๋ฐ์ดํธ | ||
| const handleLikeToggle = async () => { | ||
| try { | ||
| if (isLiked) { | ||
| await onUnlike(id); | ||
| setIsLiked(false); | ||
| } else { | ||
| await onLike(id); | ||
| setIsLiked(true); | ||
| } | ||
| } catch (error) { | ||
| Toast({ message: '์ฐ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์คํจํ์ต๋๋ค.', type: 'error' }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
์ข์์ ํ ๊ธ ํธ๋ค๋ฌ์ ๊ฐ์ ์ด ํ์ํฉ๋๋ค.
ํ์ฌ ๊ตฌํ์์ ๋ค์ ์ฌํญ๋ค์ ๊ฐ์ ํ๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค:
- ๋ก๋ฉ ์ํ ๊ด๋ฆฌ๊ฐ ์์ด ์ฌ์ฉ์ ํผ๋๋ฐฑ์ด ๋ถ์กฑํฉ๋๋ค.
- ๋น๋๊ธฐ ์์ ์ค ๋ฐ์ํ ์ ์๋ ๋ ์ด์ค ์ปจ๋์ ์ ์ทจ์ฝํฉ๋๋ค.
- ์๋ฌ ์ฒ๋ฆฌ๊ฐ ๋ชจ๋ ์ผ์ด์ค๋ฅผ ํฌ๊ด์ ์ผ๋ก ๋ค๋ฃจ์ง ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํด๋ณด์ธ์:
+ const [isLoading, setIsLoading] = useState(false);
const handleLikeToggle = async () => {
+ if (isLoading) return;
+ setIsLoading(true);
try {
if (isLiked) {
await onUnlike(id);
- setIsLiked(false);
+ setIsLiked(prev => !prev);
} else {
await onLike(id);
- setIsLiked(true);
+ setIsLiked(prev => !prev);
}
} catch (error) {
+ // ์๋ ์ํ๋ก ๋ณต๊ตฌ
+ setIsLiked(prev => !prev);
+ if (error instanceof Error) {
+ Toast({ message: `์ข์์ ์
๋ฐ์ดํธ ์คํจ: ${error.message}`, type: 'error' });
+ } else {
Toast({ message: '์ฐ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๋ ๋ฐ ์คํจํ์ต๋๋ค.', type: 'error' });
+ }
} finally {
+ setIsLoading(false);
}
};๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // ์ฐํ๊ธฐ ์ํ ์ ๋ฐ์ดํธ | |
| const handleLikeToggle = async () => { | |
| try { | |
| if (isLiked) { | |
| await onUnlike(id); | |
| setIsLiked(false); | |
| } else { | |
| await onLike(id); | |
| setIsLiked(true); | |
| } | |
| } catch (error) { | |
| Toast({ message: '์ฐ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์คํจํ์ต๋๋ค.', type: 'error' }); | |
| } | |
| const [isLoading, setIsLoading] = useState(false); | |
| const handleLikeToggle = async () => { | |
| if (isLoading) return; | |
| setIsLoading(true); | |
| try { | |
| if (isLiked) { | |
| await onUnlike(id); | |
| setIsLiked(prev => !prev); | |
| } else { | |
| await onLike(id); | |
| setIsLiked(prev => !prev); | |
| } | |
| } catch (error) { | |
| // ์๋ ์ํ๋ก ๋ณต๊ตฌ | |
| setIsLiked(prev => !prev); | |
| if (error instanceof Error) { | |
| Toast({ message: `์ข์์ ์ ๋ฐ์ดํธ ์คํจ: ${error.message}`, type: 'error' }); | |
| } else { | |
| Toast({ message: '์ฐ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์คํจํ์ต๋๋ค.', type: 'error' }); | |
| } | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; |
| ModalWithUser.args = { | ||
| opened: false, | ||
| data: { | ||
| crewId: 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ก Codebase verification
crewId ์์ฑ์ด stories์๋ง ์ถ๊ฐ๋์ด ์๊ณ ์ค์ ์ปดํฌ๋ํธ์์๋ ์ฌ์ฉ๋์ง ์์ต๋๋ค
๊ฒ์ฆ ๊ฒฐ๊ณผ:
- stories ํ์ผ์์๋ง
crewId: 1์์ฑ์ด ์ถ๊ฐ๋์ด ์์ต๋๋ค - container.tsx์ presenter.tsx์์
GatheringDetailTypeํ์ ์ ์ฌ์ฉํ๊ณ ์์ง๋ง,crewId๋ฅผ ์ค์ ๋ก ์ฌ์ฉํ๋ ๋ก์ง์ ์์ต๋๋ค - ์ข์์ API ์ฐ๋์ ์ํด ์ถ๊ฐ๋ ์์ฑ์ด๋ผ๋ฉด, ๊ด๋ จ ๊ธฐ๋ฅ ๊ตฌํ์ด ๋๋ฝ๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค
๐ Analysis chain
crewId ์์ฑ์ด ์ผ๊ด๋๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.
๋ชจ๋ ์คํ ๋ฆฌ์ crewId: 1์ด ์ถ๊ฐ๋ ๊ฒ์ ํ์ธํ์ต๋๋ค. ์ด๋ ์ข์์ API ์ฐ๋์ ์ํ ๋ณ๊ฒฝ์ฌํญ๊ณผ ์ ๋ถํฉํฉ๋๋ค.
์ปดํฌ๋ํธ์์ ์ด ์์ฑ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉ๋๋์ง ํ์ธํ๊ธฐ ์ํด ๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๊ฒ ์ต๋๋ค:
Also applies to: 101-101, 144-144
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: GatheringDetailModalContainer ์ปดํฌ๋ํธ์์ crewId ์ฌ์ฉ ์ฌ๋ถ ํ์ธ
# container.tsx ํ์ผ์์ crewId ์ฌ์ฉ ํ์ธ
ast-grep --pattern 'interface GatheringDetailType {
$$$
crewId: number
$$$
}'
# crewId๋ฅผ props๋ก ์ ๋ฌํ๋ ํจํด ํ์ธ
ast-grep --pattern 'function GatheringDetailModalContainer({ $$$, data }: GatheringDetailModalContainerProps) {
$$$
crewId
$$$
}'
Length of output: 223
Script:
#!/bin/bash
# ๋ชจ๋ฌ ์ปดํฌ๋ํธ ํ์ผ์์ crewId ์ฌ์ฉ ํจํด ๊ฒ์
rg -l "crewId" "src/app/(crew)/crew/_components/gathering-detail-modal/"
# ๋ชจ๋ฌ ์ปดํฌ๋ํธ์ ์ธํฐํ์ด์ค์ ํ์
์ ์ ๊ฒ์
rg "interface|type.*GatheringDetail" "src/app/(crew)/crew/_components/gathering-detail-modal/" -A 5
# crewId ์ฌ์ฉ ์ปจํ
์คํธ ํ์ธ
rg "crewId" "src/app/(crew)/crew/_components/gathering-detail-modal/" -B 2 -A 2
Length of output: 3064
Script:
#!/bin/bash
# GatheringDetailType ์ธํฐํ์ด์ค ์ ์ ๊ฒ์
rg "type.*GatheringDetailType" -A 10 "src/app/(crew)/crew/_components"
# ๋ชจ๋ฌ ์ปดํฌ๋ํธ์์ data prop์ ์ฌ์ฉ ํ์ธ
rg "data: GatheringDetailType" -B 5 -A 10 "src/app/(crew)/crew/_components"
# crewId๊ฐ ์ค์ ๋ก ์ฌ์ฉ๋๋ ๋ก์ง ๊ฒ์
rg "data\.crewId" "src/app/(crew)/crew/_components"
Length of output: 3085
| <GatheringCard | ||
| crewId={crewId} | ||
| {...card} | ||
| className="w-full" | ||
| onLike={() => { | ||
| if (isLoggedIn) { | ||
| return onLike(card.id); | ||
| } | ||
| onShowLoginModal(); // ๋ก๊ทธ์ธ์ด ์ ๋์ด ์์ผ๋ฉด ๋ชจ๋ฌ ํ์ | ||
| return Promise.resolve(); | ||
| }} | ||
| onUnlike={() => { | ||
| if (isLoggedIn) { | ||
| return onUnlike(card.id); | ||
| } | ||
| onShowLoginModal(); // ๋ก๊ทธ์ธ์ด ์ ๋์ด ์์ผ๋ฉด ๋ชจ๋ฌ ํ์ | ||
| return Promise.resolve(); | ||
| }} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ข์์/์ข์์ ์ทจ์ ํธ๋ค๋ฌ์ ๊ฐ์ ์ด ํ์ํฉ๋๋ค.
ํ์ฌ ๊ตฌํ์์ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ์ ๋ค์ด ๋ฐ๊ฒฌ๋์์ต๋๋ค:
- ์ข์์์ ์ข์์ ์ทจ์ ํธ๋ค๋ฌ์ ์ค๋ณต ์ฝ๋๊ฐ ์กด์ฌํฉ๋๋ค.
- ๋ก๊ทธ์ธํ์ง ์์ ์ํ์์
Promise.resolve()๋ฅผ ๋ฐํํ๋ ๊ฒ์ ์์ ์ปดํฌ๋ํธ์์ ์์์น ๋ชปํ ๋์์ ์ผ์ผํฌ ์ ์์ต๋๋ค. - ์๋ฌ ์ฒ๋ฆฌ๊ฐ ๋๋ฝ๋์ด ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ๋ ๊ตฌํ์ ์ ์ํฉ๋๋ค:
+const createAuthenticatedHandler = (
+ action: (id: number) => Promise<void>,
+ gatheringId: number,
+ showLoginModal: () => void,
+) => async () => {
+ if (!isLoggedIn) {
+ showLoginModal();
+ return;
+ }
+
+ try {
+ await action(gatheringId);
+ } catch (error) {
+ console.error('Failed to process action:', error);
+ // ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง ์ถ๊ฐ ํ์
+ }
+};
<GatheringCard
crewId={crewId}
{...card}
className="w-full"
- onLike={() => {
- if (isLoggedIn) {
- return onLike(card.id);
- }
- onShowLoginModal();
- return Promise.resolve();
- }}
- onUnlike={() => {
- if (isLoggedIn) {
- return onUnlike(card.id);
- }
- onShowLoginModal();
- return Promise.resolve();
- }}
+ onLike={createAuthenticatedHandler(onLike, card.id, onShowLoginModal)}
+ onUnlike={createAuthenticatedHandler(onUnlike, card.id, onShowLoginModal)}
/>๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <GatheringCard | |
| crewId={crewId} | |
| {...card} | |
| className="w-full" | |
| onLike={() => { | |
| if (isLoggedIn) { | |
| return onLike(card.id); | |
| } | |
| onShowLoginModal(); // ๋ก๊ทธ์ธ์ด ์ ๋์ด ์์ผ๋ฉด ๋ชจ๋ฌ ํ์ | |
| return Promise.resolve(); | |
| }} | |
| onUnlike={() => { | |
| if (isLoggedIn) { | |
| return onUnlike(card.id); | |
| } | |
| onShowLoginModal(); // ๋ก๊ทธ์ธ์ด ์ ๋์ด ์์ผ๋ฉด ๋ชจ๋ฌ ํ์ | |
| return Promise.resolve(); | |
| }} | |
| /> | |
| <GatheringCard | |
| crewId={crewId} | |
| {...card} | |
| className="w-full" | |
| onLike={createAuthenticatedHandler(onLike, card.id, onShowLoginModal)} | |
| onUnlike={createAuthenticatedHandler(onUnlike, card.id, onShowLoginModal)} | |
| /> |
minkyung5x5
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ข์์ ๊ธฐ๋ฅ์ด ๋๋ค๋!! ์๊ณ ํ์ จ์ต๋๋ค-!!
|
๐storybook: https://67206cc6ff9d7a05a3528ff8-mpbgyrtayu.chromatic.com/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
๐งน Outside diff range and nitpick comments (6)
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (3)
41-44: window.location ๋์ Next.js์ ๊ธฐ๋ฅ์ ํ์ฉํด์ฃผ์ธ์ํ์ฌ ๊ตฌํ์ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ API๋ฅผ ์ง์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. Next.js์ ๊ธฐ๋ฅ์ ํ์ฉํ์ฌ ๋ ๊น๋ํ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ ์ ์ ์ํฉ๋๋ค:
const handleLoginRedirect = () => { - const currentPath = window.location.href; + const currentPath = window.location.pathname + window.location.search; router.push(`/login?redirect=${encodeURIComponent(currentPath)}`); };
21-44: ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ต์ ํ๊ฐ ํ์ํฉ๋๋ค์ด๋ฒคํธ ํธ๋ค๋ฌ๋ค์ด ๋งค ๋ ๋๋ง๋ง๋ค ์ฌ์์ฑ๋๊ณ ์์ต๋๋ค. useCallback์ ์ฌ์ฉํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ ์ ์ ์ํฉ๋๋ค:
const handleLike = useCallback(async (gatheringId: number) => { try { await addLike(gatheringId); } catch (apiError) { if (apiError instanceof ApiError) { Toast({ message: `์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค: ${apiError.message}`, type: 'error' }); } } }, []); const handleUnlike = useCallback(async (gatheringId: number) => { try { await removeLike(gatheringId); } catch (apiError) { if (apiError instanceof ApiError) { Toast({ message: `์ฐํ๊ธฐ ํด์ ์ ์คํจํ์ต๋๋ค: ${apiError.message}`, type: 'error' }); } } }, []); const handleLoginRedirect = useCallback(() => { const currentPath = window.location.pathname + window.location.search; router.push(`/login?redirect=${encodeURIComponent(currentPath)}`); }, [router]);
53-59: GatheringCardCarousel์ prop ํ์ ์ ๋ช ์ํด์ฃผ์ธ์์ปดํฌ๋ํธ์ ์ ๋ฌ๋๋ prop๋ค์ ํ์ ์ด ๋ช ์๋์ด ์์ง ์์ต๋๋ค. ํ์ ์์ ์ฑ์ ์ํด prop ํ์ ์ ์ ์ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
GatheringCardCarousel ์ปดํฌ๋ํธ์ ๋ค์๊ณผ ๊ฐ์ด prop ํ์ ์ ์ ์ํด์ฃผ์ธ์:
interface GatheringCardCarouselProps { gatheringData: GatheringData[]; crewId: number; onLike: (gatheringId: number) => Promise<void>; onUnlike: (gatheringId: number) => Promise<void>; onShowLoginModal: () => void; }src/components/gathering-list/gathering-card-carousel.tsx (3)
11-16: ์ธํฐํ์ด์ค ๋ฌธ์ํ ๊ฐ์ ํ์๊ฐ ํ๋กํผํฐ์ ๋ชฉ์ ๊ณผ ์ฌ์ฉ๋ฒ์ ๋ช ํํ ํ๊ธฐ ์ํด JSDoc ๋ฌธ์ํ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข๊ฒ ์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๋ฌธ์ํ๋ฅผ ์ถ๊ฐํด๋ณด์ธ์:
interface GatheringCardCarouselProps { gatheringData: GatheringType[]; crewId: number; + /** ๋ชจ์ ์ข์์ ์ฒ๋ฆฌ ํจ์ */ onLike: (gatheringId: number) => Promise<void>; + /** ๋ชจ์ ์ข์์ ์ทจ์ ์ฒ๋ฆฌ ํจ์ */ onUnlike: (gatheringId: number) => Promise<void>; + /** ๋ก๊ทธ์ธ ๋ชจ๋ฌ์ ํ์ํ๋ ํจ์ */ onShowLoginModal: () => void; }
30-32: ๋ก๊ทธ์ธ ์ํ ํ์ธ ๋ก์ง ๊ฐ์ ์ ์ํ์ฌ ๊ตฌํ์ ์ ํํ์ง๋ง, ๋ ๋ช ์์ ์ผ๋ก ์์ฑํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํด๋ณด์ธ์:
- // ๋ก๊ทธ์ธ ์ฌ๋ถ ํ์ธ - const token = useAuthStore((state) => state.token); - const isLoggedIn = !!token; + const token = useAuthStore((state) => state.token); + const isLoggedIn = Boolean(token);
97-103: GatheringCard ์ปดํฌ๋ํธ props ์ ๋ฌ ๋ฐฉ์ ๊ฐ์ํ์ฌ ๊ตฌํ์ ์ ํํ์ง๋ง, ๋ ๊ฐ๊ฒฐํ๊ฒ ์์ฑํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํด๋ณด์ธ์:
<GatheringCard crewId={crewId} {...card} className="w-full" - onLike={() => handleLikeAction('like', card.id)} - onUnlike={() => handleLikeAction('unlike', card.id)} + onLike={handleLikeAction.bind(null, 'like', card.id)} + onUnlike={handleLikeAction.bind(null, 'unlike', card.id)} />์ด๋ ๊ฒ ํ๋ฉด:
- ๋ถํ์ํ ํ์ดํ ํจ์ ์์ฑ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๊ฐ์ ๋ฉ๋๋ค
- ์ปดํฌ๋ํธ ๋ฆฌ๋ ๋๋ง ์ฑ๋ฅ์ด ํฅ์๋ ์ ์์ต๋๋ค
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
๐ Files selected for processing (3)
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx(2 hunks)src/components/common/gathering-card/container.tsx(3 hunks)src/components/gathering-list/gathering-card-carousel.tsx(4 hunks)
๐ Additional comments (4)
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (1)
26-26: Toast ์ปดํฌ๋ํธ ์ฌ์ฉ ๋ฐฉ์์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค
์ด์ ๋ฆฌ๋ทฐ์์ ์ง์ ๋ Toast ์ปดํฌ๋ํธ ์ฌ์ฉ ๋ฐฉ์ ๋ฌธ์ ๊ฐ ์์ง ํด๊ฒฐ๋์ง ์์์ต๋๋ค. Toast๋ React ์ปดํฌ๋ํธ์ด๋ฏ๋ก JSX ๋ฌธ๋ฒ์ผ๋ก ์ฌ์ฉ๋์ด์ผ ํฉ๋๋ค.
Also applies to: 36-36
src/components/common/gathering-card/container.tsx (2)
15-16: Props ์ธํฐํ์ด์ค๊ฐ ์ ์ ์๋์ด ์์ต๋๋ค! ๐
๋น๋๊ธฐ ์์ ์ ์ํ Promise ํ์ ๊ณผ ํจ๊ป ์ข์์/์ข์์ ์ทจ์ ํธ๋ค๋ฌ๊ฐ ๋ช ํํ๊ฒ ์ ์๋์ด ์์ต๋๋ค.
48-60: ์ด์ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ๊ฐ ์์ง ํด๊ฒฐ๋์ง ์์์ต๋๋ค.
๋ก๋ฉ ์ํ ๊ด๋ฆฌ, ๋ ์ด์ค ์ปจ๋์ ๋ฐฉ์ง, ๊ทธ๋ฆฌ๊ณ ๋ ๋์ ์๋ฌ ์ฒ๋ฆฌ์ ๋ํ ์ด์ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ์ ์ ์์ฌํญ๋ค์ด ์์ง ๋ฐ์๋์ง ์์์ต๋๋ค.
src/components/gathering-list/gathering-card-carousel.tsx (1)
73-79: ์ด์ ๋ฆฌ๋ทฐ ์๊ฒฌ์ด ์์ง ๋ฐ์๋์ง ์์์ต๋๋ค
์ด์ ๋ฆฌ๋ทฐ์์ ์ ์๋ ๊ฐ์ ์ฌํญ๋ค์ด ์ฌ์ ํ ์ ํจํฉ๋๋ค:
- ์๋ฌ ์ฒ๋ฆฌ๊ฐ ๋๋ฝ๋์ด ์์ต๋๋ค
- Promise.resolve() ๋ฐํ์ ์ ์ฌ์ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํ ์ ์์ต๋๋ค
| // TODO: ์ถํ ์๋ฌ, ๋ก๋ฉ ์์ | ||
| if (isLoading) return <p>๋ก๋ฉ ์ค...</p>; | ||
| if (error) return <p>๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค</p>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
๋ก๋ฉ ๋ฐ ์๋ฌ ์ํ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ ํด์ฃผ์ธ์
ํ์ฌ ๊ตฌํ์ ๊ธฐ๋ณธ์ ์ธ ํ ์คํธ๋ง ํ์ํ๊ณ ์์ต๋๋ค. ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๊ธฐ ์ํด ๋ ๋์ UI ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ฌํญ์ ์ ์ํฉ๋๋ค:
- ๋ก๋ฉ ์ํ: Skeleton UI ๋๋ ๋ก๋ฉ ์คํผ๋ ์ฌ์ฉ
- ์๋ฌ ์ํ: ์๋ฌ ๋ฉ์์ง์ ํจ๊ป ์ฌ์๋ ๋ฒํผ ์ถ๊ฐ
- ๋ฐ์ดํฐ ์์ ์ํ: ์ ์ ํ ์๋ด ๋ฉ์์ง์ ํจ๊ป ์๊ฐ์ ํผ๋๋ฐฑ ์ ๊ณต
if (isLoading) return <LoadingSpinner />;
if (error) return (
<ErrorState
message="๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค"
onRetry={() => refetch()}
/>
);
if (!gatheringList || gatheringList.length === 0) return (
<EmptyState message="์์ง ๋ฑ๋ก๋ ๋ชจ์์ด ์์ต๋๋ค" />
);|
๐storybook: https://67206cc6ff9d7a05a3528ff8-wuotrdjohm.chromatic.com/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
๐งน Outside diff range and nitpick comments (7)
src/app/(crew)/layout.tsx (1)
14-26: Toast ์ค์ ์ด ์ ์ ํด ๋ณด์ ๋๋ค๋ง, ์ ๊ทผ์ฑ ๊ฐ์ ์ด ํ์ํฉ๋๋คToast ์๋ฆผ์ ๊ธฐ๋ณธ ์ค์ ์ ์ ์ ํ๋, ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์๋ฅผ ์ํ ์ ๊ทผ์ฑ ์์ฑ์ด ๋๋ฝ๋์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํด๋ณด์ธ์:
<ToastContainer position="bottom-right" autoClose={3000} hideProgressBar={false} newestOnTop={false} closeOnClick rtl={false} pauseOnFocusLoss draggable pauseOnHover theme="light" transition={Bounce} + role="alert" + aria-live="polite" />์ถ๊ฐ์ ์ผ๋ก, ์๋ฆผ์ด ํ์๋๋ ์๊ฐ(3์ด)์ด ๊ธด ๋ฉ์์ง๋ฅผ ์ฝ๊ธฐ์๋ ์งง์ ์ ์์ต๋๋ค. ๋ฉ์์ง ๊ธธ์ด์ ๋ฐ๋ผ ๋์ ์ผ๋ก ์๊ฐ์ ์กฐ์ ํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
src/app/(crew)/crew/_components/gathering-detail-modal/container.tsx (3)
26-28: toast ์ค์ ์ ๊ฐ์ ํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.ํ์ฌ ๊ตฌํ์ ์๋ํ์ง๋ง, ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ํด ์ถ๊ฐ ์ค์ ์ ๊ณ ๋ คํด๋ณด์ธ์.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํ ์ ์์ต๋๋ค:
- toast(message, { type }); + toast(message, { + type, + position: "bottom-right", + autoClose: 3000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + });
Line range hint
30-67: ์๋ฌ ๋ฉ์์ง๋ฅผ ๋ ๊ตฌ์ฒด์ ์ผ๋ก ๊ฐ์ ํ ์ ์์ต๋๋ค.ํ์ฌ ์๋ฌ ์ฒ๋ฆฌ๋ ์ ๊ตฌํ๋์ด ์์ง๋ง, ์ฌ์ฉ์์๊ฒ ๋ ๋ช ํํ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์๋ฌ ๋ฉ์์ง๋ฅผ ๊ตฌ์ฒดํํ๋ ๊ฒ์ ์ ์ํฉ๋๋ค:
- showToast(`์ฐธ์ฌ ์ค ์๋ฌ ๋ฐ์: ${error.message}`, 'error'); + const errorMessage = error.status === 403 + ? '์ฐธ์ฌ ๊ถํ์ด ์์ต๋๋ค. ํฌ๋ฃจ ๋ฉค๋ฒ๋ง ์ฐธ์ฌํ ์ ์์ต๋๋ค.' + : `์ฐธ์ฌํ ์ ์์ต๋๋ค: ${error.message}`; + showToast(errorMessage, 'error');
Line range hint
17-17: ํ ์คํธ ๊ตฌํ์ด ํ์ํฉ๋๋ค.ํ์ฌ TODO ์ฃผ์์ ๋ช ์๋ ๋๋ก ๋ก๊ทธ์ธ ํ ํฐ ๊ด๋ จ ํ ์คํธ๊ฐ ๋๋ฝ๋์ด ์์ต๋๋ค. ํ ์คํธ ๊ตฌํ์ ํตํด ์ปดํฌ๋ํธ์ ์์ ์ฑ์ ๋ณด์ฅํด์ผ ํฉ๋๋ค.
ํ ์คํธ ์ฝ๋ ์์ฑ์ ๋์๋๋ฆด๊น์? ๋ค์๊ณผ ๊ฐ์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ ์ํ ์ ์์ต๋๋ค:
- ๋ชจ๋ฌ ์ด๊ธฐ/๋ซ๊ธฐ ํ ์คํธ
- ์ฝ์ ์ฐธ์ฌ/์ทจ์ ๊ธฐ๋ฅ ํ ์คํธ
- ์๋ฌ ์ฒ๋ฆฌ ํ ์คํธ
- ํ ์คํธ ๋ฉ์์ง ํ์ ํ ์คํธ
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (1)
21-39: ์ข์์ ์ํ ๊ด๋ฆฌ ๊ฐ์ ์ด ํ์ํฉ๋๋คํ์ฌ ๊ตฌํ์์๋ API ํธ์ถ ์ค์ธ ์ํ๋ฅผ ์ฌ์ฉ์์๊ฒ ํ์ํ์ง ์๊ณ ์์ต๋๋ค. ์ฌ์ฉ์ ๊ฒฝํ ํฅ์์ ์ํด ๋ก๋ฉ ์ํ ๊ด๋ฆฌ๊ฐ ํ์ํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ ์ ์ํฉ๋๋ค:
+ const [isLikeLoading, setIsLikeLoading] = useState<number | null>(null); const handleLike = async (gatheringId: number) => { + setIsLikeLoading(gatheringId); try { await addLike(gatheringId); } catch (apiError) { if (apiError instanceof ApiError) { toast.error(`์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค: ${apiError.message}`); } + } finally { + setIsLikeLoading(null); } };์ด ๋ก๋ฉ ์ํ๋ฅผ
GatheringCardCarousel์ ์ ๋ฌํ์ฌ ์ข์์ ๋ฒํผ์ ๋ก๋ฉ ์ํ๋ฅผ ํ์ํ ์ ์์ต๋๋ค.src/components/common/gathering-card/container.tsx (2)
72-78: ์๋ฌ ๋ฉ์์ง์ ๊ตญ์ ํ(i18n) ์ ์ฉ์ ๊ณ ๋ คํด์ฃผ์ธ์.ํ์ฌ ์๋ฌ ๋ฉ์์ง๊ฐ ํ๊ธ๋ก ํ๋์ฝ๋ฉ๋์ด ์์ต๋๋ค. ์ถํ ๋ค๊ตญ์ด ์ง์์ ์ํด i18n ์์คํ ์ ๋์ ํ๋ ๊ฒ์ด ์ข๊ฒ ์ต๋๋ค.
์์ ๊ตฌํ:
- toast.error('๋ชจ์ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.'); + toast.error(t('errors.gathering.not_found')); - toast.error('๋ฐ์ดํฐ ํต์ ์ ์คํจํ์ต๋๋ค.'); + toast.error(t('errors.common.communication_failed'));
Line range hint
1-108: ์ปดํฌ๋ํธ ๋ก์ง ๋ถ๋ฆฌ๋ฅผ ํตํ ๊ตฌ์กฐ ๊ฐ์ ์ ์ ์๋๋ฆฝ๋๋ค.ํ์ฌ ์ปดํฌ๋ํธ๊ฐ ๋ค์๊ณผ ๊ฐ์ ์ฌ๋ฌ ์ฑ ์์ ๊ฐ์ง๊ณ ์์ต๋๋ค:
- ์ข์์ ์ํ ๊ด๋ฆฌ
- ๋ชจ๋ฌ ์ํ ๊ด๋ฆฌ
- ๋ ์ง ๊ด๋ จ ๋ก์ง
- API ์๋ฌ ์ฒ๋ฆฌ
์ด๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ถ๋ฆฌํ๋ฉด ์ ์ง๋ณด์์ฑ์ด ํฅ์๋ ๊ฒ ๊ฐ์ต๋๋ค:
// hooks/useLikeToggle.ts const useLikeToggle = (id: number, initialState: boolean) => { const [isLiked, setIsLiked] = useState(initialState); const [isLoading, setIsLoading] = useState(false); const toggleLike = async (onLike: Function, onUnlike: Function) => { // ์ข์์ ๋ก์ง ๊ตฌํ }; return { isLiked, isLoading, toggleLike }; }; // hooks/useGatheringDate.ts const useGatheringDate = (dateTime: string) => { // ๋ ์ง ๊ด๋ จ ๋ก์ง ๊ตฌํ }; // hooks/useGatheringError.ts const useGatheringError = (error: Error | null) => { // ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง ๊ตฌํ };
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
๐ Files selected for processing (5)
src/app/(crew)/crew/_components/gathering-detail-modal/container.tsx(2 hunks)src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx(2 hunks)src/app/(crew)/layout.tsx(2 hunks)src/components/common/gathering-card/container.tsx(4 hunks)src/components/gathering-list/liked-list-container.tsx(1 hunks)
๐ง Files skipped from review as they are similar to previous changes (1)
- src/components/gathering-list/liked-list-container.tsx
๐ Additional comments (6)
src/app/(crew)/crew/_components/gathering-detail-modal/container.tsx (1)
3-3: react-toastify ๋ผ์ด๋ธ๋ฌ๋ฆฌ import๊ฐ ์ ์ ํ ์ถ๊ฐ๋์์ต๋๋ค.
์ฝ๋๋ฒ ์ด์ค์ ์ผ๊ด์ฑ์ ์ํด toast ์๋ฆผ ์์คํ ์ด ์ ํตํฉ๋์์ต๋๋ค.
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (3)
4-4: Toast ๊ตฌํ ๋ฐฉ์ ๊ฐ์ ์ด ํ์ํฉ๋๋ค
ํ์ฌ toast.error()๋ฅผ ์ง์ ํธ์ถํ๋ ๋ฐฉ์์ ์ด์ ๋ฆฌ๋ทฐ์์ ์ง์ ๋ ๋ฌธ์ ์ ์ ์ฌํฉ๋๋ค. Toast ์ปดํฌ๋ํธ๋ฅผ React ์ปดํฌ๋ํธ๋ก ์ ์ ํ๊ฒ ์ฌ์ฉํ๋๋ก ์์ ์ด ํ์ํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ ์ ์ ์ํฉ๋๋ค:
- toast.error(`์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค: ${apiError.message}`);
+ {apiError instanceof ApiError && (
+ <Toast message={`์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค: ${apiError.message}`} type="error" />
+ )}Also applies to: 25-27, 35-37
46-48: ๋ก๋ฉ ๋ฐ ์๋ฌ ์ํ UI ๊ฐ์ ์ด ํ์ํฉ๋๋ค
์ด์ ๋ฆฌ๋ทฐ์์ ์ง์ ๋ ๋๋ก, ํ์ฌ ๊ตฌํ์ ๊ธฐ๋ณธ์ ์ธ ํ ์คํธ๋ง ํ์ํ๊ณ ์์ต๋๋ค. ์ฌ์ฉ์ ๊ฒฝํ ํฅ์์ ์ํด ๋ ๋์ UI ์ปดํฌ๋ํธ ์ฌ์ฉ์ด ํ์ํฉ๋๋ค.
53-59: Props ํ์
๊ฒ์ฆ์ด ํ์ํฉ๋๋ค
GatheringCardCarousel ์ปดํฌ๋ํธ์ ์ ๋ฌ๋๋ props์ ํ์
์์ ์ฑ์ ํ์ธํด์ฃผ์ธ์. ํนํ ์๋ก ์ถ๊ฐ๋ onLike, onUnlike, onShowLoginModal props์ ํ์
์ด ์ปดํฌ๋ํธ์ ์ธํฐํ์ด์ค์ ์ผ์นํ๋์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
โ Verification successful
Props ํ์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ ์๋์ด ์์ต๋๋ค
GatheringCardCarousel ์ปดํฌ๋ํธ์ props ํ์
์ด ์ธํฐํ์ด์ค์ ๋ช
ํํ๊ฒ ์ ์๋์ด ์์ผ๋ฉฐ, ์ ๋ฌ๋๋ ๋ชจ๋ props(onLike, onUnlike, onShowLoginModal)๊ฐ ํ์
์ ์์ ์ผ์นํฉ๋๋ค:
onLike: (gatheringId: number) => Promise<void>onUnlike: (gatheringId: number) => Promise<void>onShowLoginModal: () => void
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# GatheringCardCarousel ์ปดํฌ๋ํธ์ props ํ์
์ ์ ํ์ธ
ast-grep --pattern 'interface $_ {
$$$
onLike?: $_
onUnlike?: $_
onShowLoginModal?: $_
$$$
}'
Length of output: 105
Script:
#!/bin/bash
# Search for GatheringCardCarousel component definition and its props
ast-grep --pattern 'type GatheringCardCarouselProps = {
$$$
}'
# Also try interface pattern
ast-grep --pattern 'interface GatheringCardCarouselProps {
$$$
}'
# Search for the component definition
ast-grep --pattern 'const GatheringCardCarousel = ({ $$ }: $_) => {
$$$
}'
# Find the file containing GatheringCardCarousel
fd GatheringCardCarousel
Length of output: 896
src/components/common/gathering-card/container.tsx (2)
15-16: ์ธํฐํ์ด์ค ์ ์๊ฐ ๋ช
ํํ๊ณ ์ ์ ํฉ๋๋ค.
Promise๋ฅผ ๋ฐํํ๋ ๋น๋๊ธฐ ํจ์๋ก ํ์ ์ด ์ ์ ์๋์ด ์์ต๋๋ค.
48-60: ์ด์ ๋ฆฌ๋ทฐ ์๊ฒฌ์ ๋ฐ์ํ ๊ฐ์ ์ด ํ์ํฉ๋๋ค.
์ด์ ๋ฆฌ๋ทฐ์์ ์ ์๋ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ์ ์๋ฌ ์ฒ๋ฆฌ ๊ฐ์ ์ด ์์ง ๋ฐ์๋์ง ์์์ต๋๋ค. ํด๋น ๊ฐ์ ์ฌํญ๋ค์ ์ ์ฉํ์๋ฉด ์ข๊ฒ ์ต๋๋ค.
| @@ -1,3 +1,4 @@ | |||
| import { Bounce, ToastContainer } from 'react-toastify'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์คํ์ผ import ์์ ๊ฐ์ ์ด ํ์ํฉ๋๋ค
react-toastify์ ์คํ์ผ ํ์ผ์ด import๋์ง ์์์ต๋๋ค. Toast ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์คํ์ผ๋ง๋๋ ค๋ฉด CSS import๊ฐ ํ์ํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ ํด์ฃผ์ธ์:
import { Bounce, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
import '@mantine/core/styles.css';๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { Bounce, ToastContainer } from 'react-toastify'; | |
| import { Bounce, ToastContainer } from 'react-toastify'; | |
| import 'react-toastify/dist/ReactToastify.css'; |
| const handleLoginRedirect = () => { | ||
| const currentPath = window.location.href; | ||
| router.push(`/login?redirect=${encodeURIComponent(currentPath)}`); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URL ์ธ์ฝ๋ฉ ๋ณด์ ๊ฐํ๊ฐ ํ์ํฉ๋๋ค
ํ์ฌ ๊ตฌํ์์๋ window.location.href๋ฅผ ์ง์ ์ฌ์ฉํ๊ณ ์์ด ์ ์ฌ์ ์ธ ๋ณด์ ์ํ์ด ์์ ์ ์์ต๋๋ค. URL ํ๋ผ๋ฏธํฐ์ ์ ์ ํ ๊ฒ์ฆ๊ณผ ์ ์ ๊ฐ ํ์ํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ ์ ์ํฉ๋๋ค:
const handleLoginRedirect = () => {
- const currentPath = window.location.href;
+ const currentPath = window.location.pathname + window.location.search;
+ const sanitizedPath = currentPath.replace(/[^\w\-\.\~\/\?\[\]\@\!\$\&\'\(\)\*\+\,\;\=]/g, '');
- router.push(`/login?redirect=${encodeURIComponent(currentPath)}`);
+ router.push(`/login?redirect=${encodeURIComponent(sanitizedPath)}`);
};๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleLoginRedirect = () => { | |
| const currentPath = window.location.href; | |
| router.push(`/login?redirect=${encodeURIComponent(currentPath)}`); | |
| }; | |
| const handleLoginRedirect = () => { | |
| const currentPath = window.location.pathname + window.location.search; | |
| const sanitizedPath = currentPath.replace(/[^\w\-\.\~\/\?\[\]\@\!\$\&\'\(\)\*\+\,\;\=]/g, ''); | |
| router.push(`/login?redirect=${encodeURIComponent(sanitizedPath)}`); | |
| }; |
|
๐storybook: https://67206cc6ff9d7a05a3528ff8-pezabfvkuq.chromatic.com/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
๐งน Outside diff range and nitpick comments (2)
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (2)
53-59: Props ํ์ ์ ์ ํ์GatheringCardCarousel ์ปดํฌ๋ํธ์ ์ ๋ฌ๋๋ props์ ํ์ ์ด ๋ช ์์ ์ผ๋ก ์ ์๋์ด ์์ง ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ํ์ ์ ์ ์ํ๋ ๊ฒ์ ์ ์๋๋ฆฝ๋๋ค:
interface GatheringCardCarouselProps { gatheringData: GatheringData[]; crewId: number; onLike: (gatheringId: number) => Promise<void>; onUnlike: (gatheringId: number) => Promise<void>; onShowLoginModal: () => void; }
60-68: ๋ชจ๋ฌ ์ํ ๊ด๋ฆฌ ๊ฐ์ ํ์๋ชจ๋ฌ์ opened prop์ด showLoginModal state์ ์ค๋ณต๋์ด ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํ๋ ๊ฒ์ ์ ์๋๋ฆฝ๋๋ค:
{showLoginModal && ( <ConfirmModal - opened={showLoginModal} onClose={() => setShowLoginModal(false)} onConfirm={handleLoginRedirect} > ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค! </ConfirmModal> )}
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
๐ Files selected for processing (2)
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx(2 hunks)src/app/(crew)/layout.tsx(2 hunks)
๐ง Files skipped from review as they are similar to previous changes (1)
- src/app/(crew)/layout.tsx
๐ Additional comments (3)
src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx (3)
4-4: toast ์ปดํฌ๋ํธ ์ฌ์ฉ ๋ฐฉ์ ๊ฐ์ ํ์
์ด์ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ์์ ์ง์ ๋ ๊ฒ์ฒ๋ผ, toast ์ปดํฌ๋ํธ๊ฐ ํจ์ํํ๋ก ํธ์ถ๋๊ณ ์์ต๋๋ค. React ์ปดํฌ๋ํธ๋ก ์ ์ ํ ๋ ๋๋ง๋๋๋ก ์์ ์ด ํ์ํฉ๋๋ค.
41-44: ๋ก๊ทธ์ธ ๋ฆฌ๋ค์ด๋ ํธ ๋ก์ง ๋ณด์ ๊ฐํ ํ์
ํ์ฌ ๊ตฌํ์์ URL ํ๋ผ๋ฏธํฐ์ ๋ํ ์ ์ ํ ๊ฒ์ฆ์ด ๋๋ฝ๋์ด ์์ต๋๋ค.
46-48: ๋ก๋ฉ ๋ฐ ์๋ฌ ์ํ UI ๊ฐ์ ํ์
ํ์ฌ ๋จ์ ํ ์คํธ๋ก ํ์๋๋ ๋ก๋ฉ ๋ฐ ์๋ฌ ์ํ๋ฅผ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ณ ๋ คํ UI ์ปดํฌ๋ํธ๋ก ๊ฐ์ ํด์ผ ํฉ๋๋ค.
| const handleLike = async (gatheringId: number) => { | ||
| try { | ||
| await addLike(gatheringId); | ||
| } catch (apiError) { | ||
| if (apiError instanceof ApiError) { | ||
| toast.error(`์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค: ${apiError.message}`); | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
์๋ฌ ์ฒ๋ฆฌ ๋ก์ง ๊ฐ์ ํ์
์๋ฌ ์ฒ๋ฆฌ ๋ก์ง์ด handleLike์ handleUnlike์์ ์ค๋ณต๋์ด ์์ต๋๋ค. ๋ํ ์ผ๋ฐ์ ์ธ ์๋ฌ ์ํฉ์ ๋ํ ์ฒ๋ฆฌ๊ฐ ๋๋ฝ๋์ด ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํ๋ ๊ฒ์ ์ ์๋๋ฆฝ๋๋ค:
+ const handleApiError = (action: string, error: unknown) => {
+ if (error instanceof ApiError) {
+ toast.error(`${action}์ ์คํจํ์ต๋๋ค: ${error.message}`);
+ } else {
+ toast.error(`${action} ์ค ์์์น ๋ชปํ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค`);
+ console.error(error);
+ }
+ };
const handleLike = async (gatheringId: number) => {
try {
await addLike(gatheringId);
} catch (error) {
- if (apiError instanceof ApiError) {
- toast.error(`์ฐํ๊ธฐ์ ์คํจํ์ต๋๋ค: ${apiError.message}`);
- }
+ handleApiError('์ฐํ๊ธฐ', error);
}
};
const handleUnlike = async (gatheringId: number) => {
try {
await removeLike(gatheringId);
} catch (error) {
- if (apiError instanceof ApiError) {
- toast.error(`์ฐํ๊ธฐ ํด์ ์ ์คํจํ์ต๋๋ค: ${apiError.message}`);
- }
+ handleApiError('์ฐํ๊ธฐ ํด์ ', error);
}
};Also applies to: 31-39
๐ Issue Ticket
#121
โ๏ธ Description
โ Checklist
PR
Test
Summary by CodeRabbit
๋ฆด๋ฆฌ์ค ๋ ธํธ
์ ๊ท ๊ธฐ๋ฅ
LikedList๋ฐLikedListPresenter์ปดํฌ๋ํธ๋ก ์ฌ์ฉ์ ์ธํฐํ์ด์ค ๊ฐ์ .GatheringListSection์ปดํฌ๋ํธ์ ์ข์์ ๋ฐ ์ข์์ ์ทจ์ ๊ธฐ๋ฅ ํตํฉ.GatheringCardCarousel์ปดํฌ๋ํธ์ ์ฌ์ฉ์ ์ธ์ฆ ์ฒดํฌ ๊ธฐ๋ฅ ์ถ๊ฐ.๋ฒ๊ทธ ์์