-
Notifications
You must be signed in to change notification settings - Fork 7
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
Guild Member Setting 페이지 #275
Conversation
…ld-detail-modify # Conflicts: # apps/web/src/app/[locale]/guild/page.tsx # packages/api/src/guild/index.ts
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 개요워크스루이 풀 리퀘스트는 길드(Guild) 기능의 전면적인 구현을 포함하고 있습니다. 새로운 컴포넌트, API 엔드포인트, 라우팅 로직, 그리고 사용자 인터페이스 요소들이 추가되었습니다. 길드 생성, 가입, 멤버 관리, 세부 정보 표시 등 길드와 관련된 다양한 기능들이 구현되었습니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant User
participant GuildCreatePage
participant CreateGuildAPI
participant Router
User->>GuildCreatePage: 길드 생성 페이지 접속
User->>GuildCreatePage: 길드 정보 입력
User->>GuildCreatePage: 페르소나 선택
GuildCreatePage->>CreateGuildAPI: 길드 생성 요청
CreateGuildAPI-->>GuildCreatePage: 생성 성공
GuildCreatePage->>Router: 새로 생성된 길드 페이지로 이동
관련 가능성 있는 PR들
제안된 라벨
제안된 리뷰어hyesungoh 시 (Poem)
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
Documentation and Community
|
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: 50
🔭 Outside diff range comments (1)
apps/web/src/app/[locale]/shop/AuctionSection/HistoryTable.tsx (1)
Line range hint
22-26
: 데이터 처리 로직 개선이 필요합니다.현재 구현에서 다음 사항들을 고려해보시면 좋겠습니다:
- 데이터 페칭 에러 처리가 없습니다
- 데이터가 없는 경우의 처리가 명확하지 않습니다
예시 구현:
const { data } = useQuery({ ...auctionQueries.historyOptions({ pageNumber: currentPage, ...searchOptions }), placeholderData: (prevData) => prevData, + onError: (error) => { + // 에러 처리 로직 추가 + }, }); + const NoDataView = () => ( + <div className={tbodyCss}> + <div>{t('no_history_data')}</div> + </div> + ); return ( <> <div className={tableCss}> {/* ... thead ... */} <div className={tbodyCss}> {!data && Array.from({ length: 8 }).map((_, index) => <ShopTableRowViewSkeleton key={`skeleton-${index}`} />)} + {data?.products.length === 0 && <NoDataView />} {data?.products.map((product) => { // ... 현재 구현 ... })} </div> </div> </> );
🧹 Nitpick comments (55)
packages/api/src/auction/getProductHistories.ts (1)
Line range hint
9-12
: 상수 타입을 별도의 스키마로 분리하는 것을 고려해보세요.
orderType
과sortDirection
에 대한 유니온 타입이getProducts.ts
의 스키마와 중복됩니다. 이러한 상수 타입을 공통 스키마로 분리하면 코드 재사용성이 향상될 것 같습니다.apps/web/src/app/[locale]/shop/AuctionSection/SellSection/PetList.tsx (1)
30-30
: 상태 표현 방식이 개선되었습니다!불리언 값 대신 문자열 상태를 사용하여 컴포넌트의 상태를 더 명확하게 표현하고 있습니다. 향후 확장성을 위해 status 타입을 상수나 enum으로 정의하는 것을 고려해보세요.
// 예시: 상수로 정의 const BANNER_STATUS = { SELECTED: 'selected', DEFAULT: 'default' } as const; type BannerStatus = typeof BANNER_STATUS[keyof typeof BANNER_STATUS];apps/web/src/app/[locale]/mypage/PersonaList.tsx (1)
88-93
: 상태 처리와 로딩 상태가 잘 구현되어 있습니다!Banner 컴포넌트의 상태 처리가 일관성 있게 구현되어 있고, 로딩 상태도 적절히 처리되어 있습니다. 성능 최적화를 위해 status 값을 useMemo로 캐싱하는 것을 고려해보세요.
const bannerStatus = useMemo( () => (isSelected ? 'selected' : 'default'), [isSelected] );apps/web/src/app/[locale]/shop/AuctionSection/PersonaSearch.tsx (1)
95-95
: 중복 로직을 컴포넌트로 분리하면 좋을 것 같습니다.이벤트 페르소나와 일반 페르소나에서 Banner 컴포넌트를 동일한 방식으로 사용하고 있습니다. 코드 재사용성을 높이기 위해 별도의 컴포넌트로 분리하는 것을 추천드립니다.
interface PersonaBannerProps { type: string; selected?: string; onClick: (type: string) => void; } const PersonaBanner = ({ type, selected, onClick }: PersonaBannerProps) => ( <button onClick={() => onClick(type)}> <Banner image={getPersonaImage(type)} status={selected === type ? 'selected' : 'default'} /> </button> );Also applies to: 107-110
apps/web/src/app/[locale]/shop/AuctionSection/HistoryTable.tsx (1)
Line range hint
35-38
: 날짜 포맷팅 로직 개선이 필요합니다.현재 문자열 조작으로 처리되는 날짜 포맷팅을 개선하는 것이 좋겠습니다.
date-fns
또는dayjs
와 같은 라이브러리를 사용하면 더 안정적이고 유지보수가 쉬운 코드가 될 것 같습니다.예시 구현:
- const getHistoryActionLabel = (soldAt: string) => { - return String(soldAt)?.slice(2, 10).replace(/-/g, '.'); - }; + import { format } from 'date-fns'; + + const getHistoryActionLabel = (soldAt: string) => { + return format(new Date(soldAt), 'yy.MM.dd'); + };apps/web/src/serverActions/guild.ts (1)
28-55
: 에러 처리와 로깅을 개선해주세요.다음 사항들의 개선이 필요합니다:
- 중복된 console.log 제거
- 주석 처리된 redirect 로직에 대한 결정
- 에러 메시지 한글화
try { await updateGuild(guildId, { title, body, farmType, guildIcon, autoJoin: true, }); - // redirect(`/guild/${guildId}`); - return { message: 'Guild updated', success: true }; + return { message: '길드가 업데이트되었습니다', success: true }; } catch (error) { - console.log('error: ', error); - console.log('error: ', (error as Error).message); + console.error('길드 업데이트 중 오류 발생:', error); - return { message: 'Failed to update guild', success: false }; + return { message: '길드 업데이트에 실패했습니다', success: false }; }packages/api/src/_instance/safe.ts (1)
3-3
: import 경로 최적화 검토 필요
CustomException
import 경로가 매우 구체적으로 지정되어 있습니다. 이는 빌드 최적화에 영향을 미칠 수 있습니다.제안사항:
패키지 레벨에서 export하도록 수정하여 import 경로를 단순화하는 것을 고려해보세요.-import { CustomException } from '@gitanimals/exception/src/CustomException'; +import { CustomException } from '@gitanimals/exception';packages/api/src/guild/kickMemberFromGuild.ts (1)
4-9
: API 요청 구조와 타입 정의가 명확합니다.zod를 사용한 스키마 검증이 잘 구현되어 있습니다. 다만, API 엔드포인트에서 사용되는 파라미터 네이밍 컨벤션을 일관성 있게 유지하는 것이 좋겠습니다.
- 'user-id': request.userId, + userId: request.userId,이렇게 수정하면 전체 API에서 일관된 네이밍 컨벤션을 유지할 수 있습니다.
Also applies to: 11-17
packages/api/src/guild/getGuildIcons.ts (1)
6-11
: 응답 타입 정의에 대한 개선 제안
GetGuildIconsResponse
타입이IconSchema
에서 직접 추론되고 있는데,GetGuildIconsResponseSchema
에서 추론하는 것이 더 명확할 것 같습니다.- export type GetGuildIconsResponse = z.infer<typeof IconSchema>[]; + export type GetGuildIconsResponse = z.infer<typeof GetGuildIconsResponseSchema>;packages/api/src/guild/getGuildBackgrounds.ts (1)
9-15
: 응답 타입 정의 개선이 필요합니다.
getGuildIcons.ts
와 동일한 패턴으로, 응답 타입 정의를 개선하면 좋을 것 같습니다.- export type GetGuildBackgroundsResponse = z.infer<typeof BackgroundSchema>[]; + export type GetGuildBackgroundsResponse = z.infer<typeof GetGuildBackgroundsResponseSchema>;apps/web/src/components/GNB/menu.constants.tsx (1)
17-21
: 길드 메뉴 아이콘 및 순서 검토 필요
- 길드를 나타내는 아이콘으로
HouseIcon
보다Users
또는Group
아이콘이 더 직관적일 것 같습니다.- 메뉴 순서가 적절한지 검토가 필요합니다. 현재 shop 다음에 위치해 있는데, 사용자 경험 관점에서 이 순서가 최적인지 확인해주세요.
{ label: 'guild', href: '/guild', - icon: <HouseIcon size={20} color="#9295A1" />, + icon: <UsersIcon size={20} color="#9295A1" />, },packages/api/src/guild/joinGuild.ts (1)
14-16
: API 엔드포인트 설계 및 에러 처리 개선 필요
- 현재 엔드포인트가 RESTful 하지 않습니다.
/guilds/{guildId}/join
이 더 적절할 것 같습니다.- 실패 케이스에 대한 명시적인 에러 처리가 필요합니다.
다음과 같은 개선을 제안합니다:
export const joinGuild = async (request: JoinGuildRequest): Promise<JoinGuildResponse> => { - return renderPost(`/guilds/${request.guildId}`, { personaId: request.personaId }); + try { + return await renderPost( + `/guilds/${request.guildId}/join`, + { personaId: request.personaId } + ); + } catch (error) { + if (error instanceof ApiError) { + switch (error.status) { + case 403: + throw new Error('이미 길드에 가입되어 있습니다.'); + case 404: + throw new Error('길드를 찾을 수 없습니다.'); + default: + throw new Error('길드 가입 중 오류가 발생했습니다.'); + } + } + throw error; + } };apps/web/src/app/[locale]/guild/layout.tsx (1)
15-26
: 스타일 구현에 대한 제안사항이 있습니다.현재 구현은 전반적으로 잘 되어 있으나, 다음과 같은 개선사항을 고려해보시기 바랍니다:
calc(100vh - 60px)
의 매직 넘버를 상수로 분리하는 것이 좋습니다.- mobile 스타일의
100vh
와 공백 문자 오타를 수정해야 합니다.다음과 같이 수정하는 것을 추천드립니다:
const HEADER_HEIGHT = '60px'; const containerStyle = css({ width: '100%', background: 'linear-gradient(180deg, #000 0%, #004875 38.51%, #005B93 52.46%, #006FB3 73.8%, #0187DB 100%)', minHeight: 'fit-content', - height: 'calc(100vh - 60px)', + height: `calc(100vh - ${HEADER_HEIGHT})`, overflow: 'hidden', position: 'relative', _mobile: { - minHeight: '100vh ', + minHeight: '100vh', }, });packages/api/src/guild/createGuild.ts (1)
20-22
: 에러 처리 개선이 필요합니다.API 호출 시 발생할 수 있는 에러에 대한 처리가 부족합니다. 사용자에게 더 명확한 에러 메시지를 제공하는 것이 좋습니다.
다음과 같은 에러 처리 개선을 제안드립니다:
export const createGuild = (request: CreateGuildRequest): Promise<CreateGuildResponse> => { - return safeRenderPost(CreateGuildResponseSchema)('/guilds', request); + return safeRenderPost(CreateGuildResponseSchema)('/guilds', request) + .catch((error) => { + if (error.response?.status === 409) { + throw new Error('이미 존재하는 길드 이름입니다'); + } + throw new Error('길드 생성 중 오류가 발생했습니다. 다시 시도해주세요'); + }); };apps/web/src/app/[locale]/guild/[id]/setting/layout.tsx (1)
7-23
: 시맨틱 HTML 구조 개선이 필요합니다현재 일반
div
태그들로만 구성되어 있습니다. 접근성과 시맨틱한 구조를 위해 다음과 같이 수정하는 것을 추천드립니다:- <div className={css({ minH: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' })}> + <main className={css({ minH: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' })}> - <div + <section className={css({ maxWidth: '880px', mx: 'auto', background: 'gray.gray_150', p: '40px', borderRadius: '16px', color: 'white.white', w: '100%', })} > <Flex flexDirection="column" gap="24px"> {children} </Flex> - </div> - </div> + </section> + </main>apps/web/src/app/[locale]/guild/detail/[id]/layout.tsx (2)
13-21
: 디자인 토큰 사용을 권장드립니다스타일 상수에 하드코딩된 값들이 많이 있습니다. 유지보수성과 일관성을 위해 디자인 토큰을 사용하는 것이 좋겠습니다:
maxWidth: '1000px'
padding: '20px'
padding: '60px 100px'
borderRadius: '16px'
이러한 값들을 디자인 시스템의 토큰으로 관리하면 향후 디자인 변경 시 더 효율적으로 대응할 수 있습니다.
Also applies to: 23-32
17-17
: 계산된 높이값 개선이 필요합니다
minHeight: 'calc(100vh - 60px)'
에서 60px이 하드코딩되어 있습니다. 이 값이 네비게이션 바의 높이라면, 상수나 CSS 변수로 관리하는 것이 좋겠습니다.- minHeight: 'calc(100vh - 60px)', + minHeight: 'calc(100vh - var(--navbar-height))',apps/web/src/components/Guild/MemeberSlider.tsx (1)
10-24
: 접근성 개선이 필요합니다슬라이더 컴포넌트에 다음과 같은 접근성 개선이 필요합니다:
- ARIA 레이블 추가
- 키보드 네비게이션 지원
- 스크린 리더 지원
또한 에러 바운더리와 로딩 상태 처리도 추가하면 좋겠습니다.
packages/api/src/guild/schema.ts (1)
15-31
: Guild 스키마에 추가적인 유효성 검사가 필요합니다.title, body 등의 필드에 대한 길이 제한이나 추가적인 유효성 검사 규칙이 없습니다. 데이터 품질 보장을 위해 추가하는 것이 좋겠습니다.
export const GuildSchema = z.object({ id: z.string(), - title: z.string(), + title: z.string().min(1).max(100), - body: z.string(), + body: z.string().max(1000), guildIcon: z.string(), leader: z.object({ userId: z.string(), name: z.string(), - contributions: z.string(), + contributions: z.number(), personaId: z.string(), personaType: z.string(), }), farmType: z.string(), - totalContributions: z.string(), + totalContributions: z.number(), members: z.array(GuildMemberSchema), waitMembers: z.array(GuildMemberSchema), createdAt: z.string(), });packages/lib/react-query/src/guild/queries.ts (1)
8-11
: 쿼리 설정에 캐싱 전략을 추가해주세요.현재 쿼리 설정에 staleTime과 cacheTime이 정의되어 있지 않습니다. 성능 최적화를 위해 적절한 캐싱 전략을 추가하는 것이 좋겠습니다.
queryOptions({ queryKey: guildQueries.getGuildById(guildId), queryFn: () => getGuildById({ guildId }), + staleTime: 1000 * 60 * 5, // 5분 + cacheTime: 1000 * 60 * 30, // 30분 + retry: 3, }),apps/web/src/app/[locale]/guild/[id]/@modal/(.)setting/page.tsx (3)
9-15
: 데이터 페칭 최적화 및 에러 처리 필요데이터 페칭 로직에 다음과 같은 개선이 필요합니다:
- 권한 체크와 데이터 페칭을 병렬로 처리하여 성능을 개선할 수 있습니다.
- 데이터 페칭 실패 시 에러 처리가 누락되어 있습니다.
export default async function GuildSettingModal({ params }: { params: { id: string } }) { - const isLeader = await checkIsLeader(params.id); - - const icons = await getGuildIcons(); - const backgrounds = await getGuildBackgrounds(); - const data = await getGuildById({ guildId: params.id }); + const [isLeader, icons, backgrounds, data] = await Promise.all([ + checkIsLeader(params.id), + getGuildIcons(), + getGuildBackgrounds(), + getGuildById({ guildId: params.id }).catch(error => { + console.error('Failed to fetch guild data:', error); + throw error; + }) + ]);
16-18
: 권한 체크 위치 최적화 필요권한 체크를 데이터 페칭 이후에 수행하면 불필요한 API 호출을 방지할 수 있습니다.
20-26
: 타입 안전성 개선 필요컴포넌트 props의 타입 정의가 누락되어 있습니다. 타입스크립트의 이점을 최대한 활용하기 위해 props 타입을 명시적으로 정의하는 것이 좋습니다.
apps/web/src/app/[locale]/guild/(components)/GuildPetSelectDialog.tsx (1)
17-21
: 에러 처리 보완 필요
onDone
함수에서selectPersona
가 없는 경우에 대한 사용자 피드백이 누락되어 있습니다.const onDone = () => { if (selectPersona) { onSubmit(selectPersona); + } else { + toast.error(t('guild.select_persona_required')); } };apps/web/src/app/[locale]/guild/[id]/setting/member/MemberCard.tsx (1)
22-30
: 접근성 개선 필요Remove 버튼에 적절한 aria-label이 누락되어 있습니다.
bottomElement={ - <Button onClick={kickMember}>Remove</Button> + <Button + onClick={kickMember} + aria-label={t('guild.remove_member_aria_label', { name: member.name })} + > + {t('guild.remove_member')} + </Button> }apps/web/src/app/[locale]/guild/[id]/setting/member/BannerGuildMember.tsx (1)
27-35
: 반응형 디자인 보완 필요반응형 디자인을 위한 미디어 쿼리가 누락되어 있습니다.
const bannerStyle = css({ display: 'inline-flex', padding: '8px 16px 16px 16px', flexDirection: 'column', alignItems: 'center', gap: '12px', backgroundColor: 'white.white_10', borderRadius: '8px', + '@media (max-width: 768px)': { + padding: '4px 8px 8px 8px', + gap: '8px', + } });apps/web/src/components/InterceptingDialog.tsx (2)
20-27
: useEffect 로직 최적화 필요현재 useEffect 내의 조건문이 불필요하게 복잡합니다. 더 간단한 구현이 가능합니다.
다음과 같이 변경하는 것을 추천드립니다:
useEffect(() => { - if (!isOpen) { - setIsOpen(true); - } else { - setIsOpen(false); - } + setIsOpen(!isOpen); // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname]);
30-34
: 접근성 속성 추가 필요Dialog 컴포넌트에 접근성 관련 속성이 부족합니다.
다음 속성들의 추가를 고려해주세요:
<Dialog open={isOpen} onOpenChange={onClose}> - <Dialog.Content size="large" className={dialogContentStyle}> + <Dialog.Content + size="large" + className={dialogContentStyle} + aria-describedby="dialog-description" + role="dialog" + >packages/api/src/guild/searchGuild.ts (1)
7-8
: 필터 타입 개선 제안현재 필터 타입이 매직 스트링으로 되어있어 유지보수가 어려울 수 있습니다.
상수로 분리하여 관리하는 것을 추천드립니다:
+ export const FILTER_TYPES = { + RANDOM: 'RANDOM', + PEOPLE_ASC: 'PEOPLE_ASC', + PEOPLE_DESC: 'PEOPLE_DESC', + CONTRIBUTION_ASC: 'CONTRIBUTION_ASC', + CONTRIBUTION_DESC: 'CONTRIBUTION_DESC', + } as const; - const FilterSchema = z.enum(['RANDOM', 'PEOPLE_ASC', 'PEOPLE_DESC', 'CONTRIBUTION_ASC', 'CONTRIBUTION_DESC']); + const FilterSchema = z.enum(Object.values(FILTER_TYPES));apps/web/src/app/[locale]/guild/(components)/GuildSearch.tsx (1)
31-42
: 접근성 및 사용성 개선 필요검색 컴포넌트의 접근성이 부족합니다.
다음과 같은 개선사항을 제안드립니다:
return ( <> {text && ( - <button onClick={onSearchReset}> + <button + onClick={onSearchReset} + aria-label="검색 초기화" + className="reset-button" + > <ChevronLeftIcon size="28px" color="#FFFFFF80" /> </button> )} <Box flex="1"> <SearchBar defaultValue={text} onChange={(e) => setInput(e.target.value)} onSubmit={onSubmit} + placeholder="길드 검색..." + aria-label="길드 검색 입력창" /> </Box> </> );apps/web/src/app/[locale]/guild/@modal/GuildModal.tsx (2)
14-28
: 상태 관리 로직 개선 필요현재 상태 관리 로직이 복잡하고 불필요한 리렌더링을 발생시킬 수 있습니다.
다음과 같은 개선을 제안드립니다:
- const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = useState(true); useEffect(() => { - if (!isOpen) { - setIsOpen(true); - } else { - setIsOpen(false); - } + setIsOpen(true); // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname]);
29-36
: 로딩 상태 처리 추가 필요모달이 열릴 때 컨텐츠 로딩 상태를 표시하지 않고 있습니다.
로딩 상태를 추가하는 것을 추천드립니다:
+ const [isLoading, setIsLoading] = useState(false); return ( <Dialog open={isOpen} onOpenChange={onClose}> <Dialog.Content size="large" className={dialogContentStyle}> {title && <Dialog.Title>{title}</Dialog.Title>} + {isLoading ? ( + <LoadingSpinner /> + ) : ( {children} + )} </Dialog.Content> </Dialog> );apps/web/src/app/[locale]/guild/[id]/layout.tsx (2)
7-16
: 이미지 최적화 개선 필요배경 이미지 구현에 다음과 같은 최적화가 필요합니다:
- 큰 이미지(3600x228)의 경우 성능에 영향을 줄 수 있습니다.
- 반응형 디자인을 위한 이미지 크기 최적화가 필요합니다.
다음과 같은 최적화를 제안합니다:
- <Image src="/guild/guild-bg-bottom.webp" className="bg-bottom" alt="bg-bottom" width={3600} height={228} /> + <Image + src="/guild/guild-bg-bottom.webp" + className="bg-bottom" + alt="길드 배경 이미지" + width={3600} + height={228} + sizes="100vw" + priority + />
34-53
: 모바일 환경 대응 필요현재 배경 이미지 스타일링이 모바일 환경에서 최적화되어 있지 않습니다.
다음과 같은 반응형 스타일 적용을 제안합니다:
const bottomBgStyle = css({ position: 'absolute', width: '100vw', bottom: 0, left: '50%', transform: 'translateX(-50%)', '& .bg-bottom': { height: '228px', objectFit: 'cover', }, '& .bg-bottom-house': { position: 'absolute', bottom: '32px', right: '62px', height: '202px', width: 'auto', objectFit: 'contain', + '@media (max-width: 768px)': { + right: '20px', + height: '150px', + }, }, });apps/web/src/app/[locale]/guild/[id]/setting/GuildSetting.tsx (1)
46-46
: 에러 메시지 UI 개선 필요현재 에러 메시지 표시 방식이 사용자 경험을 저해할 수 있습니다.
- {state?.message && <p aria-live="polite">{state?.message}</p>} + {state?.message && ( + <p + aria-live="polite" + className={css({ + color: 'red.500', + mt: 2, + textAlign: 'center', + fontWeight: 'medium' + })} + > + {state?.message} + </p> + )}apps/web/src/app/[locale]/guild/(components)/SortSelect.tsx (2)
22-22
: 타입 안전성 개선 필요FilterType이 undefined일 경우의 처리가 암시적으로 이루어지고 있습니다. 명시적인 타입 가드를 추가하는 것이 좋습니다.
- const filter = (searchParams.get('filter') as FilterType) ?? 'RANDOM'; + const filterParam = searchParams.get('filter'); + const filter: FilterType = filterParam && Object.values(FilterType).includes(filterParam as FilterType) + ? (filterParam as FilterType) + : 'RANDOM';
10-16
: 상수 분리 및 국제화 적용 필요정렬 옵션의 레이블이 하드코딩되어 있습니다. 이를 국제화하고 상수로 분리하는 것이 좋습니다.
+ import { useTranslations } from 'next-intl'; + + const SORT_OPTIONS = ['RANDOM', 'PEOPLE_ASC', 'PEOPLE_DESC', 'CONTRIBUTION_ASC', 'CONTRIBUTION_DESC'] as const; + export function SortSelect() { + const t = useTranslations('guild.sort'); const options = SORT_OPTIONS.map((value) => ({ - label: 'Random', // 하드코딩된 레이블 + label: t(value.toLowerCase()), value, })); // ... }apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/join/page.tsx (1)
43-43
: 빈 스타일 객체 제거 필요빈 CSS 스타일 객체가 정의되어 있습니다. 필요한 스타일이 없다면 제거하는 것이 좋습니다.
- const dialogTitleStyle = css({});
apps/web/src/app/[locale]/guild/[id]/MoreMenu.tsx (2)
8-10
: 영문 주석으로 변경 필요한글로 작성된 주석이 있습니다. 국제적인 협업을 위해 영문으로 변경하는 것이 좋습니다.
- /** - * 더보기 메뉴 - * 현재는 모두 길드장만 볼 수 있음 - */ + /** + * More menu + * Currently only accessible to guild leader + */
12-14
: 로딩 상태 처리 필요isLeader 체크 시 로딩 상태 처리가 필요합니다.
+ import { Suspense } from 'react'; + import { LoadingSpinner } from '@/components/LoadingSpinner'; export async function MoreMenu({ guildId }: { guildId: string }) { + return ( + <Suspense fallback={<LoadingSpinner size="sm" />}> + <MoreMenuContent guildId={guildId} /> + </Suspense> + ); }apps/web/src/app/[locale]/guild/detail/[id]/join/page.tsx (1)
15-28
: 로딩 상태 처리 필요
submitJoinGuild
함수 실행 중 로딩 상태를 표시하여 사용자 경험을 개선해야 합니다.export default function GuildJoinPage({ params }: { params: { id: string } }) { const queryClient = useQueryClient(); const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); const submitJoinGuild = async (selectPersona: string) => { + setIsLoading(true); try { await joinGuildAction({ guildId: params.id, personaId: selectPersona, }); router.replace(`/guild`); queryClient.invalidateQueries({ queryKey: inboxQueries.allKey() }); } catch (error) { console.error(error); + } finally { + setIsLoading(false); } };apps/web/src/app/[locale]/guild/(components)/GuildCreate.tsx (1)
48-50
: 하드코딩된 가격 값 개선 필요버튼 텍스트에 하드코딩된 가격 값이 있습니다. 상수나 설정 값으로 관리하는 것이 좋습니다.
+ const GUILD_CREATION_COST = 100_000; - Create / 100,000P + Create / {GUILD_CREATION_COST.toLocaleString()}Papps/web/src/components/Pagination/PaginationServer.tsx (1)
28-29
: 하드코딩된 색상 값 개선 필요색상 값이 하드코딩되어 있습니다. 테마 시스템을 활용하는 것이 좋습니다.
- <ChevronLeft color="#B5B8C0" /> + <ChevronLeft color="token(colors.white.white_50)" /> - <ChevronRight color="#B5B8C0" /> + <ChevronRight color="token(colors.white.white_50)" />Also applies to: 48-49
apps/web/src/app/[locale]/guild/(components)/SelecableFormItem.tsx (1)
44-44
: 접근성 및 스타일링 개선 필요hidden input의 구현 방식을 개선해야 합니다.
- <input type="text" name={props.name} className="display-none" value={value ?? ''} hidden /> + <input + type="text" + name={props.name} + value={value ?? ''} + hidden + aria-hidden="true" + />apps/web/src/components/Gitanimals.tsx (2)
89-91
: 이미지 에러 처리와 접근성 개선이 필요합니다.다음 사항들을 개선하면 좋을 것 같습니다:
- 이미지 로드 실패 시 대체 처리
- 더 구체적인 alt 텍스트 (예:
{guildId} 길드의 이미지
)- return <img src={`https://render.gitanimals.org/guilds/${guildId}/draw`} alt="gitanimals" />; + return ( + <img + src={`https://render.gitanimals.org/guilds/${guildId}/draw`} + alt={`${guildId} 길드의 이미지`} + onError={(e) => { + e.currentTarget.src = '/default-guild-image.png'; + }} + /> + );
93-102
: 템플릿 리터럴의 들여쓰기를 조정해주세요.현재 들여쓰기가 HTML 출력에 영향을 줄 수 있습니다. 불필요한 공백을 제거하는 것이 좋겠습니다.
- return `<a href="https://www.gitanimals.org/"> - <img - src="https://render.gitanimals.org/guilds/${guildId}/draw" - width="${sizes[0]}" - height="${sizes[1]}" - alt="gitanimals" - /> - </a>`; + return `<a href="https://www.gitanimals.org/"><img src="https://render.gitanimals.org/guilds/${guildId}/draw" width="${sizes[0]}" height="${sizes[1]}" alt="gitanimals" /></a>`;apps/web/src/app/[locale]/guild/[id]/page.tsx (1)
44-44
: 매직 넘버를 상수로 추출해주세요.길드 최대 인원수(15)가 하드코딩되어 있습니다. 이 값을 상수로 추출하면 유지보수가 더 용이해질 것 같습니다.
+const MAX_GUILD_MEMBERS = 15; + export default async function GuildPage({ params }: { params: { id: string } }) { // ... - <span>{data.members.length}/ 15</span> + <span>{data.members.length}/ {MAX_GUILD_MEMBERS}</span>apps/web/src/app/[locale]/guild/(components)/GuildCreateForm.tsx (1)
36-50
: 접근성 개선과 중복된 key prop 제거가 필요합니다.
- 버튼에 aria-label을 추가하여 접근성을 개선해주세요.
- img 태그에서 중복된 key prop을 제거해주세요.
{icons?.map((icon) => ( - <button onClick={() => onDataChange('icon', icon)} key={icon}> + <button + onClick={() => onDataChange('icon', icon)} + key={icon} + aria-label={`길드 아이콘 ${icon} 선택`} + > <img src={icon} className={itemStyle} width={70} height={70} - key={icon} alt={icon} style={{ border: formData.icon === icon ? '1.5px solid' : 'none', }} /> </button> ))}apps/web/src/app/[locale]/guild/(components)/SelectPersonaList.tsx (1)
51-51
: 페르소나 정렬 기능 구현이 필요합니다.TODO 주석에 명시된 정렬 기능을 구현하는 것을 도와드릴까요? 레벨이나 획득 날짜 기준으로 정렬하는 것이 좋을 것 같습니다.
apps/web/src/app/[locale]/guild/(components)/GuildDetail.tsx (1)
46-46
: 하드코딩된 값을 상수로 분리멤버 수 제한(15)이 하드코딩되어 있습니다. 이 값을 상수로 분리하여 관리하는 것이 좋습니다.
+const MAX_MEMBERS = 15; + export const GuildDetail = wrap // ... - <span>{data.members.length}/ 15</span> + <span>{data.members.length}/ {MAX_MEMBERS}</span>apps/web/src/app/[locale]/guild/(components)/GuildCard.tsx (1)
43-43
: 하드코딩된 값을 상수로 분리멤버 수 제한(15)이 하드코딩되어 있습니다. 이 값을 상수로 분리하여 재사용성을 높이는 것이 좋습니다.
apps/web/src/app/[locale]/guild/page.tsx (4)
27-36
: 주석 처리된 코드를 정리해주세요.더 이상 필요하지 않은 주석 처리된 코드는 제거하는 것이 좋습니다. 특히 리다이렉션 로직이 필요하지 않다면 관련 주석을 모두 제거해주세요.
export default async function GuildPage({ searchParams }: GuildPageProps) { const allJoinGuilds = await getAllJoinGuilds(); - // if (allJoinGuilds.guilds.length === 0) { return <GuildMain searchParams={searchParams} />; - // } - // const guildId = allJoinGuilds.guilds[0].id; - // redirect(`/guild/${guildId}`); }
38-79
: 코드 스타일 개선이 필요합니다.
- 불필요한 CSS 주석을 제거해주세요.
- 랜덤 ID 생성 로직을 별도의 유틸리티 함수로 분리하는 것이 좋을 것 같습니다.
const cardListStyle = grid({ gridTemplateRows: 'repeat(3, 210px)', columns: 3, gap: '8px', w: 'full', - // flex: 1, _mobile: { columns: 1, }, });
81-138
: 스타일 구성이 잘 되어있습니다만, 몇 가지 개선사항이 있습니다.
- 스타일 상수들의 이름을 더 구체적으로 지정하면 좋을 것 같습니다. 예:
topStyle
→guildHeaderStyle
- 반응형 디자인 브레이크포인트를 상수로 분리하는 것을 고려해보세요.
140-149
: 접근성 개선이 필요합니다.이미지의 alt 텍스트를 번역 시스템을 통해 다국어로 제공하면 좋을 것 같습니다.
- <Image src="/guild/empty-image.webp" alt="empty" width={80} height={80} /> + <Image src="/guild/empty-image.webp" alt={t('Guild.empty')} width={80} height={80} />
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
apps/web/public/guild/guild-bg-bottom-house.png
is excluded by!**/*.png
apps/web/public/guild/guild-bg-bottom.png
is excluded by!**/*.png
apps/web/public/guild/init-bg-bottom.png
is excluded by!**/*.png
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (82)
apps/web/messages/en_US.json
(1 hunks)apps/web/messages/ko_KR.json
(1 hunks)apps/web/package.json
(1 hunks)apps/web/src/apis/interceptor.ts
(0 hunks)apps/web/src/app/[locale]/auth/page.tsx
(2 hunks)apps/web/src/app/[locale]/error.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/GuidlInfoForm.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/GuidlInfoFormClient.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/GuildCard.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/GuildCreate.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/GuildCreateForm.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/GuildDetail.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/GuildPetSelectDialog.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/GuildSearch.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/SelecableFormItem.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/SelectPersonaList.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(components)/SortSelect.tsx
(1 hunks)apps/web/src/app/[locale]/guild/(list)/GuildCard.tsx
(1 hunks)apps/web/src/app/[locale]/guild/@modal/(.)create/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/join/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/@modal/GuildModal.tsx
(1 hunks)apps/web/src/app/[locale]/guild/@modal/default.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/@modal/(.)setting/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/@modal/default.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/MoreMenu.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/layout.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/setting/GuildSetting.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/setting/layout.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/setting/member/BannerGuildMember.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/setting/member/MemberCard.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/setting/member/WaitMemberCard.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/setting/member/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/[id]/setting/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/create/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/detail/[id]/join/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/detail/[id]/layout.tsx
(1 hunks)apps/web/src/app/[locale]/guild/detail/[id]/page.tsx
(1 hunks)apps/web/src/app/[locale]/guild/layout.tsx
(1 hunks)apps/web/src/app/[locale]/guild/page.tsx
(1 hunks)apps/web/src/app/[locale]/mypage/PersonaList.tsx
(1 hunks)apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergePersona.tsx
(1 hunks)apps/web/src/app/[locale]/shop/AuctionSection/HistoryTable.tsx
(1 hunks)apps/web/src/app/[locale]/shop/AuctionSection/PersonaSearch.tsx
(2 hunks)apps/web/src/app/[locale]/shop/AuctionSection/ProductTable.tsx
(1 hunks)apps/web/src/app/[locale]/shop/AuctionSection/SellSection/PetList.tsx
(1 hunks)apps/web/src/app/[locale]/shop/SellListSection/index.tsx
(1 hunks)apps/web/src/app/layout.tsx
(2 hunks)apps/web/src/components/GNB/GNB.tsx
(1 hunks)apps/web/src/components/GNB/menu.constants.tsx
(2 hunks)apps/web/src/components/Gitanimals.tsx
(5 hunks)apps/web/src/components/Global/SessionLoader.tsx
(2 hunks)apps/web/src/components/Guild/MemeberSlider.tsx
(1 hunks)apps/web/src/components/InterceptingDialog.tsx
(1 hunks)apps/web/src/components/Pagination/PaginationServer.tsx
(1 hunks)apps/web/src/hooks/useGetNewUrl.tsx
(1 hunks)apps/web/src/serverActions/guild.ts
(1 hunks)packages/api/src/_instance/render.ts
(2 hunks)packages/api/src/_instance/safe.ts
(1 hunks)packages/api/src/auction/getMyProducts.ts
(1 hunks)packages/api/src/auction/getProductHistories.ts
(1 hunks)packages/api/src/auction/getProducts.ts
(1 hunks)packages/api/src/auction/schema.ts
(0 hunks)packages/api/src/guild/acceptJoinGuild.ts
(1 hunks)packages/api/src/guild/createGuild.ts
(1 hunks)packages/api/src/guild/denyJoinGuild.ts
(1 hunks)packages/api/src/guild/getAllJoinGuilds.ts
(1 hunks)packages/api/src/guild/getGuildBackgrounds.ts
(1 hunks)packages/api/src/guild/getGuildById.ts
(1 hunks)packages/api/src/guild/getGuildIcons.ts
(1 hunks)packages/api/src/guild/index.ts
(1 hunks)packages/api/src/guild/joinGuild.ts
(1 hunks)packages/api/src/guild/kickMemberFromGuild.ts
(1 hunks)packages/api/src/guild/schema.ts
(1 hunks)packages/api/src/guild/searchGuild.ts
(1 hunks)packages/api/src/guild/updateGuild.ts
(1 hunks)packages/api/src/index.ts
(2 hunks)packages/api/src/schema.ts
(1 hunks)packages/lib/react-query/src/guild/index.ts
(1 hunks)packages/lib/react-query/src/guild/queries.ts
(1 hunks)packages/lib/react-query/src/index.ts
(1 hunks)
⛔ Files not processed due to max files limit (20)
- packages/ui/panda/package.json
- packages/ui/panda/src/components/Banner/BannerPetSelect.tsx
- packages/ui/panda/src/components/Banner/cva.ts
- packages/ui/panda/src/components/Banner/index.ts
- packages/ui/panda/src/components/Chip/CombineChip/CombineChip.tsx
- packages/ui/panda/src/components/Chip/CombineChip/index.ts
- packages/ui/panda/src/components/Chip/index.ts
- packages/ui/panda/src/components/Dialog/Dialog.styles.ts
- packages/ui/panda/src/components/Dialog/Dialog.tsx
- packages/ui/panda/src/components/Dialog/index.ts
- packages/ui/panda/src/components/DropdownMenu/DropdownMenu.tsx
- packages/ui/panda/src/components/DropdownMenu/index.tsx
- packages/ui/panda/src/components/SearchBar/SearchBar.tsx
- packages/ui/panda/src/components/SearchBar/index.ts
- packages/ui/panda/src/components/Select/index.ts
- packages/ui/panda/src/components/Textfield/TextField.tsx
- packages/ui/panda/src/components/Textfield/Textarea.tsx
- packages/ui/panda/src/components/Textfield/index.ts
- packages/ui/panda/src/components/index.ts
- packages/util/common/src/url.ts
💤 Files with no reviewable changes (2)
- packages/api/src/auction/schema.ts
- apps/web/src/apis/interceptor.ts
✅ Files skipped from review due to trivial changes (6)
- packages/lib/react-query/src/guild/index.ts
- apps/web/src/app/[locale]/guild/@modal/default.tsx
- apps/web/src/app/[locale]/guild/[id]/@modal/default.tsx
- apps/web/src/app/[locale]/shop/AuctionSection/ProductTable.tsx
- apps/web/src/app/[locale]/shop/SellListSection/index.tsx
- packages/api/src/guild/index.ts
🧰 Additional context used
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/guild/[id]/setting/page.tsx
[error] 22-23: There is a suspicious semicolon in the JSX element.
This is usually the result of a typo or some refactor gone wrong.
Remove the semicolon, or move it inside a JSX element.
(lint/suspicious/noSuspiciousSemicolonInJsx)
apps/web/src/app/[locale]/guild/detail/[id]/join/page.tsx
[error] 36-37: There is a suspicious semicolon in the JSX element.
This is usually the result of a typo or some refactor gone wrong.
Remove the semicolon, or move it inside a JSX element.
(lint/suspicious/noSuspiciousSemicolonInJsx)
🔇 Additional comments (31)
packages/api/src/auction/getMyProducts.ts (1)
2-3
: 스키마 임포트 경로 변경이 일관되게 적용되었습니다.
PaginationSchema
를 상위 디렉토리에서 임포트하도록 변경한 것이 적절해 보입니다. 다른 관련 파일들과도 일관성이 유지되었습니다.packages/api/src/auction/getProducts.ts (1)
3-3
: 스키마 임포트가 잘 구성되었습니다.로컬 스키마와 공유 스키마의 임포트를 명확하게 분리한 것이 좋습니다. 코드의 구조가 더 명확해졌습니다.
Also applies to: 5-5
packages/api/src/auction/getProductHistories.ts (1)
2-3
: 스키마 구조 개선이 일관되게 적용되었습니다.
PaginationSchema
의 임포트 경로 변경이 다른 파일들과 동일한 패턴으로 적용되었습니다. 전체적인 코드 구조가 개선되었습니다.apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergePersona.tsx (1)
176-176
: 메모이제이션이 잘 구현되어 있습니다!LevelBanner 컴포넌트의 상태 처리가 일관성 있게 변경되었고, MemoizedPersonaItem의 비교 로직도 적절하게 구현되어 있습니다. 불필요한 리렌더링을 효과적으로 방지할 수 있을 것 같습니다.
apps/web/src/app/[locale]/shop/AuctionSection/HistoryTable.tsx (1)
6-6
: 임포트 경로 변경이 잘 되었습니다!컴포넌트의 정확한 위치를 지정하는 방식으로 변경되어 코드의 구조가 더 명확해졌습니다.
apps/web/src/app/layout.tsx (1)
45-46
: 렌더링 전용 인터셉터의 필요성 검토 필요일반 인터셉터와 동일한 핸들러를 사용하는 렌더링 전용 인터셉터가 추가되었습니다. 이로 인해 코드 중복이 발생하고 있습니다.
다음 사항들을 확인해 주시기 바랍니다:
- 렌더링 전용 인터셉터가 별도로 필요한 이유가 있나요?
- 일반 인터셉터와 다른 동작이 필요하다면, 각각에 맞는 별도의 핸들러를 구현하는 것이 좋지 않을까요?
- 만약 동일한 동작이 필요하다면, 하나의 인터셉터로 통합하는 것이 유지보수에 더 유리하지 않을까요?
다음 스크립트로 인터셉터 사용 패턴을 확인해 보겠습니다:
apps/web/src/serverActions/guild.ts (1)
1-7
: 코드가 깔끔하고 잘 구성되어 있습니다!'use server' 지시문과 import 문이 적절하게 배치되어 있으며, 타입과 함수 import가 명확하게 구분되어 있습니다.
apps/web/src/components/Global/SessionLoader.tsx (1)
23-24
: 인터셉터 구현의 중복 및 문서화 필요일반 인터셉터와 렌더 인터셉터가 동일한 핸들러를 사용하고 있습니다. 이는 잠재적인 코드 중복을 야기할 수 있습니다.
제안사항:
- 두 종류의 인터셉터가 필요한 이유에 대한 주석 추가
- 공통 로직을 별도의 유틸리티 함수로 추출 고려
다음 스크립트로 인터셉터 구현의 중복을 확인해보겠습니다:
packages/api/src/_instance/render.ts (1)
13-25
: 타입 안전성이 잘 구현되었습니다인터셉터 함수들의 타입 정의가 명확하고 안전하게 구현되어 있습니다.
AxiosError
,AxiosResponse
,InternalAxiosRequestConfig
타입을 적절히 활용한 점이 좋습니다.packages/lib/react-query/src/index.ts (1)
8-8
: 내보내기 구문이 일관성 있게 추가되었습니다!기존 패턴을 따르는 적절한 구현입니다.
apps/web/src/app/[locale]/guild/detail/[id]/page.tsx (1)
3-5
: 깔끔한 구현입니다!Next.js의 페이지 라우팅 컨벤션을 잘 따르고 있으며, 타입 정의도 명확합니다.
apps/web/src/components/GNB/GNB.tsx (1)
7-7
: 반응형 구현이 개선되었습니다!MediaQuery 컴포넌트를 사용하여 조건부 렌더링을 구현한 것이 좋은 접근 방식입니다. 이는 다음과 같은 이점이 있습니다:
- 불필요한 렌더링 방지
- 더 깔끔한 코드 구조
- 유지보수성 향상
packages/api/src/schema.ts (2)
7-13
: 페이지네이션 스키마가 잘 구현되었습니다!필수 필드들이 모두 포함되어 있고,
nextPage
와prevPage
의 nullable 처리가 적절합니다.
15-15
: 타입 추론 방식이 적절합니다.
z.infer
를 사용한 타입 추론이 타입 안정성을 보장하는 좋은 방식입니다.packages/api/src/index.ts (1)
3-3
: 모듈 내보내기가 일관성 있게 구현되었습니다.기존 패턴을 따라 schema와 guild 모듈을 적절히 내보내고 있습니다.
Also applies to: 12-12
packages/api/src/guild/getGuildIcons.ts (1)
13-17
: API 응답 처리가 깔끔합니다.
safeRenderGet
을 사용한 안전한 API 호출과 응답 처리가 잘 구현되어 있습니다.packages/api/src/guild/getAllJoinGuilds.ts (2)
1-2
: 문서화가 잘 되어있습니다.한글로 API의 기능을 명확하게 설명하고 있어 좋습니다.
7-15
: 스키마 재사용과 구현이 깔끔합니다.외부
GuildSchema
를 재사용하여 중복을 피하고, 응답 타입과 API 호출이 깔끔하게 구현되어 있습니다.packages/api/src/guild/getGuildBackgrounds.ts (1)
4-9
: 스키마 정의가 명확합니다.
BackgroundSchema
와GetGuildBackgroundsResponseSchema
의 정의가 명확하고 일관성 있게 구현되어 있습니다.apps/web/src/hooks/useGetNewUrl.tsx (1)
8-18
: URL 파라미터 관리를 위한 훌륭한 커스텀 훅 구현입니다!코드가 깔끔하고 재사용성이 높으며, React hooks의 모범 사례를 잘 따르고 있습니다.
apps/web/src/app/[locale]/guild/(components)/GuidlInfoForm.tsx (2)
13-24
: 주석 처리된 코드의 구현이 필요합니다.주석으로 처리된 코드에는 다음과 같은 중요한 기능들이 포함되어 있습니다:
- 폼 상태 관리 (useFormState)
- GuildInfoFormClient 렌더링
- 상태 메시지 표시
- 제출 버튼
이러한 기능들의 구현이 완료되어야 합니다.
3-13
:⚠️ Potential issue구현이 완료되지 않은 컴포넌트입니다.
현재 빈 프래그먼트만 반환하고 있습니다. 주석 처리된 코드를 보면 form 관련 기능이 계획되어 있는 것으로 보입니다.
다음 사항들을 확인해주세요:
- 폼 상태 관리 구현
- 폼 제출 핸들러 구현
- GuildInfoFormClient 컴포넌트 연동
apps/web/src/app/[locale]/guild/[id]/setting/page.tsx (1)
8-17
: 🛠️ Refactor suggestionAPI 호출에 대한 오류 처리가 필요합니다.
여러 API 호출이 연속적으로 실행되고 있지만, 오류 처리가 되어있지 않습니다. 사용자 경험 향상을 위해 오류 처리를 추가하는 것이 좋겠습니다.
export default async function GuildSettingPage({ params }: { params: { id: string } }) { + try { const isLeader = await checkIsLeader(params.id); const icons = await getGuildIcons(); const backgrounds = await getGuildBackgrounds(); const data = await getGuildById({ guildId: params.id }); if (!isLeader) { redirect(`/guild/${params.id}`); } + } catch (error) { + console.error('길드 설정 페이지 로딩 중 오류 발생:', error); + redirect(`/guild/${params.id}`); + }✅ Verification successful
API 호출에 대한 오류 처리 추가가 필요합니다
코드베이스의 다른 부분들과의 일관성을 위해 try-catch 블록을 추가하는 것이 좋습니다. 오류 발생 시 사용자를 길드 페이지로 리다이렉트하여 graceful하게 처리하는 것을 제안드립니다.
export default async function GuildSettingPage({ params }: { params: { id: string } }) { + try { const isLeader = await checkIsLeader(params.id); const icons = await getGuildIcons(); const backgrounds = await getGuildBackgrounds(); const data = await getGuildById({ guildId: params.id }); if (!isLeader) { redirect(`/guild/${params.id}`); } + } catch (error) { + console.error('길드 설정 페이지 로딩 중 오류 발생:', error); + redirect(`/guild/${params.id}`); + }🏁 Scripts executed
The following scripts were executed for the analysis:
Error: No shell scripts found
Script:
#!/bin/bash # Check implementation of API functions ast-grep --pattern 'export $_ function checkIsLeader($_) { $$$ }' ast-grep --pattern 'export $_ function getGuildIcons($_) { $$$ }' ast-grep --pattern 'export $_ function getGuildBackgrounds($_) { $$$ }' ast-grep --pattern 'export $_ function getGuildById($_) { $$$ }' # Look for error handling patterns in similar components ast-grep --pattern 'try { $$$ } catch (error) { $$$ }' # Check for error boundary components fd -e tsx -e ts ErrorBoundaryLength of output: 20822
apps/web/src/app/[locale]/error.tsx (1)
37-37
: 로그아웃 로직 제거에 대한 검토 필요에러 발생 시 사용자 세션을 초기화하는 로직이 제거되었습니다. 이로 인해 인증 관련 에러가 발생했을 때 사용자가 수동으로 로그아웃해야 할 수 있습니다.
apps/web/src/components/Gitanimals.tsx (1)
18-18
: URL 변경이 일관되게 적용되었습니다!모든 컴포넌트에서 URL이 통일성 있게 변경되었습니다.
Also applies to: 37-37, 61-61, 80-80
apps/web/src/app/[locale]/guild/(components)/GuildDetail.tsx (1)
66-68
: 미완성된 컴포넌트 처리 필요"farm type" 텍스트가 있는 박스는 미완성으로 보입니다. 이를 TODO 주석으로 표시하거나 완성된 구현이 필요합니다.
apps/web/src/app/[locale]/guild/(components)/GuildCard.tsx (1)
61-69
: 주석 처리된 코드 정리 필요
GuildJoinPetSelectDialog
관련 코드가 주석 처리되어 있습니다. 이 기능의 구현 계획이 있다면 TODO 주석으로 표시하고, 그렇지 않다면 제거하는 것이 좋습니다.apps/web/src/app/[locale]/guild/page.tsx (1)
1-25
: 코드가 깔끔하고 잘 구성되어 있습니다!필요한 모든 의존성이 잘 임포트되어 있으며, 검색 매개변수에 대한 타입이 명확하게 정의되어 있습니다.
apps/web/package.json (1)
29-29
: 의존성 추가가 적절합니다.Radix UI의 드롭다운 메뉴 컴포넌트는 접근성이 뛰어나고 안정적인 선택입니다.
apps/web/messages/ko_KR.json (1)
11-12
: 번역이 자연스럽습니다.한글 번역이 간단명료하게 잘 되어있습니다.
apps/web/messages/en_US.json (1)
6-6
: 번역이 일관성 있게 잘 되어있습니다.영문 번역이 다른 메뉴 아이템들과 일관된 대문자 스타일을 유지하고 있습니다.
apps/web/src/app/layout.tsx
Outdated
@@ -1,5 +1,6 @@ | |||
import type { Metadata } from 'next'; | |||
import { setRequestInterceptor, setResponseInterceptor } from '@gitanimals/api'; | |||
import { setRenderRequestInterceptor, setRenderResponseInterceptor } from '@gitanimals/api/src/_instance'; |
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
내부 구현 세부사항 import 개선 필요
'_instance'와 같은 내부 구현 세부사항을 직접 import하는 것은 권장되지 않습니다. 대신 메인 패키지 진입점인 '@gitanimals/api'에서 import하는 것이 좋습니다.
다음과 같이 수정을 제안합니다:
-import { setRenderRequestInterceptor, setRenderResponseInterceptor } from '@gitanimals/api/src/_instance';
+import { setRenderRequestInterceptor, setRenderResponseInterceptor } from '@gitanimals/api';
📝 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 { setRenderRequestInterceptor, setRenderResponseInterceptor } from '@gitanimals/api/src/_instance'; | |
import { setRenderRequestInterceptor, setRenderResponseInterceptor } from '@gitanimals/api'; |
apps/web/src/serverActions/guild.ts
Outdated
export async function joinGuildAction(request: JoinGuildRequest) { | ||
return joinGuild(request); | ||
} |
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
에러 처리 로직 추가가 필요합니다.
API 호출 시 발생할 수 있는 예외 상황에 대한 처리가 없습니다. try-catch 블록을 추가하여 에러를 적절히 처리하는 것이 좋겠습니다.
export async function joinGuildAction(request: JoinGuildRequest) {
- return joinGuild(request);
+ try {
+ return await joinGuild(request);
+ } catch (error) {
+ console.error('길드 참가 중 오류 발생:', error);
+ throw new Error('길드 참가에 실패했습니다.');
+ }
}
📝 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 async function joinGuildAction(request: JoinGuildRequest) { | |
return joinGuild(request); | |
} | |
export async function joinGuildAction(request: JoinGuildRequest) { | |
try { | |
return await joinGuild(request); | |
} catch (error) { | |
console.error('길드 참가 중 오류 발생:', error); | |
throw new Error('길드 참가에 실패했습니다.'); | |
} | |
} |
apps/web/src/serverActions/guild.ts
Outdated
// NOTE: 작업 중 | ||
export const createGuildAction = async (prevState: FormState, formData: FormData) => { | ||
const name = formData.get('name') as string; | ||
const description = formData.get('description') as string; | ||
const icon = formData.get('icon') as string; | ||
const background = formData.get('background') as string; | ||
|
||
if (!name || !description || !icon || !background) { | ||
return { message: 'Please fill in all fields', success: false }; | ||
} | ||
|
||
await new Promise((resolve) => setTimeout(resolve, 3000)); | ||
console.log('form end'); | ||
return { message: 'Guild created', success: true }; | ||
}; |
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.
작업 중인 코드의 완성이 필요합니다.
다음 사항들을 개선해주시기 바랍니다:
- API 연동이 구현되어 있지 않습니다
- 테스트용 setTimeout은 제거가 필요합니다
- console.log는 제거가 필요합니다
또한 updateGuildAction과 유사한 에러 처리 패턴을 적용하는 것이 좋겠습니다:
export const createGuildAction = async (prevState: FormState, formData: FormData) => {
const name = formData.get('name') as string;
const description = formData.get('description') as string;
const icon = formData.get('icon') as string;
const background = formData.get('background') as string;
if (!name || !description || !icon || !background) {
return { message: 'Please fill in all fields', success: false };
}
- await new Promise((resolve) => setTimeout(resolve, 3000));
- console.log('form end');
- return { message: 'Guild created', success: true };
+ try {
+ await createGuild({
+ title: name,
+ body: description,
+ guildIcon: icon,
+ farmType: background,
+ autoJoin: true,
+ });
+ return { message: '길드가 생성되었습니다', success: true };
+ } catch (error) {
+ console.error('길드 생성 중 오류 발생:', error);
+ return { message: '길드 생성에 실패했습니다', success: false };
+ }
};
Committable suggestion skipped: line range outside the PR's diff.
setRenderRequestInterceptor(interceptorRequestFulfilled); | ||
setRenderResponseInterceptor(interceptorResponseFulfilled, interceptorResponseRejected); |
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
인터셉터 설정의 중앙화 필요
SessionLoader
와 동일한 인터셉터 설정 코드가 중복되어 있습니다. 이는 유지보수성을 저하시킬 수 있습니다.
제안사항:
- 인터셉터 설정을 중앙화된 위치로 이동
- 공통 설정 함수 생성 고려
예시 구현:
// utils/interceptors.ts
export const setupAllInterceptors = () => {
setRequestInterceptor(interceptorRequestFulfilled);
setResponseInterceptor(interceptorResponseFulfilled, interceptorResponseRejected);
setRenderRequestInterceptor(interceptorRequestFulfilled);
setRenderResponseInterceptor(interceptorResponseFulfilled, interceptorResponseRejected);
};
packages/api/src/_instance/render.ts
Outdated
@@ -1,4 +1,4 @@ | |||
import axios from 'axios'; | |||
import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; | |||
import { setInterceptors } from '../_interceptor'; | |||
|
|||
const API_URL = 'https://render.gitanimals.org'; |
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
API URL 환경 설정 개선 필요
API URL이 하드코딩되어 있습니다. 이는 환경별 배포에 제약이 될 수 있습니다.
제안사항:
환경 변수를 사용하여 API URL을 설정하도록 수정해주세요.
-const API_URL = 'https://render.gitanimals.org';
+const API_URL = process.env.RENDER_API_URL || 'https://render.gitanimals.org';
📝 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 API_URL = 'https://render.gitanimals.org'; | |
const API_URL = process.env.RENDER_API_URL || 'https://render.gitanimals.org'; |
<img src={data.guildIcon} width={40} height={40} alt={data.title} /> | ||
<h2>{data.title}</h2> |
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
접근성 개선 필요
이미지 태그에 의미 있는 대체 텍스트를 제공해야 합니다. 현재는 title만 사용되고 있습니다.
-<img src={data.guildIcon} width={40} height={40} alt={data.title} />
+<img
+ src={data.guildIcon}
+ width={40}
+ height={40}
+ alt={`${data.title} 길드 아이콘`}
+/>
📝 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.
<img src={data.guildIcon} width={40} height={40} alt={data.title} /> | |
<h2>{data.title}</h2> | |
<img | |
src={data.guildIcon} | |
width={40} | |
height={40} | |
alt={`${data.title} 길드 아이콘`} | |
/> | |
<h2>{data.title}</h2> |
const submitJoinGuild = async (selectPersona: string) => { | ||
try { | ||
await joinGuildAction({ | ||
guildId: guild.id, | ||
personaId: selectPersona, | ||
}); | ||
|
||
queryClient.invalidateQueries({ queryKey: inboxQueries.allKey() }); | ||
} catch (error) { | ||
console.error(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.
에러 처리 개선 필요
현재 에러를 단순히 콘솔에 로깅하는 것은 충분하지 않습니다. 사용자에게 적절한 에러 메시지를 표시해야 합니다.
const submitJoinGuild = async (selectPersona: string) => {
try {
await joinGuildAction({
guildId: guild.id,
personaId: selectPersona,
});
queryClient.invalidateQueries({ queryKey: inboxQueries.allKey() });
} catch (error) {
- console.error(error);
+ // TODO: 에러 처리 컴포넌트나 토스트 메시지 추가
+ throw new Error('길드 가입 중 오류가 발생했습니다. 다시 시도해 주세요.');
}
};
Committable suggestion skipped: line range outside the PR's diff.
export interface FormState { | ||
message: string; | ||
} |
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
FormState 인터페이스 개선 필요
FormState
인터페이스가 현재 message 필드만 포함하고 있습니다. 폼의 모든 필드를 포함하도록 확장해야 합니다.
export interface FormState {
- message: string;
+ name: string;
+ description: string;
+ icon: string;
+ background: string;
}
📝 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 interface FormState { | |
message: string; | |
} | |
export interface FormState { | |
name: string; | |
description: string; | |
icon: string; | |
background: string; | |
} |
<TextField | ||
name="name" | ||
placeholder="Type guild name" | ||
className={css({ mb: '6px' })} | ||
defaultValue={props.initialData?.title} | ||
/> | ||
<TextArea placeholder="Type guild description" name="description" defaultValue={props.initialData?.body} /> | ||
</div> |
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
폼 유효성 검사 추가 필요
길드 이름과 설명에 대한 유효성 검사가 없습니다. 최소/최대 길이, 필수 입력 여부 등의 검증이 필요합니다.
<TextField
name="name"
placeholder="Type guild name"
className={css({ mb: '6px' })}
defaultValue={props.initialData?.title}
+ required
+ minLength={2}
+ maxLength={50}
/>
<TextArea
placeholder="Type guild description"
name="description"
defaultValue={props.initialData?.body}
+ required
+ maxLength={500}
/>
📝 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.
<TextField | |
name="name" | |
placeholder="Type guild name" | |
className={css({ mb: '6px' })} | |
defaultValue={props.initialData?.title} | |
/> | |
<TextArea placeholder="Type guild description" name="description" defaultValue={props.initialData?.body} /> | |
</div> | |
<TextField | |
name="name" | |
placeholder="Type guild name" | |
className={css({ mb: '6px' })} | |
defaultValue={props.initialData?.title} | |
required | |
minLength={2} | |
maxLength={50} | |
/> | |
<TextArea | |
placeholder="Type guild description" | |
name="description" | |
defaultValue={props.initialData?.body} | |
required | |
maxLength={500} | |
/> | |
</div> |
<p className={headingStyle}>Guild thumbnail</p> | ||
<SelectableFormItem name="icon" defaultValue={props.initialData?.guildIcon}> | ||
<Flex gap="6px"> | ||
{props.icons.map((icon) => ( | ||
<SelectableFormItemOption value={icon} key={icon}> | ||
{({ isSelected }) => ( | ||
<img | ||
src={icon} | ||
className={cx( | ||
itemStyle, | ||
isSelected && css({ border: '1.5px solid', borderColor: 'white.white_90' }), | ||
)} | ||
width={70} | ||
height={70} | ||
alt={icon} | ||
/> | ||
)} | ||
</SelectableFormItemOption> | ||
))} | ||
</Flex> | ||
</SelectableFormItem> | ||
</div> |
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에 적절한 레이블과 설명이 없습니다. 스크린 리더 사용자를 위한 접근성 개선이 필요합니다.
-<p className={headingStyle}>Guild thumbnail</p>
+<p className={headingStyle} id="thumbnail-label">길드 썸네일 선택</p>
<SelectableFormItem
name="icon"
defaultValue={props.initialData?.guildIcon}
+ aria-labelledby="thumbnail-label"
+ role="radiogroup"
>
📝 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.
<p className={headingStyle}>Guild thumbnail</p> | |
<SelectableFormItem name="icon" defaultValue={props.initialData?.guildIcon}> | |
<Flex gap="6px"> | |
{props.icons.map((icon) => ( | |
<SelectableFormItemOption value={icon} key={icon}> | |
{({ isSelected }) => ( | |
<img | |
src={icon} | |
className={cx( | |
itemStyle, | |
isSelected && css({ border: '1.5px solid', borderColor: 'white.white_90' }), | |
)} | |
width={70} | |
height={70} | |
alt={icon} | |
/> | |
)} | |
</SelectableFormItemOption> | |
))} | |
</Flex> | |
</SelectableFormItem> | |
</div> | |
<p className={headingStyle} id="thumbnail-label">길드 썸네일 선택</p> | |
<SelectableFormItem | |
name="icon" | |
defaultValue={props.initialData?.guildIcon} | |
aria-labelledby="thumbnail-label" | |
role="radiogroup" | |
> | |
<Flex gap="6px"> | |
{props.icons.map((icon) => ( | |
<SelectableFormItemOption value={icon} key={icon}> | |
{({ isSelected }) => ( | |
<img | |
src={icon} | |
className={cx( | |
itemStyle, | |
isSelected && css({ border: '1.5px solid', borderColor: 'white.white_90' }), | |
)} | |
width={70} | |
height={70} | |
alt={icon} | |
/> | |
)} | |
</SelectableFormItemOption> | |
))} | |
</Flex> | |
</SelectableFormItem> | |
</div> |
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.
Caution
Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.
Actionable comments posted: 24
🧹 Nitpick comments (16)
apps/web/src/app/[locale]/error.tsx (1)
Line range hint
1-57
: 전반적인 에러 처리 구조가 잘 설계되어 있습니다다음과 같은 긍정적인 부분들이 있습니다:
- 개발 환경과 프로덕션 환경의 구분
- 알려진 에러 메시지에 대한 별도 처리
- Slack을 통한 에러 모니터링
- 사용자 친화적인 에러 메시지와 복구 옵션 제공
한 가지 제안사항으로는, 알려진 에러 메시지(
KNOWN_ERROR_MESSAGES
)를 상수 파일로 분리하여 관리하면 더 좋을 것 같습니다.-const KNOWN_ERROR_MESSAGES = [NOT_AUTHORIZED_MESSAGE];
이 부분을 별도의 상수 파일로 이동하는 것을 고려해보시겠습니까?
apps/web/src/app/[locale]/guild/[id]/setting/page.tsx (2)
9-9
: 타입 정의를 개선하세요props 타입을 인라인으로 정의하는 대신 별도의 인터페이스로 분리하면 코드의 재사용성과 유지보수성이 향상됩니다.
+interface GuildSettingPageProps { + params: { + id: string; + }; +} + -export default async function GuildSettingPage({ params }: { params: { id: string } }) { +export default async function GuildSettingPage({ params }: GuildSettingPageProps) {
16-18
: 권한 체크 중 로딩 상태 표시가 필요합니다사용자 경험 향상을 위해 권한 체크 중임을 나타내는 로딩 상태를 추가하는 것이 좋습니다.
권한 체크 중 로딩 컴포넌트를 표시하도록 수정하시겠습니까? 필요하시다면 구현을 도와드릴 수 있습니다.
apps/web/src/components/Trigger/BackTrigger.tsx (1)
11-14
: 접근성 및 타입 안전성 개선이 필요합니다.다음 사항들을 개선하면 좋을 것 같습니다:
- 버튼에 적절한 aria-label 추가
- children prop이 없을 경우의 기본 텍스트 설정
- onClick 이벤트 핸들러 분리
-export function BackTrigger(props: ComponentProps<'button'>) { +type BackTriggerProps = ComponentProps<'button'> & { + ariaLabel?: string; +}; + +export function BackTrigger({ + children = '뒤로 가기', + ariaLabel = '이전 페이지로 이동', + ...props +}: BackTriggerProps) { const router = useRouter(); + + const handleClick = () => router.back(); - return <button onClick={() => router.back()} {...props} />; + return ( + <button + onClick={handleClick} + aria-label={ariaLabel} + {...props} + > + {children} + </button> + ); }apps/web/src/app/[locale]/guild/_components/GuildModalPageLayout.tsx (1)
35-37
: 타이틀 컴포넌트의 유연성 개선이 필요합니다.타이틀 컴포넌트에 대한 개선사항입니다:
- 헤딩 레벨 커스터마이징 지원
- 스타일 확장성 개선
-export const GuildModalPageTitle = ({ children, className }: PropsWithChildren<{ className?: string }>) => ( +type GuildModalPageTitleProps = PropsWithChildren<{ + className?: string; + as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; +}>; + +export const GuildModalPageTitle = ({ + children, + className, + as: Heading = 'h2' +}: GuildModalPageTitleProps) => ( - <h2 className={cx(dialogTitleStyle, className)}>{children}</h2> + <Heading className={cx(dialogTitleStyle, className)}>{children}</Heading> );apps/web/src/app/[locale]/guild/_components/GuildDetail.tsx (2)
1-15
: 이미지 처리 방식 개선 필요Next.js의 Image 컴포넌트를 사용하지 않는 이유가 명확하지 않습니다. Next/Image를 사용하면 다음과 같은 이점이 있습니다:
- 자동 이미지 최적화
- 레이아웃 시프트 방지
- 더 나은 로딩 성능
- /* eslint-disable @next/next/no-img-element */ - import { UsersRoundIcon } from 'lucide-react'; + import Image from 'next/image'; + import { UsersRoundIcon } from 'lucide-react';
22-30
: 접근성 개선 필요헤더 섹션의 접근성을 개선할 수 있습니다:
- 시맨틱 HTML 사용
- ARIA 레이블 추가
- 이미지 로드 실패 처리
- <div> + <header role="banner"> - <div className={titleStyle}> + <div className={titleStyle} aria-label="길드 정보"> <img src={data.guildIcon} width={40} height={40} alt={data.title} + onError={(e) => { + e.currentTarget.src = '/default-guild-icon.png'; + }} /> - <h2>{data.title}</h2> + <h2 aria-label="길드 이름">{data.title}</h2> </div> - <div className={bodyStyle}>{data.body}</div> + <div className={bodyStyle} aria-label="길드 설명">{data.body}</div> - </div> + </header>apps/web/src/app/[locale]/guild/_components/GuildPetSelectDialog.tsx (2)
10-21
: 콜백 처리 개선이 필요합니다.현재 구현에서 다음과 같은 개선사항을 제안드립니다:
onSubmit
콜백 실행 시 발생할 수 있는 에러 처리가 필요합니다- 제출 중 로딩 상태 관리가 필요합니다
다음과 같이 개선해보세요:
export const GuildJoinPetSelectDialog = ({ onSubmit }: { onSubmit: (selectPersona: string) => void }) => { const [selectPersona, setSelectPersona] = useState<string>(); + const [isSubmitting, setIsSubmitting] = useState(false); const onSelectPersona = (currentSelectPersona: Persona) => { setSelectPersona(currentSelectPersona.id); }; - const onDone = () => { + const onDone = async () => { if (selectPersona) { - onSubmit(selectPersona); + try { + setIsSubmitting(true); + await onSubmit(selectPersona); + } catch (error) { + console.error('Failed to submit selected persona:', error); + // TODO: 에러 처리 UI 추가 + } finally { + setIsSubmitting(false); + } } };
23-30
: UI 및 접근성 개선이 필요합니다.다음과 같은 개선사항을 제안드립니다:
- 버튼의 비활성화 상태에 로딩 상태도 반영해야 합니다
- 접근성 향상을 위한
aria-
속성 추가가 필요합니다return ( <> <SelectPersonaList selectPersona={selectPersona ? [selectPersona] : []} onSelectPersona={onSelectPersona} /> - <Button mx="auto" w="100px" onClick={onDone} disabled={!selectPersona}> - Done + <Button + mx="auto" + w="100px" + onClick={onDone} + disabled={!selectPersona || isSubmitting} + aria-busy={isSubmitting} + > + {isSubmitting ? 'Submitting...' : 'Done'} </Button> </> );apps/web/src/app/[locale]/guild/_components/GuildSearch.tsx (1)
31-43
: 접근성 및 사용자 경험 개선이 필요합니다.리셋 버튼과 검색바에 대한 개선사항입니다:
- 리셋 버튼에 접근성 레이블 추가
- 검색 중 로딩 상태 표시
- 검색바 비활성화 상태 관리
return ( <> {text && ( - <button onClick={onSearchReset}> + <button + onClick={onSearchReset} + aria-label="검색 초기화" + disabled={isSearching} + > <ChevronLeftIcon size="28px" color="#FFFFFF80" /> </button> )} <Box flex="1"> - <SearchBar defaultValue={text} onChange={(e) => setInput(e.target.value)} onSubmit={onSubmit} /> + <SearchBar + value={input} + onChange={(e) => setInput(e.target.value)} + onSubmit={onSubmit} + disabled={isSearching} + placeholder={isSearching ? "검색 중..." : "길드 검색"} + /> </Box> </> );apps/web/src/app/[locale]/guild/_components/SortSelect.tsx (1)
10-16
: 상수 분리 및 타입 안정성 개선이 필요합니다.정렬 옵션 배열을 다음과 같이 개선하면 좋을 것 같습니다:
- 상수를 별도 파일로 분리
- 타입 안정성 강화
src/constants/guild.ts
파일을 생성하고 다음과 같이 구현해보세요:import type { FilterType } from '@gitanimals/api'; export interface SortOption { label: string; value: FilterType; } export const GUILD_SORT_OPTIONS: readonly SortOption[] = [ { label: 'Random', value: 'RANDOM' }, { label: 'People (asc)', value: 'PEOPLE_ASC' }, { label: 'People (desc)', value: 'PEOPLE_DESC' }, { label: 'Contribution (asc)', value: 'CONTRIBUTION_ASC' }, { label: 'Contribution (desc)', value: 'CONTRIBUTION_DESC' }, ] as const;apps/web/src/app/[locale]/guild/_components/SelecableFormItem.tsx (1)
50-54
: 이벤트 핸들러 타입 정의가 필요합니다.
SelectableFormItemOptionProps
인터페이스에 이벤트 핸들러 관련 타입이 누락되어 있습니다.다음과 같이 수정하는 것을 제안합니다:
interface SelectableFormItemOptionProps { className?: string; children: React.ReactNode | ((props: { isSelected: boolean }) => React.ReactNode); value: string; + onSelect?: (value: string) => void; + onFocus?: React.FocusEventHandler<HTMLButtonElement>; + onBlur?: React.FocusEventHandler<HTMLButtonElement>; }apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx (2)
51-51
: 정렬 기능 구현이 필요합니다.TODO 주석으로 표시된 정렬 기능이 구현되지 않았습니다. 페르소나 목록을 레벨이나 이름순으로 정렬하는 기능을 추가하는 것이 좋습니다.
정렬 기능 구현을 도와드릴까요? GitHub 이슈를 생성하여 추적할 수 있습니다.
96-98
: 메모이제이션 최적화가 필요합니다.현재 메모이제이션 조건이 제한적입니다. 페르소나의 다른 속성이 변경될 때도 불필요한 리렌더링이 발생할 수 있습니다.
const MemoizedPersonaItem = memo(PersonaItem, (prev, next) => { - return prev.isSelected === next.isSelected && prev.persona.level === next.persona.level; + return ( + prev.isSelected === next.isSelected && + prev.persona.level === next.persona.level && + prev.persona.id === next.persona.id && + prev.persona.type === next.persona.type + ); });apps/web/src/app/[locale]/guild/_components/GuildCreateForm.tsx (1)
12-15
: 타입 안정성 개선이 필요합니다
formData
의 키 값들이 'title', 'body', 'icon', 'background' 등으로 한정되어 있으므로, Record 타입 대신 구체적인 타입을 사용하는 것이 좋습니다.export interface GuildCreateFormProps { - formData: Record<string, string>; + formData: { + title?: string; + body?: string; + icon?: string; + background?: string; + }; onDataChange: (key: string, value: string) => void; }apps/web/src/app/[locale]/guild/page.tsx (1)
99-108
: 반응형 디자인 개선이 필요합니다카드 리스트의 반응형 처리가 모바일에서 최적화되어 있지 않습니다.
- 모바일에서 단일 컬럼으로 변경하는 대신 그리드 시스템 활용
- 하드코딩된 크기값들을 테마 시스템으로 관리
const cardListStyle = grid({ gridTemplateRows: 'repeat(3, 210px)', - columns: 3, + gridTemplateColumns: { + base: '1fr', + sm: 'repeat(2, 1fr)', + md: 'repeat(3, 1fr)', + }, gap: '8px', w: 'full', - _mobile: { - columns: 1, - }, });
🛑 Comments failed to post (24)
apps/web/src/app/[locale]/guild/[id]/setting/page.tsx (2)
22-22: 🛠️ Refactor suggestion
제목 텍스트에 국제화(i18n)를 적용해주세요
하드코딩된 영문 텍스트 "Guild Setting"을 국제화하여 다국어 지원이 가능하도록 수정해주세요.
- <GuildModalPageTitle>Guild Setting</GuildModalPageTitle> + <GuildModalPageTitle>{t('guild.setting')}</GuildModalPageTitle>Committable suggestion skipped: line range outside the PR's diff.
10-14: 🛠️ Refactor suggestion
데이터 페칭 최적화 및 에러 처리가 필요합니다
Promise.all
을 사용하여 병렬로 데이터를 페칭하면 성능이 향상됩니다.- API 호출 실패에 대한 에러 처리가 없습니다.
- const isLeader = await checkIsLeader(params.id); - const icons = await getGuildIcons(); - const backgrounds = await getGuildBackgrounds(); - const data = await getGuildById({ guildId: params.id }); + try { + const [isLeader, icons, backgrounds, data] = await Promise.all([ + checkIsLeader(params.id), + getGuildIcons(), + getGuildBackgrounds(), + getGuildById({ guildId: params.id }) + ]); + } catch (error) { + // TODO: 에러 처리 로직 추가 + console.error('길드 설정 데이터 로딩 실패:', error); + throw error; + }Committable suggestion skipped: line range outside the PR's diff.
apps/web/src/app/[locale]/guild/_components/GuildModalPageLayout.tsx (1)
9-33: 🛠️ Refactor suggestion
시맨틱 마크업 및 접근성 개선이 필요합니다.
현재 구현에서 다음과 같은 개선사항들이 있습니다:
- 시맨틱 HTML 요소 사용 (
main
,article
등)- ARIA 속성 추가
- 스타일 속성의 재사용성 개선
+const modalLayoutStyles = { + wrapper: css({ + minH: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }), + container: css({ + maxWidth: '880px', + mx: 'auto', + background: 'gray.gray_150', + p: '40px', + borderRadius: '16px', + color: 'white.white', + w: '100%', + position: 'relative', + }) +}; -export function GuildModalPageLayout({ children }: PropsWithChildren) { +type GuildModalPageLayoutProps = PropsWithChildren<{ + ariaLabel?: string; +}>; + +export function GuildModalPageLayout({ + children, + ariaLabel = '길드 설정', +}: GuildModalPageLayoutProps) { return ( - <div className={css({ minH: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' })}> - <div - className={css({ - maxWidth: '880px', - mx: 'auto', - background: 'gray.gray_150', - p: '40px', - borderRadius: '16px', - color: 'white.white', - w: '100%', - position: 'relative', - })} + <main className={modalLayoutStyles.wrapper}> + <article + role="dialog" + aria-label={ariaLabel} + className={modalLayoutStyles.container} > <BackTrigger className={css({ position: 'absolute', top: '16px', right: '16px' })}> <XIcon /> </BackTrigger> <Flex flexDirection="column" gap="24px"> {children} </Flex> - </div> - </div> + </article> + </main> ); }📝 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 modalLayoutStyles = { wrapper: css({ minH: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }), container: css({ maxWidth: '880px', mx: 'auto', background: 'gray.gray_150', p: '40px', borderRadius: '16px', color: 'white.white', w: '100%', position: 'relative', }) }; type GuildModalPageLayoutProps = PropsWithChildren<{ ariaLabel?: string; }>; export function GuildModalPageLayout({ children, ariaLabel = '길드 설정', }: GuildModalPageLayoutProps) { return ( <main className={modalLayoutStyles.wrapper}> <article role="dialog" aria-label={ariaLabel} className={modalLayoutStyles.container} > <BackTrigger className={css({ position: 'absolute', top: '16px', right: '16px' })}> <XIcon /> </BackTrigger> <Flex flexDirection="column" gap="24px"> {children} </Flex> </article> </main> ); }
apps/web/src/app/[locale]/guild/_components/GuildDetail.tsx (3)
31-40: 🛠️ Refactor suggestion
국제화(i18n) 적용 필요
문자열이 하드코딩되어 있어 다국어 지원이 어렵습니다. 국제화를 위해 번역 키를 사용하는 것이 좋습니다.
<div className={leaderStyle}> - <p>Leader</p> + <p>{t('guild.leader')}</p> <BannerPetSelectMedium name={data.leader.name} count={data.leader.contributions} image={getPersonaImage(data.leader.personaType)} status="gradient" + aria-label={t('guild.leader_info', { name: data.leader.name })} /> </div>Committable suggestion skipped: line range outside the PR's diff.
16-21: 🛠️ Refactor suggestion
에러 처리 및 로딩 상태 개선 필요
현재 Suspense와 ErrorBoundary의 fallback이 null로 설정되어 있어 사용자 경험이 좋지 않을 수 있습니다. 적절한 로딩 상태와 에러 메시지를 표시하는 것이 좋습니다.
export const GuildDetail = wrap - .Suspense({ fallback: null }) - .ErrorBoundary({ fallback: null }) + .Suspense({ fallback: <LoadingSpinner /> }) + .ErrorBoundary({ + fallback: ({ error }) => ( + <ErrorMessage + message="길드 정보를 불러오는 중 오류가 발생했습니다." + error={error} + /> + ) + })Committable suggestion skipped: line range outside the PR's diff.
41-64: 🛠️ Refactor suggestion
성능 최적화 및 코드 개선 필요
다음과 같은 개선이 필요합니다:
- 멤버 제한이 하드코딩되어 있습니다
- key prop이 중복 사용되고 있습니다
- 큰 리스트에 대한 성능 최적화가 필요합니다
+ const MEMBER_LIMIT = 15; <Flex mb="1" justifyContent="space-between"> - <p>Members</p> + <p>{t('guild.members')}</p> <Flex gap="6px" alignItems="center"> <UsersRoundIcon size={16} color="#FFFFFF80" /> - <span>{data.members.length}/ 15</span> + <span>{data.members.length}/ {MEMBER_LIMIT}</span> </Flex> </Flex> <Flicking moveType="freeScroll" align="prev" bound={true} + virtualSize={data.members.length} + renderOnlyVisible={true} > {data.members.map((member) => ( <div className={cx('flicking-panel', css({ height: 'fit-content', _first: { ml: 0 }, marginLeft: 1 }))} key={member.id} > <BannerPetSelectMedium - key={member.id} name={member.name} count={member.contributions} image={member.personaId} + aria-label={t('guild.member_info', { name: member.name })} /> </div> ))} </Flicking>📝 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.<div className={membersStyle}> <Flex mb="1" justifyContent="space-between"> <p>{t('guild.members')}</p> <Flex gap="6px" alignItems="center"> <UsersRoundIcon size={16} color="#FFFFFF80" /> <span>{data.members.length}/ {MEMBER_LIMIT}</span> </Flex> </Flex> <Flicking moveType="freeScroll" align="prev" bound={true} virtualSize={data.members.length} renderOnlyVisible={true} > {data.members.map((member) => ( <div className={cx('flicking-panel', css({ height: 'fit-content', _first: { ml: 0 }, marginLeft: 1 }))} key={member.id} > <BannerPetSelectMedium name={member.name} count={member.contributions} image={member.personaId} aria-label={t('guild.member_info', { name: member.name })} /> </div> ))} </Flicking> </div>
apps/web/src/app/[locale]/guild/_components/GuildSearch.tsx (1)
17-24: 🛠️ Refactor suggestion
검색 기능 최적화가 필요합니다.
현재 구현에서 다음과 같은 개선사항이 필요합니다:
- 검색 입력 시 디바운스 처리가 필요합니다
- URL과 입력 상태의 동기화가 필요합니다
- 검색 중 로딩 상태 표시가 필요합니다
+ const [isSearching, setIsSearching] = useState(false); - const [input, setInput] = useState(''); + const [input, setInput] = useState(text); + + useEffect(() => { + setInput(text); + }, [text]); + + const debouncedSearch = useCallback( + debounce(async (value: string) => { + setIsSearching(true); + const nextUrl = getNextUrl({ text: value }); + await router.replace(nextUrl); + setIsSearching(false); + }, 300), + [getNextUrl, router] + ); - const onSubmit = () => { - const nextUrl = getNextUrl({ text: input }); - router.replace(nextUrl); + const onSubmit = async () => { + await debouncedSearch(input); };Committable suggestion skipped: line range outside the PR's diff.
apps/web/src/app/[locale]/guild/_components/SortSelect.tsx (1)
18-43: 🛠️ Refactor suggestion
상태 관리 및 에러 처리 개선이 필요합니다.
다음과 같은 개선사항을 제안드립니다:
- 정렬 변경 시 로딩 상태 관리
- URL 업데이트 실패 시 에러 처리
- 컴포넌트 비활성화 상태 관리
export function SortSelect() { const router = useRouter(); const getNextUrl = useGetNextUrl(); const searchParams = useSearchParams(); const filter = (searchParams.get('filter') as FilterType) ?? 'RANDOM'; + const [isUpdating, setIsUpdating] = useState(false); - const onSelect = (value: FilterType) => { - const nextUrl = getNextUrl({ filter: value }); - router.replace(nextUrl); + const onSelect = async (value: FilterType) => { + try { + setIsUpdating(true); + const nextUrl = getNextUrl({ filter: value }); + await router.replace(nextUrl); + } catch (error) { + console.error('Failed to update sort filter:', error); + // TODO: 에러 처리 UI 추가 + } finally { + setIsUpdating(false); + } }; return ( - <CombineChip defaultValue={filter} onValueChange={(value) => onSelect(value as FilterType)}> + <CombineChip + defaultValue={filter} + onValueChange={(value) => onSelect(value as FilterType)} + disabled={isUpdating} + > <CombineChip.Trigger> - <CombineChip.Value /> + <CombineChip.Value /> + {isUpdating && <span aria-label="정렬 중...">⌛</span>} </CombineChip.Trigger>📝 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 function SortSelect() { const router = useRouter(); const getNextUrl = useGetNextUrl(); const searchParams = useSearchParams(); const filter = (searchParams.get('filter') as FilterType) ?? 'RANDOM'; const [isUpdating, setIsUpdating] = useState(false); const onSelect = async (value: FilterType) => { try { setIsUpdating(true); const nextUrl = getNextUrl({ filter: value }); await router.replace(nextUrl); } catch (error) { console.error('Failed to update sort filter:', error); // TODO: 에러 처리 UI 추가 } finally { setIsUpdating(false); } }; return ( <CombineChip defaultValue={filter} onValueChange={(value) => onSelect(value as FilterType)} disabled={isUpdating} > <CombineChip.Trigger> <CombineChip.Value /> {isUpdating && <span aria-label="정렬 중...">⌛</span>} </CombineChip.Trigger> <CombineChip.Content> {options.map((option) => ( <CombineChip.Item key={option.value} value={option.value}> {option.label} </CombineChip.Item> ))} </CombineChip.Content> </CombineChip> ); }
apps/web/src/app/[locale]/guild/_components/GuildCreate.tsx (2)
14-21: 🛠️ Refactor suggestion
폼 데이터 유효성 검사가 필요합니다.
현재 빈 문자열 체크만 수행하고 있습니다. 길드 제목 길이 제한이나 특수문자 제한 등의 추가적인 유효성 검사가 필요합니다.
다음과 같은 유효성 검사 함수를 추가하는 것을 제안합니다:
+ const validateFormData = (data: typeof formData) => { + const errors: Record<string, string> = {}; + if (data.title.length < 2 || data.title.length > 20) { + errors.title = '길드 이름은 2-20자 사이여야 합니다'; + } + if (data.body.length > 200) { + errors.body = '길드 설명은 200자를 초과할 수 없습니다'; + } + return errors; + };Committable suggestion skipped: line range outside the PR's diff.
29-41:
⚠️ Potential issue에러 처리 로직이 누락되었습니다.
에러가 발생했을 때 사용자에게 적절한 피드백을 제공하지 않고 있습니다. 에러 처리를 추가하고 로딩 상태를 표시하는 것이 좋습니다.
다음과 같이 수정하는 것을 제안합니다:
const onSubmit = async () => { + const [isLoading, setIsLoading] = useState(false); try { + setIsLoading(true); const res = await createGuild({ title: formData.title, body: formData.body, guildIcon: formData.icon, autoJoin: false, farmType: formData.background, personaId: selectPersona, }); router.push(`/guild/${res.id}`); - } catch (error) {} + } catch (error) { + console.error('길드 생성 중 오류 발생:', error); + // TODO: 사용자에게 에러 메시지 표시 + } finally { + setIsLoading(false); + } };Committable suggestion skipped: line range outside the PR's diff.
apps/web/src/app/[locale]/guild/_components/SelecableFormItem.tsx (1)
65-72: 🛠️ Refactor suggestion
접근성 개선이 필요합니다.
버튼 요소에 접근성 속성이 누락되어 있습니다. 스크린 리더 사용자를 위한 ARIA 속성을 추가해야 합니다.
다음과 같이 수정하는 것을 제안합니다:
<button type="button" className={cx('selectable-form-item-option group', props.className)} onClick={() => onChange(props.value)} data-active={selected === props.value} + role="radio" + aria-checked={selected === props.value} + aria-label={`${props.value} 선택 옵션`} > {typeof props.children === 'function' ? props.children({ isSelected: selected === props.value }) : props.children} </button>📝 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.<button type="button" className={cx('selectable-form-item-option group', props.className)} onClick={() => onChange(props.value)} data-active={selected === props.value} role="radio" aria-checked={selected === props.value} aria-label={`${props.value} 선택 옵션`} > {typeof props.children === 'function' ? props.children({ isSelected: selected === props.value }) : props.children} </button>
apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx (1)
38-40: 🛠️ Refactor suggestion
에러 메시지 현지화가 필요합니다.
ErrorBoundary의 fallback에 하드코딩된 에러 메시지가 있습니다. 다국어 지원을 위해 현지화가 필요합니다.
export const SelectPersonaList = wrap - .ErrorBoundary({ fallback: <div>error</div> }) + .ErrorBoundary({ + fallback: <div>{t('common.error.failed_to_load_personas')}</div> + })Committable suggestion skipped: line range outside the PR's diff.
apps/web/src/app/[locale]/guild/_components/GuildCard.tsx (3)
23-34: 🛠️ Refactor suggestion
길드 가입 시 로딩 상태 처리가 필요합니다.
submitJoinGuild
함수에서 로딩 상태 처리가 누락되어 있습니다. 사용자에게 적절한 피드백을 제공해야 합니다.+ const [isJoining, setIsJoining] = useState(false); const submitJoinGuild = async (selectPersona: string) => { try { + setIsJoining(true); await joinGuildAction({ guildId: guild.id, personaId: selectPersona, }); queryClient.invalidateQueries({ queryKey: inboxQueries.allKey() }); } catch (error) { console.error(error); + // TODO: 에러 메시지 표시 + } finally { + setIsJoining(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 [isJoining, setIsJoining] = useState(false); const submitJoinGuild = async (selectPersona: string) => { try { setIsJoining(true); await joinGuildAction({ guildId: guild.id, personaId: selectPersona, }); queryClient.invalidateQueries({ queryKey: inboxQueries.allKey() }); } catch (error) { console.error(error); // TODO: 에러 메시지 표시 } finally { setIsJoining(false); } };
43-43: 🛠️ Refactor suggestion
하드코딩된 멤버 제한값이 있습니다.
멤버 제한이 15로 하드코딩되어 있습니다. 이는 설정 가능한 값이어야 합니다.
- <span>{guild.members.length}/ 15</span> + <span>{guild.members.length} / {guild.memberLimit}</span>Committable suggestion skipped: line range outside the PR's diff.
61-69: 💡 Codebase verification
주석 처리된 코드를 제거해 주세요
GuildJoinPetSelectDialog
컴포넌트는 이미GuildPetSelectDialog.tsx
에 구현되어 있고 다른 파일들에서 정상적으로 사용되고 있습니다. 따라서GuildCard.tsx
의 주석 처리된 코드는 불필요한 중복이므로 제거하는 것이 좋겠습니다.🔗 Analysis chain
주석 처리된 코드를 정리해야 합니다.
구현되지 않은 기능에 대한 주석 처리된 코드가 있습니다. 이는 제거하거나 구현을 완료해야 합니다.
주석 처리된
GuildJoinPetSelectDialog
컴포넌트의 구현 상태를 확인하겠습니다:🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # GuildJoinPetSelectDialog 컴포넌트 구현 여부 확인 rg -l "GuildJoinPetSelectDialog"Length of output: 338
Script:
#!/bin/bash # Check the implementation of both dialog components echo "=== GuildPetSelectDialog.tsx ===" cat apps/web/src/app/[locale]/guild/_components/GuildPetSelectDialog.tsx echo -e "\n=== Looking for GuildJoinPetSelectDialog implementation ===" rg "export.*GuildJoinPetSelectDialog" -A 5Length of output: 1919
apps/web/src/app/[locale]/guild/_components/GuidlInfoFormClient.tsx (2)
44-53: 🛠️ Refactor suggestion
접근성 및 이미지 대체 텍스트 개선 필요
이미지의 alt 텍스트가 파일 이름이나 background 값을 그대로 사용하고 있어 스크린 리더 사용자에게 의미 있는 정보를 제공하지 못합니다.
<img src={icon} className={cx( itemStyle, isSelected && css({ border: '1.5px solid', borderColor: 'white.white_90' }), )} width={70} height={70} - alt={icon} + alt={`길드 아이콘 옵션: ${icon.split('/').pop()?.split('.')[0]}`} /> <img src={getBackgroundImage(background)} className={cx( itemStyle, isSelected && css({ border: '1.5px solid', borderColor: 'white.white_90', }), )} width={284} height={124} key={background} - alt={background} + alt={`길드 배경 옵션: ${background}`} />Also applies to: 67-81
29-36: 🛠️ Refactor suggestion
폼 유효성 검사 개선 필요
현재 필드의 기본적인 유효성 검사가 누락되어 있습니다. 길드 이름과 설명에 대한 최소/최대 길이 제한이나 특수문자 제한 등의 유효성 검사가 필요합니다.
<TextField name="name" placeholder="Type guild name" className={css({ mb: '6px' })} defaultValue={props.initialData?.title} + required + minLength={2} + maxLength={50} /> <TextArea placeholder="Type guild description" name="description" defaultValue={props.initialData?.body} + required + minLength={10} + maxLength={500} />📝 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.<TextField name="name" placeholder="Type guild name" className={css({ mb: '6px' })} defaultValue={props.initialData?.title} required minLength={2} maxLength={50} /> <TextArea placeholder="Type guild description" name="description" defaultValue={props.initialData?.body} required minLength={10} maxLength={500} /> </div>
apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/join/page.tsx (1)
43-43: 🛠️ Refactor suggestion
빈 스타일 객체 수정 필요
dialogTitleStyle
이 빈 객체로 정의되어 있습니다. 필요한 스타일을 추가하거나 불필요한 경우 제거해주세요.-const dialogTitleStyle = css({}); +const dialogTitleStyle = css({ + textStyle: 'glyph24.bold', + textAlign: 'center', + mb: 2 +});📝 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 dialogTitleStyle = css({ textStyle: 'glyph24.bold', textAlign: 'center', mb: 2 });
apps/web/src/serverActions/guild.ts (1)
51-53: 🛠️ Refactor suggestion
로깅 개선 필요
에러 로깅이 중복되어 있으며, 운영 환경에 적합하지 않습니다. 구조화된 로깅으로 개선이 필요합니다.
} catch (error) { - console.log('error: ', error); - console.log('error: ', (error as Error).message); + console.error('길드 수정 실패:', { + guildId, + error: error instanceof Error ? error.message : String(error) + }); return { message: 'Failed to update guild', success: 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.console.error('길드 수정 실패:', { guildId, error: error instanceof Error ? error.message : String(error) }); return { message: 'Failed to update guild', success: false };
apps/web/src/app/[locale]/guild/_components/GuildCreateForm.tsx (3)
24-32: 🛠️ Refactor suggestion
필수 입력 필드 표시와 유효성 검사가 필요합니다
길드 이름과 설명이 필수 입력 항목인지 사용자가 알 수 없습니다. 또한 입력값에 대한 유효성 검사가 없습니다.
- 필수 입력 필드에 대한 표시 추가
- 길드 이름 길이 제한 추가
- 설명 글자 수 제한 추가
36-50: 🛠️ Refactor suggestion
⚠️ Potential issue코드 중복을 제거하고 이미지 선택 컴포넌트를 분리해주세요
아이콘과 배경 이미지 선택 부분의 코드가 매우 유사합니다. 재사용 가능한 컴포넌트로 분리하는 것이 좋습니다.
또한
img
태그에 불필요한key
prop이 중복 사용되고 있습니다.- <img - src={icon} - className={itemStyle} - width={70} - height={70} - key={icon} - alt={icon} - style={{ - border: formData.icon === icon ? '1.5px solid' : 'none', - }} - /> + <img + src={icon} + className={itemStyle} + width={70} + height={70} + alt={icon} + style={{ + border: formData.icon === icon ? '1.5px solid' : 'none', + }} + />Also applies to: 56-70
18-19: 🛠️ Refactor suggestion
접근성 개선과 로딩 상태 처리가 필요합니다
데이터 로딩 중일 때의 상태 처리가 없으며, 버튼들에 대한 접근성 레이블이 누락되었습니다.
isLoading
상태에 따른 로딩 UI 추가- 버튼에
aria-label
추가- 선택된 아이템에 대한
aria-selected
상태 추가apps/web/src/app/[locale]/guild/page.tsx (2)
27-36:
⚠️ Potential issue주석 처리된 코드를 정리하고 라우팅 로직을 명확히 해주세요
불필요한 주석 처리된 코드가 있으며,
allJoinGuilds
결과가 사용되지 않고 있습니다. 라우팅 로직의 의도가 불분명합니다.
- 주석 처리된 코드 제거
allJoinGuilds
관련 로직 정리- 라우팅 의도에 대한 주석 추가
38-50: 🛠️ Refactor suggestion
URL 생성 함수를 분리하고 에러 처리를 추가해주세요
URL 생성 로직이 컴포넌트 내부에 인라인으로 구현되어 있으며, API 호출에 대한 에러 처리가 없습니다.
- URL 생성 함수를 별도의 유틸리티로 분리
searchGuild
API 호출에 대한 에러 처리 추가randomId
파싱 실패에 대한 예외 처리 추가
# Conflicts: # apps/web/src/app/[locale]/guild/(components)/GuildSearch.tsx # apps/web/src/app/[locale]/guild/(components)/SelectPersonaList.tsx # apps/web/src/app/[locale]/guild/(components)/SortSelect.tsx # apps/web/src/app/[locale]/guild/(list)/GuildPetSelectDialog.tsx # apps/web/src/app/[locale]/guild/@modal/(.)create/page.tsx # apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/join/page.tsx # apps/web/src/app/[locale]/guild/@modal/(.)detail/[id]/page.tsx # apps/web/src/app/[locale]/guild/GuildSearch.tsx # apps/web/src/app/[locale]/guild/SortSelect.tsx # apps/web/src/app/[locale]/guild/[id]/MoreMenu.tsx # apps/web/src/app/[locale]/guild/[id]/setting/GuildSetting.tsx # apps/web/src/app/[locale]/guild/[id]/setting/layout.tsx # apps/web/src/app/[locale]/guild/[id]/setting/page.tsx # apps/web/src/app/[locale]/guild/_components/GuildSearch.tsx # apps/web/src/app/[locale]/guild/_components/SelectPersonaList.tsx # apps/web/src/app/[locale]/guild/_components/SortSelect.tsx # apps/web/src/app/[locale]/guild/create/page.tsx # apps/web/src/app/[locale]/guild/detail/[id]/join/page.tsx # apps/web/src/app/[locale]/guild/detail/[id]/layout.tsx # apps/web/src/app/[locale]/guild/detail/[id]/page.tsx # apps/web/src/app/[locale]/guild/page.tsx # apps/web/src/serverActions/guild.ts # packages/api/src/guild/index.ts
💡 기능
🔎 기타
Summary by CodeRabbit
릴리즈 노트
새로운 기능
개선 사항
버그 수정
기타