-
Notifications
You must be signed in to change notification settings - Fork 1
[Refactor] member 페이지 전반적인 구조 개선 및 코드 정리 #203
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
[Refactor] member 페이지 전반적인 구조 개선 및 코드 정리 #203
The head ref may contain hidden characters: "201-refactor-member-\uD398\uC774\uC9C0-\uC804\uBC18\uC801\uC778-\uAD6C\uC870-\uAC1C\uC120-\uBC0F-\uCF54\uB4DC-\uC815\uB9AC"
Conversation
- 오탈자 수정 - 불필요한 import 제거 - 주석 수정
- 함수명 개선 - onSubmit 함수 SearchForm.tsx으로 이동 - 문자열 상수 사용
|
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
apps/mobile/app/(route)/seats.tsxOops! Something went wrong! :( ESLint: 8.57.1 ESLint couldn't find the config "expo" to extend from. Please check that the name of the config is correct. The config "expo" was referenced from the config file in "/apps/mobile/.eslintrc.js". If you still have problems, please stop by https://eslint.org/chat/help to chat with the team. apps/mobile/app/(route)/dashboard.tsxOops! Something went wrong! :( ESLint: 8.57.1 ESLint couldn't find the config "expo" to extend from. Please check that the name of the config is correct. The config "expo" was referenced from the config file in "/apps/mobile/.eslintrc.js". If you still have problems, please stop by https://eslint.org/chat/help to chat with the team. apps/mobile/app/(route)/meetings.tsxOops! Something went wrong! :( ESLint: 8.57.1 ESLint couldn't find the config "expo" to extend from. Please check that the name of the config is correct. The config "expo" was referenced from the config file in "/apps/mobile/.eslintrc.js". If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.
워크스루이 풀 리퀘스트는 멤버 관리 페이지의 전반적인 구조를 개선하고 코드를 리팩토링하는 대규모 변경 사항을 포함하고 있습니다. 주요 변경 사항은 유틸리티 함수 이름 변경, 인터페이스 재구성, 컴포넌트 구조 최적화, 그리고 폼 처리 로직 개선에 중점을 두고 있습니다. 이러한 변경은 코드의 가독성과 유지보수성을 향상시키는 것을 목표로 합니다. 변경 사항
가능한 관련 이슈
추천 리뷰어
시
✨ Finishing Touches
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 (
|
🚀 Preview URLBranch: 201-refactor-member-페이지-전반적인-구조-개선-및-코드-정리 Preview URL: https://codeit.click?pr=203 |
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: 5
🔭 Outside diff range comments (5)
apps/api/src/utils/createPagedMembers.ts (1)
Line range hint
13-21: 타입 안전성 개선이 필요합니다.lastMember._id가 undefined일 경우 toString() 호출 시 런타임 에러가 발생할 수 있습니다.
다음과 같이 수정을 제안드립니다:
- const lastMember = results[results.length - 1]; + const lastMember = results[results.length - 1]; + const nextCursor = hasNextPage && lastMember?._id?.toString() || null; return { members: results, - nextCursor: hasNextPage && lastMember ? lastMember._id.toString() : null, + nextCursor, };apps/api/src/controllers/userController.ts (3)
Line range hint
264-283: 심각한 보안 문제가 발견되었습니다.
- 비밀번호가 1234로 하드코딩되어 있습니다.
- 비밀번호 해싱이 누락되어 있습니다.
다음과 같이 수정을 제안드립니다:
+ import { hash } from "bcryptjs"; const user = new User({ role: role ?? "member", name, email, - password: 1234, + password: await hash(password, 10), teams, profileImage: profileImageUrl, });
Line range hint
549-571: 파일 업로드 검증이 필요합니다.파일 타입과 크기에 대한 검증이 누락되어 있어 보안 위험이 있습니다.
다음과 같은 검증 로직 추가를 제안드립니다:
export const updateProfileImage = async (req: UpdateProfileImageRequest, res: Response): Promise<void> => { const userId = req.user?._id; const file = req.file as Express.MulterS3.File; + + const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif']; + const MAX_SIZE = 5 * 1024 * 1024; // 5MB + + if (!ALLOWED_TYPES.includes(file.mimetype)) { + res.status(400).send({ message: "지원하지 않는 파일 형식입니다." }); + return; + } + + if (file.size > MAX_SIZE) { + res.status(400).send({ message: "파일 크기는 5MB를 초과할 수 없습니다." }); + return; + } const profileImageUrl = file.location; // ... rest of the code
Line range hint
646-693: 비밀번호 복잡성 검증이 필요합니다.새 비밀번호의 복잡성(길이, 특수문자, 대소문자 등)을 검증하는 로직이 누락되어 있습니다.
다음과 같은 검증 로직 추가를 제안드립니다:
+ const isPasswordValid = (password: string): boolean => { + const minLength = 8; + const hasUpperCase = /[A-Z]/.test(password); + const hasLowerCase = /[a-z]/.test(password); + const hasNumbers = /\d/.test(password); + const hasSpecialChar = /[!@#$%^&*]/.test(password); + + return password.length >= minLength && + hasUpperCase && + hasLowerCase && + hasNumbers && + hasSpecialChar; + } export const updateUserCredentials = async (req: UpdateUserCredentialsRequest, res: Response): Promise<void> => { // ... existing validation ... + if (!isPasswordValid(newPassword)) { + res.status(400).send({ + message: "비밀번호는 8자 이상이며, 대문자, 소문자, 숫자, 특수문자를 포함해야 합니다." + }); + return; + } // ... rest of the codeapps/web/components/common/Fallback/index.tsx (1)
Line range hint
6-8: 인터페이스와 구현이 일치하지 않습니다.ErrorFallbackProps 인터페이스는 error를 필수 속성으로 정의하고 있지만, 컴포넌트 구현에서는 사용하지 않고 있습니다.
다음과 같이 수정하는 것을 제안합니다:
interface ErrorFallbackProps { - error: Error; resetErrorBoundary: () => void; }Also applies to: 11-11
🧹 Nitpick comments (20)
apps/web/app/(admin)/members/_components/Header.tsx (1)
25-25: 버튼 텍스트 구현이 개선되었습니다템플릿 리터럴을 사용하여 더 간단하고 유지보수하기 좋은 방식으로 개선되었습니다.
다음과 같이 더 간단하게 작성할 수 있습니다:
- <span>{`+ ${MEMBER_FORM_MESSAGES.TITLE.ADD}`}</span> + <span>+ {MEMBER_FORM_MESSAGES.TITLE.ADD}</span>apps/web/app/(admin)/members/_components/sidepanel/Header.tsx (1)
Line range hint
1-40: 전반적인 컴포넌트 구조에 대한 제안컴포넌트의 전반적인 구조는 잘 작성되어 있지만, 다음과 같은 개선사항을 고려해보시면 좋을 것 같습니다:
- 타입 정의를 별도의 types.ts 파일로 분리
- 스타일 관련 클래스네임이 길어 가독성이 떨어지므로, 별도의 상수로 분리
다음과 같이 리팩토링하는 것을 제안드립니다:
// types.ts export interface HeaderProps { selectedMember: MemberWithFileImage | null; onClose: () => void; } // constants.ts export const WITHDRAW_BUTTON_STYLES = "text-sm-medium text-custom-black/80 hover:bg-custom-black/5 hover:text-custom-black w-71 rounded-6 border-custom-black/20 h-32 border transition-all duration-300";그리고 컴포넌트에서는 다음과 같이 사용:
import { type HeaderProps } from './types'; import { WITHDRAW_BUTTON_STYLES } from './constants'; export default function Header({ selectedMember, onClose }: HeaderProps): JSX.Element { // ... 기존 코드 ... return ( // ... 기존 코드 ... <button type="button" onClick={handleWithdraw} className={WITHDRAW_BUTTON_STYLES} > // ... 기존 코드 ... ); }apps/web/lib/axiosInterceptor.ts (1)
6-15: 에러 처리 로직을 더욱 견고하게 개선하면 좋겠습니다.현재 구현은 기본적인 에러 처리를 제공하지만, 다음과 같은 개선사항들을 고려해보시면 좋겠습니다:
- HTTP 상태 코드별 세분화된 에러 처리
- 에러 로깅 메커니즘 추가
- 특정 에러에 대한 재시도 로직
다음과 같이 개선해보는 것은 어떨까요?:
axiosInstance.interceptors.response.use( (response) => response, (error: Error | AxiosError<ResponseWithMessage>) => { if (axios.isAxiosError(error)) { + // 상태 코드별 처리 + const status = error.response?.status; + switch (status) { + case 401: + // 인증 에러 처리 + break; + case 404: + // 리소스 없음 처리 + break; + case 500: + // 서버 에러 처리 + break; + } + + // 에러 로깅 + console.error(`API Error: ${error.message}`, { + status, + url: error.config?.url, + method: error.config?.method, + }); + notifyMutationError(error); } return Promise.reject(error); }, );apps/web/app/(admin)/teams/_hooks/useTeamsMutations.ts (1)
9-9: 상대 경로 사용에 대한 재고려가 필요합니다.상대 경로(
../../../_hooks/useDebounceCallback)를 사용하면 파일 구조 변경 시 유지보수가 어려워질 수 있습니다. 절대 경로(@/hooks/useDebounceCallback)를 사용하는 것이 더 안정적일 수 있습니다.-import { useDebouncedCallback } from "../../../_hooks/useDebounceCallback"; +import { useDebouncedCallback } from "@/hooks/useDebounceCallback";apps/api/src/controllers/userController.ts (1)
Line range hint
386-440: 트랜잭션 지원 추가를 권장드립니다.여러 데이터베이스 작업이 순차적으로 실행되므로, 트랜잭션을 사용하여 데이터 일관성을 보장하는 것이 좋습니다.
다음과 같이 트랜잭션을 적용하는 것을 제안드립니다:
export const updateUser = async (req: UpdateUserRequest, res: Response): Promise<void> => { + const session = await mongoose.startSession(); + try { + await session.withTransaction(async () => { // ... existing code ... const updatedUser = await User.findByIdAndUpdate( userId, { $set: updateFields }, - { new: true } + { new: true, session } ); res.status(200).send({ message: "사용자 정보가 성공적으로 수정되었습니다.", user: updatedUser, }); + }); + } catch (error) { + res.status(500).send({ message: "사용자 정보 수정 중 오류가 발생했습니다." }); + } finally { + session.endSession(); + } };apps/web/app/types/ImageType.ts (1)
1-8: 타입 구조가 잘 설계되었습니다!타입 계층이 명확하고 유연성이 좋습니다. 다만, 각 타입의 사용 목적과 예시를 JSDoc으로 문서화하면 더 좋을 것 같습니다.
예시:
+/** 이미지 URL을 나타내는 문자열 타입 */ export type ImageUrlType = string; +/** next/image의 StaticImageData를 위한 타입 */ export type StaticImageType = StaticImageData;apps/web/app/(admin)/members/_schemas/Form.schema.ts (1)
12-15: 이메일 유효성 검사를 강화해주세요.현재 기본 이메일 패턴만 확인하고 있습니다. 비즈니스 요구사항에 따라 다음과 같은 추가 검증이 필요할 수 있습니다:
- 특정 도메인만 허용
- 이메일 길이 제한
- 특수문자 제한
예시:
email: z .string() .nonempty(MEMBER_FORM_MESSAGES.VALIDATION.EMAIL.REQUIRED) - .email(MEMBER_FORM_MESSAGES.VALIDATION.EMAIL.PATTERN), + .email(MEMBER_FORM_MESSAGES.VALIDATION.EMAIL.PATTERN) + .max(100, "이메일은 100자를 초과할 수 없습니다") + .refine( + (email) => email.endsWith("@company.com"), + "회사 이메일(@company.com)만 사용 가능합니다" + ),apps/web/components/common/Fallback/index.tsx (1)
23-23: 에러 메시지 처리 방식이 개선되었습니다.사용자 친화적인 정적 메시지로 변경한 것은 좋은 접근입니다. 하지만 개발자가 디버깅할 때 필요할 수 있는 실제 에러 정보를 로깅하는 것을 고려해보세요.
apps/web/app/(admin)/members/_components/search/SearchInput.tsx (1)
20-22: 함수 이름을 더 명확하게 개선할 수 있습니다.
updateInputChange보다는handleKeywordChange가 함수의 목적을 더 잘 설명할 것 같습니다.- const updateInputChange = (e: ChangeEvent<HTMLInputElement>): void => { + const handleKeywordChange = (e: ChangeEvent<HTMLInputElement>): void => { const value = e.target.value; debouncedSetValue(value); };packages/types/src/membersType.ts (1)
61-66: 쿼리 파라미터 타입이 잘 정의되었습니다.MembersQueryParams 인터페이스가 명확하게 정의되어 있습니다. 각 필드에 대한 JSDoc 문서를 추가하면 더 좋을 것 같습니다.
export interface MembersQueryParams { + /** 정렬 옵션 (최신순, 오래된순, 가나다순) */ selectedSort: SortOption; + /** 역할별 필터링 (optional) */ role?: string; + /** 팀별 필터링 (optional) */ team?: string; + /** 검색어 (optional) */ keyword?: string; }apps/web/app/(admin)/members/_hooks/useKeywordsForm.ts (1)
40-43: 사용자 피드백 추가 고려
removeAllKeywords실행 시 사용자에게 작업 완료 피드백을 제공하면 좋을 것 같습니다.apps/web/app/(admin)/members/_components/MemberList.tsx (1)
36-36: queryParams 할당 위치 재검토
queryParams를 컴포넌트 최상위에서 할당하는 것이 더 명확할 수 있습니다.apps/web/app/(admin)/members/_components/sidepanel/ProfileImageUploader.tsx (1)
56-57: 이미지 크기 상수를 활용한 개선이 이루어졌습니다.하드코딩된 값 대신
IMAGE_SIZES.MD상수를 사용하여 일관성이 향상되었습니다. 다만, className에서 문자열 보간법을 사용할 때 주의가 필요합니다.className을 다음과 같이 개선할 수 있습니다:
- className={`rounded-full object-cover size-${IMAGE_SIZES.MD}`} + className={cn('rounded-full object-cover', `size-${IMAGE_SIZES.MD}`)}Also applies to: 61-61
apps/web/app/(admin)/members/_components/search/index.tsx (1)
46-59: 검색 제출 로직이 개선되었습니다.검색어 처리와 히스토리 저장 로직이 잘 구현되었습니다. 다만, 중복 검색어 처리 시 성능 개선의 여지가 있습니다.
검색어 중복 제거 로직을 다음과 같이 최적화할 수 있습니다:
- const updatedKeywords = [ - trimmedKeyword, - ...searchHistory.filter((existingKeyword) => existingKeyword !== trimmedKeyword), - ].slice(0, 5); + const updatedKeywords = new Set([trimmedKeyword, ...searchHistory]).slice(0, 5);apps/web/app/(admin)/members/_components/ResponsiveMembersPage.tsx (1)
71-77: 모바일 환경을 위한 UI가 적절히 구현되었습니다.모바일에서만 보이는 하단 버튼이 잘 구현되었습니다. 다만, 접근성 개선이 필요해 보입니다.
접근성 향상을 위해 다음과 같이 개선할 수 있습니다:
- <Button variant="Primary" type="button" className="flex h-48 w-full md:hidden" onClick={handleOpenSidePanel}> + <Button + variant="Primary" + type="button" + className="flex h-48 w-full md:hidden" + onClick={handleOpenSidePanel} + aria-label={MEMBER_FORM_MESSAGES.TITLE.ADD} + >apps/web/app/(admin)/members/_components/sidepanel/Form.tsx (5)
1-17: 의존성 import 구조 개선 필요import 구문이 일관성 없이 구성되어 있습니다. 다음과 같이 개선하는 것이 좋겠습니다:
- 외부 라이브러리
- 내부 공통 컴포넌트
- 상수 및 타입
- 로컬 컴포넌트/훅
다음과 같이 import 구문을 정리해보세요:
// 외부 라이브러리 import { zodResolver } from "@hookform/resolvers/zod"; import { Controller, useForm } from "react-hook-form"; // 내부 공통 컴포넌트 import { Radio } from "@ui/index"; import Input from "@ui/src/components/common/Input"; import Button from "@ui/src/components/common/Button"; // 상수 및 타입 import { MEMBER_FORM_MESSAGES } from "@repo/constants/messages"; import { DEFAULT_MEMBER_FORM_VALUES } from "@repo/constants"; import { ROLE_LABELS, type RoleOption, type MemberWithFileImage, type SidePanelFormData, } from "@repo/types/src/membersType"; import type { FormImageType, ImageFileType } from "@/app/types/ImageType"; // 로컬 컴포넌트/훅 import { useMembersMutations } from "../../_hooks/useMembersMutations"; import { memberFormSchema } from "../../_schemas/Form.schema"; import ProfileImageUploader from "./ProfileImageUploader"; import TeamDropdown from "./TeamDropdown";
34-41: 폼 초기화 및 뮤테이션 성공 콜백 처리 개선 필요뮤테이션 성공 콜백에서 폼 초기화와 패널 닫기가 동기적으로 실행되고 있습니다. 사용자 경험 향상을 위해 약간의 지연을 추가하는 것이 좋겠습니다.
const { updateMember, createMember, isPending } = useMembersMutations({ onSuccess: () => { - reset(DEFAULT_MEMBER_FORM_VALUES); - onClose(); + // 성공 메시지가 보일 수 있도록 약간의 지연 추가 + setTimeout(() => { + reset(DEFAULT_MEMBER_FORM_VALUES); + onClose(); + }, 500); }, });
43-57: 유틸리티 함수 모듈화 필요역할(role) 관련 유틸리티 함수들을 별도의 파일로 분리하여 재사용성을 높이는 것이 좋겠습니다.
_utils/roleUtils.ts파일을 생성하여 다음과 같이 분리하는 것을 제안합니다:// _utils/roleUtils.ts import { ROLE_LABELS, type RoleOption } from "@repo/types/src/membersType"; export const getRoleDisplay = (value: RoleOption): string => { return ROLE_LABELS[value]; }; export const getRoleValue = (displayText: string): RoleOption => { const entry = Object.entries(ROLE_LABELS).find(([_, value]) => value === displayText); return entry?.[0] as RoleOption; };
95-101: 폼 리셋 로직에 클린업 함수 추가 필요
useEffect훅에 클린업 함수를 추가하여 컴포넌트 언마운트 시 폼 상태를 정리하는 것이 좋겠습니다.useEffect(() => { if (selectedMember) { reset({ ...selectedMember }); } else { reset(DEFAULT_MEMBER_FORM_VALUES); } + return () => { + reset(DEFAULT_MEMBER_FORM_VALUES); + }; }, [reset, selectedMember]);
104-104: 폼 제출 핸들러 단순화 필요
void연산자를 사용한 폼 제출 핸들러가 불필요하게 복잡합니다.- <form className="flex h-full flex-col justify-between" onSubmit={(...args) => void handleSubmit(onSubmit)(...args)}> + <form className="flex h-full flex-col justify-between" onSubmit={handleSubmit(onSubmit)}>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (37)
apps/api/src/controllers/userController.ts(17 hunks)apps/api/src/utils/createFilters.ts(1 hunks)apps/api/src/utils/createPagedMembers.ts(1 hunks)apps/web/api/members.ts(1 hunks)apps/web/app/(admin)/members/_components/Header.tsx(2 hunks)apps/web/app/(admin)/members/_components/MemberList.tsx(3 hunks)apps/web/app/(admin)/members/_components/MemberListItem.tsx(2 hunks)apps/web/app/(admin)/members/_components/MembersContent.tsx(0 hunks)apps/web/app/(admin)/members/_components/Navbar.tsx(2 hunks)apps/web/app/(admin)/members/_components/ResponsiveMembersPage.tsx(1 hunks)apps/web/app/(admin)/members/_components/search/SearchInput.tsx(3 hunks)apps/web/app/(admin)/members/_components/search/index.tsx(2 hunks)apps/web/app/(admin)/members/_components/sidepanel/Form.tsx(4 hunks)apps/web/app/(admin)/members/_components/sidepanel/Header.tsx(1 hunks)apps/web/app/(admin)/members/_components/sidepanel/ProfileImageUploader.tsx(3 hunks)apps/web/app/(admin)/members/_components/sidepanel/index.tsx(2 hunks)apps/web/app/(admin)/members/_components/skeleton/MemberListSkeleton.tsx(1 hunks)apps/web/app/(admin)/members/_components/skeleton/TabSkeleton.tsx(1 hunks)apps/web/app/(admin)/members/_hooks/useKeywordsForm.ts(1 hunks)apps/web/app/(admin)/members/_hooks/useMembersForm.ts(0 hunks)apps/web/app/(admin)/members/_hooks/useMembersMutations.ts(0 hunks)apps/web/app/(admin)/members/_hooks/useMembersSuspenseInfiniteQuery.ts(2 hunks)apps/web/app/(admin)/members/_schemas/Form.schema.ts(1 hunks)apps/web/app/(admin)/teams/_hooks/useTeamsMutations.ts(3 hunks)apps/web/app/_hooks/useSignInMutation.ts(0 hunks)apps/web/app/providers.tsx(1 hunks)apps/web/app/settings/_components/modals/MembersSettingModal.tsx(2 hunks)apps/web/app/settings/_hooks/useUserMutations.ts(0 hunks)apps/web/app/types/ImageType.ts(1 hunks)apps/web/components/common/Fallback/index.tsx(2 hunks)apps/web/lib/axiosInterceptor.ts(1 hunks)apps/web/lib/queryKey.ts(1 hunks)apps/web/package.json(2 hunks)packages/constants/index.ts(3 hunks)packages/constants/messages.ts(1 hunks)packages/types/src/membersType.ts(2 hunks)packages/types/src/searchFormType.ts(1 hunks)
💤 Files with no reviewable changes (5)
- apps/web/app/_hooks/useSignInMutation.ts
- apps/web/app/settings/_hooks/useUserMutations.ts
- apps/web/app/(admin)/members/_hooks/useMembersMutations.ts
- apps/web/app/(admin)/members/_components/MembersContent.tsx
- apps/web/app/(admin)/members/_hooks/useMembersForm.ts
✅ Files skipped from review due to trivial changes (7)
- apps/web/app/providers.tsx
- apps/api/src/utils/createFilters.ts
- apps/web/app/(admin)/members/_components/skeleton/TabSkeleton.tsx
- apps/web/app/(admin)/members/_components/skeleton/MemberListSkeleton.tsx
- packages/types/src/searchFormType.ts
- apps/web/app/(admin)/members/_components/sidepanel/index.tsx
- apps/web/app/(admin)/members/_components/MemberListItem.tsx
🔇 Additional comments (28)
apps/web/app/(admin)/members/_components/Navbar.tsx (2)
6-6: 컴포넌트 이름이 더 명확해졌습니다!단수형
TabSkeleton으로 변경한 것이 컴포넌트의 실제 용도를 더 잘 반영합니다. 각각의 탭에 대한 스켈레톤이 아닌, 전체 탭 영역에 대한 단일 스켈레톤이기 때문입니다.
21-23: Suspense 패턴이 잘 구현되었습니다.Tab 컴포넌트를 Suspense로 감싸고 적절한 스켈레톤 UI를 제공하여 사용자 경험이 향상되었습니다. 이는 React의 권장 패턴을 잘 따르고 있습니다.
apps/web/app/(admin)/members/_components/Header.tsx (2)
14-14: 화면 크기에 따른 타이틀 표시 방식 검토 필요타이틀이 중간 크기 화면(md)에서 표시되지 않고 큰 화면(lg)에서만 표시되도록 변경되었습니다. 사용자 경험 측면에서 중간 크기 화면에서도 타이틀이 필요할 수 있습니다.
중간 크기 화면에서 타이틀이 없어도 문제가 없는지 확인이 필요합니다.
15-15: 레이아웃 간격 개선이 잘 되었습니다큰 화면에서의 간격(lg:gap-30)과 반응형 레이아웃이 적절하게 구현되었습니다.
apps/web/app/(admin)/members/_components/sidepanel/Header.tsx (2)
11-11: 함수 이름 변경이 적절합니다.컴포넌트가 이미 Header.tsx 파일에 위치해 있으므로,
PanelHeader에서Header로의 이름 변경은 중복성을 제거하고 더 간결한 네이밍을 제공합니다.
18-18: 가독성 향상을 위한 빈 줄 추가가 좋습니다.조건 검사와 실제 동작 사이에 빈 줄을 추가하여 코드의 논리적 흐름을 더 명확하게 구분했습니다.
apps/web/lib/axiosInterceptor.ts (1)
1-4: 필요한 의존성들이 잘 구성되어 있습니다!타입 정의와 유틸리티 함수들이 적절하게 임포트되어 있으며, 공유 타입을 사용하는 것이 좋은 접근 방식입니다.
apps/api/src/utils/createPagedMembers.ts (1)
3-11: 인터페이스 구조가 명확하고 잘 정의되어 있습니다!타입 정의가 명확하며 네이밍이 직관적입니다.
apps/web/lib/queryKey.ts (1)
1-1: 타입 정의가 개선되었습니다!
UseMembersSuspenseInfiniteQueryParams에서MembersQueryParams로의 변경은 타입 시스템을 단순화하면서도 타입 안전성을 유지하고 있습니다.Also applies to: 6-6
apps/web/app/settings/_components/modals/MembersSettingModal.tsx (1)
1-1: 새로운 컴포넌트의 구현을 확인해주세요.
MembersContent에서ResponsiveMembersPage로의 변경이 확인되었습니다. 다음 사항들을 검증해주시기 바랍니다:
- 반응형 레이아웃이 모든 화면 크기에서 정상 작동하는지
- 이전
MembersContent의 모든 기능이 새 컴포넌트에 정상적으로 구현되었는지Also applies to: 14-14
✅ Verification successful
ResponsiveMembersPage컴포넌트 구현이 확인되었습니다새로운 컴포넌트가 다음 기능들을 올바르게 구현하고 있습니다:
- 모바일 리디렉션 및 반응형 레이아웃 처리
- 멤버 목록 관리, 검색, 정렬 기능
- 사이드 패널을 통한 멤버 상세 정보 표시
- 에러 처리 및 로딩 상태 관리
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # ResponsiveMembersPage 컴포넌트의 구현을 확인 ast-grep --pattern 'export default function ResponsiveMembersPage' -A 20Length of output: 74
Script:
#!/bin/bash # Find the ResponsiveMembersPage component file fd ResponsiveMembersPage # Look for the component implementation using ripgrep rg -A 20 "ResponsiveMembersPage" --type tsx # Check the specific path from import fd -p "app/\(admin\)/members/_components/ResponsiveMembersPage"Length of output: 298
Script:
#!/bin/bash # View the ResponsiveMembersPage component implementation cat "apps/web/app/(admin)/members/_components/ResponsiveMembersPage.tsx" # Search for any references to the old MembersContent component rg "MembersContent" -t ts -t tsxLength of output: 3254
apps/web/app/(admin)/members/_schemas/Form.schema.ts (1)
7-7: role 필드의 기본값 설정을 재검토해주세요.
role필드의 기본값이 "member"로 설정되어 있습니다. 보안상의 이유로 기본값 없이 명시적인 role 지정을 요구하는 것이 더 안전할 수 있습니다.- role: z.enum(Object.keys(ROLE_LABELS) as [keyof typeof ROLE_LABELS]).default("member"), + role: z.enum(Object.keys(ROLE_LABELS) as [keyof typeof ROLE_LABELS]),✅ Verification successful
기본 role 설정이 적절합니다
현재 구현은 다음과 같은 이유로 안전하고 적절합니다:
- 해당 폼은 관리자 섹션에서만 접근 가능합니다
- "member"는 가장 낮은 권한을 가진 역할이므로 기본값으로 적절합니다
- 관리자가 필요한 경우 명시적으로 역할을 변경할 수 있습니다
🏁 Scripts executed
The following scripts were executed for the analysis:
Error: No shell scripts found
Script:
#!/bin/bash # Find ROLE_LABELS definition and its usage echo "=== Looking for ROLE_LABELS definition ===" rg "ROLE_LABELS.*=" -A 5 echo -e "\n=== Looking for Form schema usage ===" rg -l "Form\.schema" echo -e "\n=== Looking for role-based security patterns ===" rg "role.*=.*member" -A 5Length of output: 2510
apps/web/app/(admin)/members/_hooks/useMembersSuspenseInfiniteQuery.ts (1)
7-7: 타입 정의가 개선되었습니다.MembersQueryParams를 사용하여 타입을 중앙화한 것은 좋은 리팩토링입니다. 코드의 일관성과 유지보수성이 향상되었습니다.
Also applies to: 21-21
apps/web/app/(admin)/members/_components/search/SearchInput.tsx (1)
5-5: 훅 임포트 경로가 개선되었습니다.공통 훅을
_hooks폴더로 이동한 것은 코드 구조를 개선하는 좋은 변경입니다.apps/web/app/(admin)/members/_hooks/useKeywordsForm.ts (1)
10-15: 인터페이스 구조가 개선되었습니다!타입 정의가 더 명확해졌고, 메서드 이름이 직관적으로 변경되었습니다.
apps/web/api/members.ts (2)
2-7: 타입 임포트 구조가 개선되었습니다!타입 임포트를 그룹화하여 가독성이 향상되었습니다.
10-12: 인터페이스 확장이 적절합니다!
MembersQueryParams를 확장하여 코드 재사용성이 향상되었습니다.packages/constants/messages.ts (1)
88-93: 검색 관련 메시지가 잘 구조화되었습니다!상수 정의가 기존 패턴을 잘 따르고 있으며, 'as const' 사용으로 타입 안정성이 보장됩니다.
apps/web/app/(admin)/members/_components/MemberList.tsx (1)
Line range hint
17-34: getQueryParams 함수의 성능 최적화 검토 필요
useMemo에서 일반 함수로 변경되었는데, 이는 렌더링 시마다 새로운 객체가 생성될 수 있습니다. 성능에 영향을 미칠 수 있으니 검토가 필요합니다.다음 스크립트로 함수가 호출되는 빈도를 확인해보세요:
apps/web/app/(admin)/members/_components/sidepanel/ProfileImageUploader.tsx (2)
7-8: 타입 정의와 상수 사용이 개선되었습니다.타입과 상수를 별도의 파일로 분리하여 재사용성과 유지보수성이 향상되었습니다.
Also applies to: 13-13
16-16: 이미지 소스 처리 로직이 단순화되었습니다.이미지 소스를 가져오는 로직이 더 간단하고 명확해졌습니다. 하지만
currentImage가null일 때의 처리를 추가로 검토해 보시기 바랍니다.다음과 같이 타입 가드를 사용하여 더 안전하게 처리할 수 있습니다:
- if (isImageError || !currentImage) { + if (isImageError || !currentImage || typeof currentImage === 'undefined') {Also applies to: 30-38
apps/web/app/(admin)/members/_components/search/index.tsx (2)
18-22: 함수 이름이 더 명확해졌습니다.
handleOpenKeyword에서openSearchHistory로,handleCloseKeyword에서closeSearchHistory로 변경되어 함수의 목적이 더 명확해졌습니다.Also applies to: 26-26
28-30: 키워드 선택 로직이 개선되었습니다.키워드 선택 후 검색창을 닫는 로직이 순차적으로 잘 구현되었습니다.
apps/web/app/(admin)/members/_components/ResponsiveMembersPage.tsx (2)
20-24: 상태 관리가 체계적으로 구현되었습니다.여러 상태들이 명확한 목적을 가지고 잘 분리되어 있습니다.
29-41: 사이드 패널 관련 핸들러 함수들이 잘 구현되었습니다.멤버 선택과 사이드 패널 열기/닫기 로직이 명확하게 구현되었습니다.
packages/constants/index.ts (3)
53-55: 이미지 크기 상수가 적절히 정의되었습니다.이미지 크기를 상수로 분리하여 일관성 있게 관리할 수 있게 되었습니다.
67-73: 멤버 폼의 기본값이 잘 정의되었습니다.필수 필드들의 기본값이 명확하게 정의되어 있습니다.
75-78: 검색 키워드 관련 기본값이 잘 정의되었습니다.검색 기록 관리를 위한 기본값이 적절히 정의되어 있습니다.
apps/web/package.json (1)
13-13: 의존성 버전 관리 개선 필요새로 추가된 의존성의 버전 관리에 대해 다음 사항들을 고려해주세요:
@hookform/resolvers와zod의 버전이 캐럿(^) 범위를 사용하고 있어 예기치 않은 호환성 문제가 발생할 수 있습니다.[email protected]은 현재 최신 버전(3.22.4)보다 오래된 버전입니다.다음 스크립트를 실행하여 의존성 버전의 호환성을 확인해주세요:
Also applies to: 27-27
bokeeeey
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.
고생했어~
apps/web/app/(admin)/members/_components/search/SearchInput.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(admin)/members/_components/sidepanel/ProfileImageUploader.tsx
Outdated
Show resolved
Hide resolved
- 스키마 이름을 MembersSchema로 변경 - SidePanelFormData 타입을 스키마 기반 MembersType으로 대체
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
🧹 Nitpick comments (5)
apps/web/app/(admin)/members/_components/search/SearchInput.tsx (2)
23-26: 이벤트 핸들러 네이밍 및 로직 개선 필요이벤트 핸들러의 네이밍과 로직을 다음과 같이 개선하는 것이 좋습니다:
- const handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => { - const value = e.target.value; - debouncedSetValue(value); - }; + const handleSearchInputChange = (e: ChangeEvent<HTMLInputElement>): void => { + debouncedSetValue(e.target.value); + };
- 함수명을
handleSearchInputChange로 변경하여 검색 입력임을 명확히 함- 불필요한 임시 변수 제거
37-39: 이벤트 핸들러 참조 업데이트 필요위에서 제안된 함수명 변경을 적용할 경우 register 설정도 함께 업데이트해야 합니다:
{...register("keyword", { - onChange: handleInputChange, + onChange: handleSearchInputChange, })}apps/web/app/(admin)/members/_components/search/index.tsx (2)
31-34: 스토리지 작업에 대한 에러 처리 추가 필요로컬 스토리지 작업 시 발생할 수 있는 예외 상황에 대한 처리가 필요합니다:
const saveSearchHistory = (keywords: string[]): void => { + try { setValue("searchHistory", keywords); storage.set<string[]>("searchHistory", keywords); + } catch (error) { + console.error('검색 기록 저장 중 오류 발생:', error); + // 사용자에게 오류 알림 표시 로직 추가 필요 + } };
85-85: 폼 제출 타입 안전성 개선 필요폼 제출 시 타입 안전성을 보장하기 위해 다음과 같이 수정하는 것이 좋습니다:
- onSubmit={(...args) => void handleSubmit(onSubmit)(...args)} + onSubmit={handleSubmit((data: KeywordsFormData) => { + void onSubmit(data); + })}apps/mobile/app/(route)/settings.tsx (1)
6-6: 타입 안정성 개선을 고려해보세요.메시지 처리 로직이 잘 구현되었습니다. 추가적인 개선사항으로,
type과data의 타입을 명시적으로 정의하여 타입 안정성을 더욱 강화하는 것을 고려해보시면 좋을 것 같습니다.예시:
type MessageType = 'navigation' | 'action' | /* other types */; type MessageData = { // define expected data structure }; const { type, data }: { type: MessageType; data: MessageData } = parseMessage(e);Also applies to: 13-15
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
.github/workflows/preview.yml(0 hunks)apps/mobile/app/(route)/dashboard.tsx(1 hunks)apps/mobile/app/(route)/meetings.tsx(1 hunks)apps/mobile/app/(route)/seats.tsx(1 hunks)apps/mobile/app/(route)/settings.tsx(1 hunks)apps/web/app/(admin)/members/_components/MemberListItem.tsx(2 hunks)apps/web/app/(admin)/members/_components/search/SearchInput.tsx(3 hunks)apps/web/app/(admin)/members/_components/search/index.tsx(3 hunks)apps/web/app/(admin)/members/_components/sidepanel/Form.tsx(4 hunks)apps/web/app/(admin)/members/_components/sidepanel/ProfileImageUploader.tsx(2 hunks)apps/web/app/(admin)/members/_hooks/useKeywordsForm.ts(0 hunks)apps/web/app/(admin)/members/_hooks/useMembersSuspenseInfiniteQuery.ts(2 hunks)apps/web/app/(admin)/members/_schemas/Form.schema.ts(1 hunks)apps/web/lib/queryKey.ts(1 hunks)packages/constants/index.ts(1 hunks)packages/types/src/membersType.ts(2 hunks)packages/types/src/searchFormType.ts(0 hunks)
💤 Files with no reviewable changes (3)
- packages/types/src/searchFormType.ts
- .github/workflows/preview.yml
- apps/web/app/(admin)/members/_hooks/useKeywordsForm.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/app/(admin)/members/_components/MemberListItem.tsx
- packages/constants/index.ts
- apps/web/app/(admin)/members/_components/sidepanel/ProfileImageUploader.tsx
🔇 Additional comments (10)
apps/web/lib/queryKey.ts (1)
1-1: 타입 일관성 개선이 잘 되었습니다!
MembersQueryParams타입을 중앙 집중화하여 사용함으로써 코드의 일관성과 유지보수성이 향상되었습니다.Also applies to: 6-6
apps/web/app/(admin)/members/_schemas/Form.schema.ts (2)
6-18: 스키마 네이밍 컨벤션 수정이 필요합니다.이전 리뷰 피드백에 따라 스키마 이름을 파스칼케이스로 변경하는 것이 좋겠습니다.
-export const MembersSchema = z.object({ +export const MembersSchema = z.object({
8-17: 스키마 유효성 검사 구현이 잘 되었습니다!
- 적절한 유효성 검사 규칙이 설정되어 있습니다.
- 에러 메시지가 상수로 분리되어 있어 관리가 용이합니다.
- 각 필드의 제약 조건이 명확하게 정의되어 있습니다.
apps/web/app/(admin)/members/_hooks/useMembersSuspenseInfiniteQuery.ts (1)
7-7: 타입 정의와 쿼리 키 참조가 개선되었습니다!
MembersQueryParams타입을 사용하여 코드 일관성이 향상되었습니다.- 쿼리 키 참조가 대문자로 통일되어 네이밍 컨벤션이 일관되게 적용되었습니다.
Also applies to: 16-21, 23-23
packages/types/src/membersType.ts (1)
53-58: 쿼리 파라미터 인터페이스가 잘 정의되었습니다!
- 필요한 모든 쿼리 파라미터가 명확하게 정의되어 있습니다.
- 옵셔널 파라미터가 적절하게 표시되어 있습니다.
- 타입 정의가 일관성 있게 되어 있습니다.
apps/web/app/(admin)/members/_components/sidepanel/Form.tsx (2)
69-84: FormData 생성 로직 개선 필요이전 리뷰 코멘트에서 제안된 FormData 생성 로직의 개선사항이 반영되지 않았습니다. 다음과 같은 개선이 필요합니다:
const createMemberFormData = (data: MembersType): FormData => { + try { const formData = new FormData(); + + // 필수 필드 검증 + if (!data.role || !data.name || !data.email) { + throw new Error('필수 필드가 누락되었습니다'); + } + formData.append("role", data.role); formData.append("name", data.name); formData.append("email", data.email); data.teams.forEach((team) => { formData.append("teams[]", team); }); if (data.profileImage instanceof File) { formData.append("profileImage", data.profileImage); } return formData; + } catch (error) { + console.error('폼 데이터 생성 중 오류 발생:', error); + throw error; + } };
126-128: 입력 필드 접근성 문제 해결 필요이전 리뷰에서 지적된 입력 필드의 접근성 문제가 아직 해결되지 않았습니다. 레이블과 aria 속성을 추가해야 합니다:
- <Input placeholder={MEMBER_FORM_MESSAGES.PLACEHOLDER.NAME} error={errors.name} {...register("name")} /> + <Input + id="name" + label="이름" + placeholder={MEMBER_FORM_MESSAGES.PLACEHOLDER.NAME} + error={errors.name} + aria-invalid={errors.name ? "true" : "false"} + {...register("name")} + /> - <Input placeholder={MEMBER_FORM_MESSAGES.PLACEHOLDER.EMAIL} error={errors.email} {...register("email")} /> + <Input + id="email" + label="이메일" + type="email" + placeholder={MEMBER_FORM_MESSAGES.PLACEHOLDER.EMAIL} + error={errors.email} + aria-invalid={errors.email ? "true" : "false"} + {...register("email")} + />apps/mobile/app/(route)/seats.tsx (1)
6-6: 메시지 처리 로직이 개선되었습니다!메시지 파싱 로직을 중앙화하여 코드의 일관성과 유지보수성이 향상되었습니다.
parseMessage유틸리티를 사용하여 타입과 데이터를 명시적으로 추출하는 방식이 좋습니다.Also applies to: 13-15
apps/mobile/app/(route)/meetings.tsx (1)
6-6: 일관된 메시지 처리 패턴이 적용되었습니다!다른 화면들과 동일한 메시지 처리 패턴을 적용하여 코드의 일관성이 유지되었습니다. 이는 향후 유지보수와 디버깅을 용이하게 만들 것입니다.
Also applies to: 13-15
apps/mobile/app/(route)/dashboard.tsx (1)
6-6: parseMessage 구현을 확인해주세요.메시지 처리 로직이 일관되게 적용되었습니다. 다만,
parseMessage유틸리티의 구현을 확인하여 모든 예외 상황이 적절히 처리되는지 검증이 필요합니다.다음 스크립트를 실행하여
parseMessage구현을 확인해주세요:Also applies to: 13-15
🚀 작업 내용
FE
BE
ci/cd
🚨 관련 이슈 (이슈 번호)
✅ 체크리스트
Summary by CodeRabbit
다음은 릴리즈 노트입니다:
새로운 기능
ResponsiveMembersPage컴포넌트로 설정 모달 내용 변경버그 수정
성능 개선
기타 변경사항