-
Notifications
You must be signed in to change notification settings - Fork 1
[24.12.18 / TASK-55] Feature - 메인 페이지 제작 #5
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
Conversation
놀랍게도 현재 Next 15는 RC가 아니라는거~
+ 로그인 테스트 추가 (엣지케이스만)
괄호가 붙으면 URL로써 인식되지 않는데, 이 때문에 첫 페이지가 두개인 오류 발생
버튼과 입력 추가
+ ToastContainer 추가
- eslint 파일 정리 - 라이브러리 정리 - 이미지 형태 변경 - import 순서 정리 - 필요없는 코드 제거
개요워크스루이 풀 리퀘스트는 프로젝트의 구조와 구성을 대대적으로 개선하고 있습니다. ESLint 구성을 업데이트하고, Next.js 애플리케이션의 새로운 레이아웃과 컴포넌트를 추가하며, Sentry를 통한 오류 추적을 도입했습니다. 또한 상수, 유틸리티 함수, 훅을 재구성하여 코드의 모듈성과 유지보수성을 향상시켰습니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant User
participant App
participant Sentry
participant API
User->>App: 페이지 방문
App->>Sentry: 오류 모니터링 초기화
App->>API: 데이터 요청
alt 요청 성공
API-->>App: 데이터 반환
App->>User: 데이터 렌더링
else 요청 실패
API-->>Sentry: 오류 캡처
Sentry->>App: 오류 처리
App->>User: 오류 메시지 표시
end
시 (토끼의 관점)
Tip CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command 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 (
|
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: 21
🔭 Outside diff range comments (1)
package.json (1)
테스트 라이브러리 버전 불일치 및 의존성 관리 개선 필요
@testing-library/react
의 버전(^16.0.1)이 매우 오래된 버전이며, React 18과의 호환성 문제가 발생할 수 있습니다. 또한 다음과 같은 문제점들이 확인되었습니다:
@testing-library/react
는 현재 최신 버전이 14.x 대이며, v16은 존재하지 않는 버전입니다@testing-library/jest-dom
(^6.6.3)과@testing-library/user-event
(^14.5.2)는 상대적으로 최신 버전인데 반해, react-testing-library는 구 버전입니다- 대부분의 devDependencies가
^
를 사용하여 minor 버전 업데이트를 허용하고 있어, 예기치 않은 호환성 문제가 발생할 수 있습니다권장 조치:
@testing-library/react
를 최신 안정 버전(^14.x)으로 업데이트- 중요 테스트 도구들의 버전을 명확한 버전으로 고정 (^ 대신 정확한 버전 사용)
- package-lock.json 또는 yarn.lock 파일을 사용하여 의존성 버전 잠금 구현
🔗 Analysis chain
Line range hint
27-49
: devDependencies 버전 관리 개선 필요개발 의존성의 버전 범위가 너무 광범위하게 설정되어 있습니다. 특히 테스트 관련 패키지들의 버전을 더 엄격하게 관리하는 것이 좋습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for peer dependency warnings npm ls @testing-library/react @testing-library/jest-domLength of output: 117
Script:
#!/bin/bash # Check package.json for dependency versions and peer dependencies cat package.json | jq '.dependencies, .devDependencies, .peerDependencies' # Check if there are any package-lock.json or yarn.lock files fd -e json -e lock "package-lock.json|yarn.lock" # Look for test configuration files that might depend on these packages fd -e js -e json "jest.config|test.config"Length of output: 1379
🧹 Nitpick comments (24)
src/app/(login)/Content.tsx (1)
3-10
: 임포트 구문 개선이 필요합니다임포트 구문을 다음과 같이 그룹화하여 가독성을 향상시키는 것이 좋습니다:
- 외부 라이브러리
- Next.js 관련
- 내부 컴포넌트/유틸리티
'use client'; + // Next.js import Image from 'next/image'; import { useRouter } from 'next/navigation'; + + // External libraries import { useForm } from 'react-hook-form'; import { useMutation } from '@tanstack/react-query'; + + // Internal import { instance } from '@/api'; import { Input, Button } from '@/components'; import { NotFoundError } from '@/errors';src/utils/numberUtil.ts (1)
1-2
: 'toLocaleString' 활용하여 숫자 포매팅 개선숫자 포매팅을 위해 정규식을 사용하는 대신, JavaScript의 내장 함수인
toLocaleString()
을 사용하는 것이 가독성과 국제화 측면에서 더 유리할 수 있습니다.적용 가능한 수정 사항:
-export const parseNumber = (item: number) => - item.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); +export const parseNumber = (item: number) => + item.toLocaleString();.env.sample (1)
3-4
: 환경 변수 문서화 개선 필요Sentry 관련 환경 변수에 대한 자세한 설명과 예시를 추가하면 좋을 것 같습니다. 특히:
- 토큰을 얻는 방법
- DSN 설정 방법
- 각 변수의 형식과 용도
다음과 같이 개선하는 것을 제안합니다:
-SENTRY_AUTH_TOKEN=<'sentry auth token here'> -SENTRY_DSN=<'sentry dsn here'> +# Sentry 인증 토큰 (프로젝트 설정 > API Keys에서 생성) +SENTRY_AUTH_TOKEN=<'auth-token-format: xxx'> +# Sentry 프로젝트 DSN (프로젝트 설정 > Client Keys에서 확인) +SENTRY_DSN=<'https://xxx@xxx.ingest.sentry.io/xxx'>src/instrumentation.ts (1)
3-7
: 런타임 환경 체크 로직 개선 필요현재 구현은 기본적인 기능은 제공하지만, 다음과 같은 개선사항을 고려해보시기 바랍니다:
- 잘못된 런타임 환경값에 대한 처리
- 설정 파일 import 실패 시 에러 처리
다음과 같이 개선하는 것을 제안합니다:
export async function register() { const { NEXT_RUNTIME } = process.env; - if (NEXT_RUNTIME === 'nodejs') await import('../sentry.server.config'); - if (NEXT_RUNTIME === 'edge') await import('../sentry.edge.config'); + try { + switch (NEXT_RUNTIME) { + case 'nodejs': + await import('../sentry.server.config'); + break; + case 'edge': + await import('../sentry.edge.config'); + break; + default: + console.warn(`알 수 없는 런타임 환경: ${NEXT_RUNTIME}`); + } + } catch (error) { + console.error('Sentry 설정 로드 실패:', error); + } }tailwind.config.ts (1)
14-16
: 테마 설정 구조 개선이 필요합니다.theme 객체의 구조가 단순화되어 있어 확장성이 제한될 수 있습니다. 또한 safelist의 타입 안정성을 개선할 수 있습니다.
- theme: { colors, screens: screens }, + theme: { + extend: { + colors, + screens, + }, + }, - safelist: Object.values(sizes), + safelist: Object.values(sizes) as string[],src/components/common/QueryProvider.tsx (1)
9-10
: 에러 처리 로직 개선 제안현재 mutations의 에러 처리가 단순 메시지 표시에 그치고 있습니다. 에러 타입에 따른 세분화된 처리나 사용자 친화적인 메시지 변환을 고려해보세요.
mutations: { - onError: (err) => toast.error(`${err.message}`), + onError: (err) => { + if (err instanceof NetworkError) { + toast.error('네트워크 연결을 확인해주세요'); + } else if (err instanceof ValidationError) { + toast.error('입력값을 확인해주세요'); + } else { + toast.error('오류가 발생했습니다. 잠시 후 다시 시도해주세요'); + } + }, },src/components/common/Button.tsx (1)
21-21
: className 구성 개선 제안현재 className 문자열이 너무 길고 가독성이 떨어집니다. 별도의 유틸리티 함수나 상수로 분리하는 것을 고려해보세요.
+const getButtonClassName = (form: keyof typeof forms, size: sizeType) => { + const baseStyle = 'bg-primary-main hover:bg-primary-sub font-bold text-bg-main'; + const disabledStyle = 'disabled:bg-border-sub disabled:cursor-not-allowed'; + return `${baseStyle} ${disabledStyle} ${forms[form]} ${sizes[size]}`; +}; export const Button = ({ form = 'SMALL', size, children, ...rest }: IProp) => ( <button - className={`bg-primary-main hover:bg-primary-sub disabled:bg-border-sub disabled:cursor-not-allowed text-bg-main font-bold ${forms[form]} ${sizes[size]}`} + className={getButtonClassName(form, size)} {...rest} >src/app/global-error.tsx (1)
16-26
: 사용자 친화적인 에러 메시지 추가 검토현재는 기본 Next.js 에러 페이지만 표시되고 있습니다. 사용자에게 더 친숙한 에러 메시지나 재시도 버튼 등을 추가하는 것을 고려해보세요.
예시 구현:
return ( <html> <body> + <div style={{ padding: '20px', textAlign: 'center' }}> + <h1>죄송합니다. 문제가 발생했습니다.</h1> + <p>잠시 후 다시 시도해 주세요.</p> + <button onClick={() => window.location.reload()}> + 새로고침 + </button> + </div> <NextError statusCode={0} /> </body> </html> );src/components/auth-required/main/Summary/BarContent.tsx (1)
3-8
: 인터페이스 정의 개선 제안
increasement
속성의 타입 안전성을 높일 수 있습니다.interface IProp { title: string; content: number; - increasement?: number; + increasement?: number | null; prefix?: string; }src/components/common/Input.tsx (2)
18-21
: 스타일 상수의 분리 검토스타일 상수를 별도의 파일로 분리하여 재사용성과 유지보수성을 높일 수 있습니다.
src/constants/forms.ts
파일을 생성하여 다음과 같이 분리하는 것을 제안합니다:export const forms = { LARGE: 'p-4 h-[48px] focus:border-primary-sub rounded-sm', SMALL: 'p-2 h-[38px] focus:border-border-alt rounded-[4px]', } as const; export type FormType = keyof typeof forms;
29-32
: className 구성 방식 개선className 문자열 연결 방식을 더 관리하기 쉽게 개선할 수 있습니다.
{...rest} ref={ref} - className={`bg-bg-sub border-[1px] border-border-sub placeholder:text-text-alt text-text-main text-[16px] font-light ${forms[form]} ${sizes[size]} ${rest.className}`} + className={clsx( + 'bg-bg-sub border-[1px] border-border-sub', + 'placeholder:text-text-alt text-text-main text-[16px] font-light', + forms[form], + sizes[size], + rest.className + )}src/components/common/Icon/index.tsx (1)
24-42
: 성능 최적화를 위한 메모이제이션 추천컴포넌트가 자주 리렌더링될 수 있는 상황에서 성능을 개선할 수 있습니다.
다음과 같이 메모이제이션을 적용하는 것을 추천드립니다:
-export const Icon = ({ +export const Icon = React.memo(({ name, size, color = '#ACACAC', rotate = 'up', ...rest -}: IProp) => { +}: IProp) => { const Comp = (Icons as iconType)[name]; return ( <Comp {...rest} style={{ color }} width={size ? size : 'auto'} height={size ? size : 'auto'} className={`${rotates[rotate]} transition-all duration-300 shrink-0 ${rest.className}`} /> ); -}; +});src/components/auth-required/main/Summary/SidebarContent.tsx (1)
3-8
: Props 검증 강화 필요Props에 대한 유효성 검사가 부족합니다.
다음과 같이 Props 검증을 강화하는 것을 추천드립니다:
interface IProp { title: string; content: number; - increasement?: number; + increasement?: number | null; prefix?: string; } + +const isValidNumber = (num: number): boolean => { + return !isNaN(num) && isFinite(num) && num >= 0; +};export const SidebarContent = ({ title, content, increasement, prefix = '개', }: IProp) => { + if (!isValidNumber(content)) { + console.warn('Invalid content value provided to SidebarContent'); + return null; + } + + if (increasement !== undefined && increasement !== null && !isValidNumber(increasement)) { + console.warn('Invalid increasement value provided to SidebarContent'); + return null; + }sentry.client.config.ts (1)
27-27
: 개발 환경에서의 디버그 모드 활성화 권장개발 환경에서는 디버그 모드를 활성화하여 Sentry 설정 문제를 쉽게 파악할 수 있도록 하는 것이 좋습니다.
- debug: false, + debug: process.env.NODE_ENV !== 'production',package.json (1)
15-15
: Sentry 버전 제한 권장
^8
버전 범위는 너무 광범위합니다. 특정 버전으로 고정하거나 더 제한된 범위를 사용하는 것이 좋습니다.- "@sentry/nextjs": "^8", + "@sentry/nextjs": "~8.0.0",src/app/(auth-required)/main/page.tsx (1)
68-70
: 리스트 렌더링 최적화가 필요합니다.
datas?.map()
구문에서 옵셔널 체이닝이 불필요해 보입니다. 또한 긴 리스트의 경우 성능 최적화가 필요할 수 있습니다.-{datas?.map((i) => <Section key={i.id} {...i} />)} +{datas.map((post) => ( + <Section + key={post.id} + {...post} + /> +))}src/components/auth-required/main/Summary/index.tsx (2)
23-23
: 상태 지속성 추가를 고려해보세요.
open
상태를 localStorage에 저장하여 사용자의 선호도를 유지하는 것이 좋습니다.-const [open, setOpen] = useState(false); +const [open, setOpen] = useState(() => { + if (typeof window === 'undefined') return false; + return localStorage.getItem('summary_open') === 'true'; +}); + +const toggleOpen = () => { + setOpen(prev => { + const newState = !prev; + localStorage.setItem('summary_open', String(newState)); + return newState; + }); +};
16-22
: 컴포넌트 최적화가 필요합니다.props가 자주 변경되지 않는다면 React.memo를 사용하여 불필요한 리렌더링을 방지할 수 있습니다.
-export const Summary = ({ +export const Summary = React.memo(({ total_views, total_likes, total_posts, views, likes, -}: IProp) => { +}: IProp) => { // ... component logic -}; +});src/components/auth-required/Header.tsx (3)
11-15
: 경로 상수화 제안네비게이션 경로를 상수로 분리하여 관리하면 유지보수성이 향상될 것 같습니다.
+const ROUTES = { + MAIN: '/main', + LEADERBOARDS: '/leaderboards', + COMPARE: '/compare', +} as const; const layouts: Array<{ icon: nameType; title: string; path: string }> = [ - { icon: 'Analytics', title: '내 통계', path: '/main' }, - { icon: 'LeaderBoards', title: '리더보드', path: '/leaderboards' }, - { icon: 'Compare', title: '통계 비교', path: '/compare' }, + { icon: 'Analytics', title: '내 통계', path: ROUTES.MAIN }, + { icon: 'LeaderBoards', title: '리더보드', path: ROUTES.LEADERBOARDS }, + { icon: 'Compare', title: '통계 비교', path: ROUTES.COMPARE }, ];
17-20
: 스타일 구성 개선 제안Tailwind의 @apply 디렉티브를 사용하여 스타일을 더 깔끔하게 구성할 수 있습니다.
+// styles.css 또는 해당 컴포넌트 파일 내 +.default-nav-item { + @apply w-[180px] h-[65px] transition-all duration-300 shrink-0 max-mbi:w-[65px]; +} + +.navigate-item { + @apply gap-5 flex items-center justify-center cursor-pointer; +} -const defaultStyle = - 'w-[180px] h-[65px] transition-all duration-300 shrink-0 max-mbi:w-[65px] '; -const navigateStyle = 'gap-5 flex items-center justify-center cursor-pointer ';
32-33
: 변환 로직 분리 제안변환 계산 로직을 별도의 함수로 분리하면 가독성이 향상될 것 같습니다.
+const getTranslateX = (index: number, width: number) => { + const itemWidth = width < screens.mbi ? 65 : 180; + return `${index * itemWidth}px`; +}; style={{ - transform: `translateX(${layouts.findIndex((i) => i.path === path) * (width < screens.mbi ? 65 : 180)}px)`, + transform: `translateX(${getTranslateX(layouts.findIndex((i) => i.path === path), width)})`, }}src/components/auth-required/main/Section.tsx (3)
7-15
: 타입 정의 개선 제안더 구체적인 타입 정의를 통해 타입 안정성을 향상시킬 수 있습니다.
+type DateString = `${number}-${number}-${number}T${number}:${number}:${number}`; +type PostId = string; interface IProp { views: number; before_views: number; - date: string; + date: DateString; title: string; total_views: number; likes: number; - id: string; + id: PostId; }
30-32
: 제목 처리 로직 개선 제안제목 길이 제한과 처리 로직을 유틸리티 함수로 분리하면 좋을 것 같습니다.
+const MAX_TITLE_LENGTH = 19; + +const truncateTitle = (title: string, maxLength: number = MAX_TITLE_LENGTH) => { + if (title.length <= maxLength) return title; + return `${title.slice(0, maxLength).trim()}..`; +}; -{p.title.length > 19 ? p.title.slice(0, 19).trim() + '..' : p.title} +{truncateTitle(p.title)}
19-19
: 조회수 비교 로직 개선 제안조회수 비교 로직을 별도의 함수로 분리하고 의미있는 이름을 사용하면 좋을 것 같습니다.
+const hasViewsIncreased = (currentViews: number, previousViews: number) => { + return currentViews - previousViews > 0; +}; -const doesViewIncreased = p.views - p.before_views > 0; +const doesViewIncreased = hasViewsIncreased(p.views, p.before_views);
🛑 Comments failed to post (21)
src/app/(login)/Content.tsx (2)
49-81: 🛠️ Refactor suggestion
접근성 및 반응형 디자인 개선이 필요합니다
현재 구현에서 다음과 같은 접근성 문제가 있습니다:
- 입력 필드에 시각적으로만 보이는 플레이스홀더에 의존
- 고정된 높이(
h-[480px]
)로 인한 반응형 이슈 가능성- 스크린 리더 사용자를 위한 적절한 레이블 부재
다음과 같이 수정하는 것을 제안드립니다:
-<div className="w-[500px] h-full flex flex-col gap-[30px] items-center justify-center"> +<div className="w-[500px] h-full flex flex-col gap-[30px] items-center justify-center" role="form" aria-label="로그인 폼"> <h1 className="font-medium text-[32px] text-text-main"> Velog Dashboard </h1> + <div className="flex flex-col w-full"> + <label htmlFor="access" className="sr-only">액세스 토큰</label> <Input id="access" size="LARGE" type="password" + aria-required="true" placeholder="Access Token을 입력하세요" {...register('access_token', { required: true })} /> + </div> + <div className="flex flex-col w-full"> + <label htmlFor="refresh" className="sr-only">리프레시 토큰</label> <Input id="refresh" size="LARGE" type="password" + aria-required="true" placeholder="Refresh Token을 입력하세요" {...register('refresh_token', { required: true })} /> + </div>Committable suggestion skipped: line range outside the PR's diff.
58-71:
⚠️ Potential issue토큰 처리에 대한 보안 강화가 필요합니다
토큰 입력 처리 시 다음과 같은 보안 개선사항을 고려해주세요:
- 토큰 형식 유효성 검사
- 입력값 sanitization
- 브라우저 자동완성 비활성화
다음과 같은 수정을 제안드립니다:
<Input id="access" size="LARGE" type="password" + autoComplete="off" placeholder="Access Token을 입력하세요" - {...register('access_token', { required: true })} + {...register('access_token', { + required: true, + pattern: { + value: /^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$/, + message: '올바른 토큰 형식이 아닙니다' + }, + validate: (value) => { + const sanitized = value.trim(); + return sanitized === value || '토큰에 불필요한 공백이 포함되어 있습니다'; + } + })} />📝 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.<Input id="access" size="LARGE" type="password" autoComplete="off" placeholder="Access Token을 입력하세요" {...register('access_token', { required: true, pattern: { value: /^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$/, message: '올바른 토큰 형식이 아닙니다' }, validate: (value) => { const sanitized = value.trim(); return sanitized === value || '토큰에 불필요한 공백이 포함되어 있습니다'; } })} /> <Input id="refresh" size="LARGE" type="password" placeholder="Refresh Token을 입력하세요" {...register('refresh_token', { required: true })} />
src/api/index.ts (2)
61-64:
⚠️ Potential issue'TimeoutError' 대신 올바른 에러 이름 사용 필요
타임아웃 시 발생하는 에러의 이름이 실제로는
'AbortError'
일 수 있습니다. 정확한 에러 이름을 확인하여 예외 처리가 올바르게 동작하도록 해야 합니다.
56-71:
⚠️ Potential issue에러 객체의 타입을 확인하여 안전한 처리 필요
현재
err
를Response
또는Error
로 직접 캐스팅하고 있습니다. 하지만err
의 실제 타입이 항상 예상하는 것은 아니므로,instanceof
를 사용하여 타입을 확인한 후 처리하는 것이 좋습니다.적용 가능한 수정 사항:
try { // ... } catch (err: any) { + if (err instanceof Response) { const context = err; sentry.setContext('Request', { path: context.url, status: context.status, }); // 이후 처리 로직 + } else if (err.name === 'AbortError') { // 'TimeoutError' 대신 'AbortError' 체크 sentry.captureException(new ServerNotRespondingError()); throw new ServerNotRespondingError(); + } else { + sentry.captureException(err); + throw err; + } }Committable suggestion skipped: line range outside the PR's diff.
src/app/(auth-required)/layout.tsx (1)
4-11: 🛠️ Refactor suggestion
접근성 및 타입 안전성 개선 필요
다음 사항들을 개선하면 좋을 것 같습니다:
- 접근성 속성 추가
- children 타입 제한 완화
- Tailwind 클래스 구조화
다음과 같이 개선하는 것을 제안합니다:
-import { ReactElement } from 'react'; +import { PropsWithChildren } from 'react'; -export default function Layout({ children }: { children: ReactElement }) { +export default function Layout({ children }: PropsWithChildren) { return ( <main + role="main" + aria-label="메인 콘텐츠" className={` w-full h-full flex flex-col p-[50px_70px_70px_70px] max-tbl:p-[40px_30px] gap-[30px] max-tbl:gap-[20px] transition-all `} > <Header /> {children} </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.import { PropsWithChildren } from 'react'; export default function Layout({ children }: PropsWithChildren) { return ( <main role="main" aria-label="메인 콘텐츠" className={` w-full h-full flex flex-col p-[50px_70px_70px_70px] max-tbl:p-[40px_30px] gap-[30px] max-tbl:gap-[20px] transition-all `} > <Header /> {children} </main> ); }
src/components/QueryProvider.tsx (1)
8-11:
⚠️ Potential issuemutation 에러 처리 로직이 누락되었습니다.
기존의 mutation 에러 처리 로직이 제거되었습니다. 사용자에게 적절한 에러 피드백을 제공하기 위해 에러 처리 로직을 추가하는 것이 좋습니다.
다음과 같이 에러 처리를 추가하는 것을 제안드립니다:
defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: false, }, + mutations: { + onError: (error) => { + toast.error(error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.'); + }, + }, },Committable suggestion skipped: line range outside the PR's diff.
src/constants/colors.ts (1)
27-27: 🛠️ Refactor suggestion
타입 정의 개선이 필요합니다.
colorType
의 정의가 모든 색상 카테고리에 'alt' 값을 요구하고 있으나,primary
와destructive
는 'alt' 값이 없습니다. 타입을 더 정확하게 정의하는 것이 좋습니다.-export type colorType = Record<keyof typeof colors, 'main' | 'sub' | 'alt'>; +type ColorKeys = { + [K in keyof typeof colors]: K extends 'primary' | 'destructive' + ? 'main' | 'sub' + : 'main' | 'sub' | 'alt'; +}; +export type colorType = ColorKeys;📝 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.type ColorKeys = { [K in keyof typeof colors]: K extends 'primary' | 'destructive' ? 'main' | 'sub' : 'main' | 'sub' | 'alt'; }; export type colorType = ColorKeys;
tailwind.config.ts (1)
4-6: 🛠️ Refactor suggestion
스크린 사이즈 객체 생성 로직 개선이 필요합니다.
현재 코드는 가독성이 떨어지고 부수 효과가 있는 방식으로 구현되어 있습니다. 더 명확하고 함수형 프로그래밍 방식으로 개선하는 것이 좋습니다.
-const screens: Record<string, string> = {}; - -Object.entries(customScreen).forEach((i) => (screens[i[0]] = i[1] + 'px')); +const screens: Record<string, string> = Object.fromEntries( + Object.entries(customScreen).map(([key, value]) => [key, `${value}px`]) +);📝 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 screens: Record<string, string> = Object.fromEntries( Object.entries(customScreen).map(([key, value]) => [key, `${value}px`]) );
🧰 Tools
🪛 Biome (1.9.4)
[error] 6-6: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.(lint/suspicious/noAssignInExpressions)
sentry.server.config.ts (1)
8-8: 💡 Codebase verification
환경 변수 설정 불일치 발견
.env.sample
파일에는SENTRY_DSN
으로 정의되어 있으나, Sentry 설정 파일들에서는NEXT_PUBLIC_SENTRY_DSN
을 사용하고 있습니다. 다음 사항들을 수정해주세요:
.env.sample
파일의SENTRY_DSN
을NEXT_PUBLIC_SENTRY_DSN
로 변경- 또는 Sentry 설정 파일들의 환경 변수를
SENTRY_DSN
으로 변경🔗 Analysis chain
환경 변수 설정 확인 필요
NEXT_PUBLIC_SENTRY_DSN
환경 변수가 올바르게 설정되어 있는지 확인이 필요합니다.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # .env 파일에서 Sentry DSN 설정 확인 if [ -f ".env" ]; then grep "NEXT_PUBLIC_SENTRY_DSN" .env fi # .env.sample 파일에서 Sentry DSN 예제 확인 if [ -f ".env.sample" ]; then grep "NEXT_PUBLIC_SENTRY_DSN" .env.sample fiLength of output: 84
Script:
#!/bin/bash # Check for environment variable in any configuration or example files fd -e env -e example -e sample -e config -H | xargs grep -l "SENTRY" || true # Check for Sentry configuration files fd -e ts -e js | xargs grep -l "SENTRY" || true # Look for documentation about environment setup fd "README|CONTRIBUTING" | xargs grep -i "sentry\|env" || trueLength of output: 254
Script:
#!/bin/bash # Check the content of .env.sample file cat .env.sample # Check Sentry configuration across different environments cat sentry.client.config.ts sentry.edge.config.ts sentry.server.config.tsLength of output: 2798
src/hooks/useResponsive.ts (2)
4-5:
⚠️ Potential issue타입 정의 및 초기값 설정 개선 필요
NodeJS
타입을 사용하기 위해@types/node
를 import 해야 합니다.- 초기 너비값 1024px이 적절한지 검토가 필요합니다. 모바일 우선 접근법을 고려하면 더 작은 값으로 시작하는 것이 좋을 수 있습니다.
+ import type { Timeout } from 'node'; - let timer: undefined | NodeJS.Timeout = undefined; + let timer: undefined | Timeout = undefined; - const [width, setWidth] = useState<number>(1024); + const [width, setWidth] = useState<number>(375); // 모바일 기본 너비📝 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 type { Timeout } from 'node'; let timer: undefined | Timeout = undefined; const [width, setWidth] = useState<number>(375); // 모바일 기본 너비
🧰 Tools
🪛 eslint
[error] 4-4: 'NodeJS' is not defined.
(no-undef)
7-21: 🛠️ Refactor suggestion
타이머 관리 로직 개선 필요
timer
변수를 컴포넌트 외부에서 선언하면 여러 컴포넌트 인스턴스 간에 공유될 수 있습니다. useRef를 사용하여 컴포넌트 인스턴스별로 관리하는 것이 좋습니다.export const useResponsive = (): number => { - let timer: undefined | NodeJS.Timeout = undefined; + const timerRef = useRef<Timeout>(); const [width, setWidth] = useState<number>(375); useEffect(() => { if (typeof window !== 'undefined') { setWidth(window.innerWidth); const handleResize = () => { - clearTimeout(timer); - timer = setTimeout(() => { + if (timerRef.current) clearTimeout(timerRef.current); + timerRef.current = setTimeout(() => { setWidth(window.innerWidth); }, 80); }; window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); + return () => { + window.removeEventListener('resize', handleResize); + if (timerRef.current) clearTimeout(timerRef.current); + }; } else { return () => window.removeEventListener('resize', () => null); } }, []);📝 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.useEffect(() => { if (typeof window !== 'undefined') { setWidth(window.innerWidth); const handleResize = () => { if (timerRef.current) clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { setWidth(window.innerWidth); }, 80); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); if (timerRef.current) clearTimeout(timerRef.current); }; } else { return () => window.removeEventListener('resize', () => null); } }, []);
src/components/auth-required/main/Summary/BarContent.tsx (1)
19-29: 🛠️ Refactor suggestion
접근성 개선이 필요합니다
시맨틱 마크업과 ARIA 속성을 추가하여 접근성을 향상시킬 수 있습니다.
return ( - <div className="w-full flex justify-between items-center"> + <div className="w-full flex justify-between items-center" role="group" aria-label={`${title} 통계`}> <span className="text-[15px] font-medium text-text-alt">{title}</span> <span className={`flex items-center font-bold text-text-main text-[25px] ${increasement ? afterContent : ''}`} data-increasement={parseNumber(increasement ? increasement : 0)} + aria-label={`${parseNumber(content)}${prefix}${increasement ? `, ${parseNumber(increasement)} 증가` : ''}`} > {parseNumber(content) + prefix} </span> </div> );📝 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.return ( <div className="w-full flex justify-between items-center" role="group" aria-label={`${title} 통계`}> <span className="text-[15px] font-medium text-text-alt">{title}</span> <span className={`flex items-center font-bold text-text-main text-[25px] ${increasement ? afterContent : ''}`} data-increasement={parseNumber(increasement ? increasement : 0)} aria-label={`${parseNumber(content)}${prefix}${increasement ? `, ${parseNumber(increasement)} 증가` : ''}`} > {parseNumber(content) + prefix} </span> </div> );
tsconfig.json (1)
2-9: 💡 Codebase verification
eslint-import-resolver-typescript 패키지가 누락되었습니다
package.json의 의존성을 확인한 결과, TypeScript resolver 설정에 필요한
eslint-import-resolver-typescript
패키지가 설치되어 있지 않습니다. 다음 작업이 필요합니다:
eslint-import-resolver-typescript
패키지를 devDependencies에 추가해야 합니다- 설치 명령어:
npm install --save-dev eslint-import-resolver-typescript
🔗 Analysis chain
import/resolver 설정 검증 필요
TypeScript resolver가 올바르게 설치되어 있는지 확인이 필요합니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # TypeScript resolver 설치 여부 확인 # package.json에서 필요한 의존성 확인 jq '.dependencies + .devDependencies | select(has("eslint-import-resolver-typescript"))' package.json # ESLint 설정에서 resolver 설정 확인 if [ -f ".eslintrc.json" ]; then jq '.settings["import/resolver"]' .eslintrc.json elif [ -f ".eslintrc.js" ]; then cat .eslintrc.js | grep -A 5 "import/resolver" fiLength of output: 162
Script:
#!/bin/bash # 1. package.json 전체 내용 확인 echo "=== Package.json Dependencies ===" cat package.json | jq '.dependencies, .devDependencies' # 2. ESLint 관련 설정 파일 검색 echo -e "\n=== ESLint Config Files ===" fd -e js -e json -e yaml -e yml -g '*eslint*' # 3. tsconfig.json 전체 내용 확인 echo -e "\n=== tsconfig.json Content ===" cat tsconfig.jsonLength of output: 2485
src/components/auth-required/main/Summary/SidebarContent.tsx (1)
10-11: 🛠️ Refactor suggestion
CSS 구현 및 접근성 개선 필요
현재 CSS 구현과 접근성 측면에서 개선이 필요합니다:
- CSS after 콘텐츠를 aria-label로 보완
- 증가량 표시를 위한 별도의 span 엘리먼트 사용
다음과 같이 개선하는 것을 추천드립니다:
-const afterContent = - 'after:text-primary-sub after:content-[attr(data-increasement)_"↑"] after:ml-2 after:text-[25px] max-tbl:after:text-[20px] transition-all'; +const incrementClass = 'text-primary-sub ml-2 text-[25px] max-tbl:text-[20px] transition-all';<span - className={`flex items-center text-text-main font-bold text-[35px] transition-all ${increasement ? afterContent : ''} max-tbl:text-[30px] max-mbi:text-[25px]`} - data-increasement={parseNumber(increasement ? increasement : 0)} + className="flex items-center text-text-main font-bold text-[35px] transition-all max-tbl:text-[30px] max-mbi:text-[25px]" + aria-label={`${parseNumber(content)}${prefix}${increasement ? `, ${parseNumber(increasement)} 증가` : ''}`} > {parseNumber(content) + prefix} + {increasement && ( + <span className={incrementClass}> + {parseNumber(increasement)}↑ + </span> + )} </span>Committable suggestion skipped: line range outside the PR's diff.
src/utils/eventTracker.tsx (3)
31-33:
⚠️ Potential issue데이터 직렬화 오류 수정 필요
data.current.toString()
은 객체를 올바르게 직렬화하지 않습니다. JSON.stringify를 사용하여 데이터를 올바르게 직렬화해야 합니다.navigator.sendBeacon( `${process.env.NEXT_PUBLIC_BASE_URL}/stay`, - data.current.toString(), + JSON.stringify(data.current), );📝 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.`${process.env.NEXT_PUBLIC_BASE_URL}/stay`, JSON.stringify(data.current), );
13-16:
⚠️ Potential issue에러 처리 추가 필요
API 호출에 대한 에러 처리가 누락되어 있습니다. try-catch 블록을 추가하여 에러를 적절히 처리해야 합니다.
export const trackUserEvent = (event_type: messages) => { const { pathname: path } = window.location; - instance('/event', { body: { path, event_type }, method: 'POST' }); + try { + instance('/event', { body: { path, event_type }, method: 'POST' }); + } catch (error) { + console.error('이벤트 추적 중 오류 발생:', 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 const trackUserEvent = (event_type: messages) => { const { pathname: path } = window.location; try { instance('/event', { body: { path, event_type }, method: 'POST' }); } catch (error) { console.error('이벤트 추적 중 오류 발생:', error); } };
30-34: 🛠️ Refactor suggestion
sendBeacon API 폴백 처리 필요
일부 브라우저에서는
sendBeacon
API를 지원하지 않을 수 있습니다. 폴백 메커니즘을 추가하는 것이 좋습니다.- navigator.sendBeacon( - `${process.env.NEXT_PUBLIC_BASE_URL}/stay`, - data.current.toString(), - ); + const url = `${process.env.NEXT_PUBLIC_BASE_URL}/stay`; + const payload = JSON.stringify(data.current); + if (navigator.sendBeacon) { + navigator.sendBeacon(url, payload); + } else { + fetch(url, { + method: 'POST', + body: payload, + keepalive: true + }); + }📝 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 url = `${process.env.NEXT_PUBLIC_BASE_URL}/stay`; const payload = JSON.stringify(data.current); if (navigator.sendBeacon) { navigator.sendBeacon(url, payload); } else { fetch(url, { method: 'POST', body: payload, keepalive: true }); } };
src/app/(auth-required)/main/page.tsx (2)
30-30:
⚠️ Potential issue제목 길이 제한이 필요합니다.
"아파트 아파트, 아파트 아파트, 아파트 아파트, 어, 어허 어허" 와 같이 긴 제목이 UI를 깨뜨릴 수 있습니다. 제목 표시에 대한 처리가 필요합니다.
-title: '아파트 아파트, 아파트 아파트, 아파트 아파트, 어, 어허 어허', +title: '아파트 아파트, 아파트 아파트, 아파트 아파트...',Committable suggestion skipped: line range outside the PR's diff.
9-56: 🛠️ Refactor suggestion
데이터 구조를 별도의 파일로 분리하는 것이 좋습니다.
현재 컴포넌트 파일 내에 하드코딩된 mock 데이터가 있습니다. 다음과 같은 개선사항을 제안드립니다:
- 데이터 타입 정의 추가
- mock 데이터를 별도의 파일로 분리
- 실제 API 연동 시 쉽게 교체할 수 있는 구조로 변경
+// src/types/dashboard.ts +export interface Post { + id: string; + title: string; + date: string; + total_views: number; + before_views: number; + views: number; + likes: number; +} +// src/mocks/dashboard.ts +import type { Post } from '@/types/dashboard'; + +export const mockPosts: Post[] = [ { id: '129036-123512-590731-048113', title: '2024 스탠다드 회고록', // ... 나머지 데이터 }, ];Committable suggestion skipped: line range outside the PR's diff.
src/components/auth-required/main/Summary/index.tsx (1)
42-48:
⚠️ Potential issue접근성 개선이 필요합니다.
클릭 가능한 요소에 대한 접근성 속성이 누락되었습니다. 또한 키보드 접근성도 고려해야 합니다.
<section className={`mbi:hidden flex flex-col w-full bg-bg-sub rounded-[4px] cursor-pointer`} onClick={() => setOpen((prev) => !prev)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setOpen((prev) => !prev); + } + }} + aria-expanded={open} + aria-label={`요약 정보 ${open ? '펼치기' : '접기'}`} >📝 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.className={`mbi:hidden flex flex-col w-full bg-bg-sub rounded-[4px] cursor-pointer`} onClick={() => setOpen((prev) => !prev)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen((prev) => !prev); } }} aria-expanded={open} aria-label={`요약 정보 ${open ? '펼치기' : '접기'}`} > <div className="text-text-alt flex items-center justify-center text-[15px] w-full h-[52px] gap-[10px] font-medium shrink-0"> <span>요약 정보 {open ? '펼치기' : '접기'}</span> <Icon name="Arrow" size={17} rotate={open ? 'up' : 'down'} /> </div>
src/components/auth-required/Header.tsx (1)
50-59:
⚠️ Potential issue접근성 및 설정 개선 필요
- 프로필 이미지에 대한 적절한 대체 텍스트가 필요합니다.
- 프로필 이미지 경로를 환경 설정으로 관리하는 것이 좋습니다.
+const DEFAULT_PROFILE_IMAGE = process.env.NEXT_PUBLIC_DEFAULT_PROFILE_IMAGE || '/profile.jpg'; <Image width={35} height={35} className="rounded-full" - src="/profile.jpg" - alt="" + src={DEFAULT_PROFILE_IMAGE} + alt="사용자 프로필 이미지" />Committable suggestion skipped: line range outside the PR's diff.
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: 0
🔭 Outside diff range comments (2)
src/app/layout.tsx (1)
Line range hint
31-42
: 중복된 ErrorBoundary와 QueryProvider를 제거해야 합니다!심각한 문제가 발견되었습니다:
- ErrorBoundary와 QueryProvider가 중복되어 있어 동일한 컴포넌트가 두 번 렌더링됩니다
- 이로 인해 상태 관리 문제와 API 호출이 중복될 수 있습니다
다음과 같이 수정해주세요:
<body className={`${NotoSansKr.className} w-full h-screen bg-bg-main`}> <sentry.ErrorBoundary> <QueryProvider> <ToastContainer autoClose={2000} /> {children} </QueryProvider> </sentry.ErrorBoundary> - <sentry.ErrorBoundary> - <QueryProvider> - <ToastContainer autoClose={2000} /> - {children} - </QueryProvider> - </sentry.ErrorBoundary> </body>src/api/index.ts (1)
Line range hint
8-15
: 환경 변수 검증 로직 개선 필요환경 변수 검증 로직이 런타임에서 수행되고 있습니다. 타입 안전성을 높이기 위해 다음과 같은 개선을 제안합니다:
+const validateEnv = () => { + if (Number.isNaN(ABORT_MS)) { + throw new Error('ABORT_MS가 ENV에서 설정되지 않았습니다'); + } + + if (!BASE_URL) { + throw new Error('BASE_URL이 ENV에서 설정되지 않았습니다.'); + } +}; + +validateEnv(); -if (Number.isNaN(ABORT_MS)) { - throw new Error('ABORT_MS가 ENV에서 설정되지 않았습니다'); -} - -if (!BASE_URL) { - throw new Error('BASE_URL이 ENV에서 설정되지 않았습니다.'); -}
🧹 Nitpick comments (2)
src/api/index.ts (2)
Line range hint
44-71
: 에러 처리 로직 단순화 필요현재 에러 처리 로직이 다소 복잡합니다. 다음과 같은 개선을 제안합니다:
- 에러 타입 명확화
- 에러 처리 로직 모듈화
- 상수로 에러 메시지 분리
+const ERROR_MESSAGES = { + SERVER_ERROR: (name: string) => `서버에서 예기치 않은 오류가 발생했습니다. (${name})`, +}; + +const handleApiError = (err: unknown, error?: ErrorObject) => { + const context = err as Response; + sentry.setContext('Request', { + path: context.url, + status: context.status, + }); + + if ((err as Error).name === 'TimeoutError') { + const timeoutError = new ServerNotRespondingError(); + sentry.captureException(timeoutError); + throw timeoutError; + } + + if (!error?.[`${(err as Response).status}`]) { + const serverError = new Error(ERROR_MESSAGES.SERVER_ERROR(err.name)); + sentry.captureException(serverError); + throw serverError; + } + + const customError = error[`${(err as Response).status}`]; + sentry.captureException(customError); + throw customError; +};
Line range hint
18-18
: 타입 정의 개선 필요
ErrorObject
타입이 너무 일반적입니다. 구체적인 에러 타입을 정의하여 타입 안전성을 높이는 것이 좋습니다.type ApiErrorStatus = 400 | 401 | 403 | 404 | 500; type ApiError = Error & { status: ApiErrorStatus }; type ErrorObject = Record<ApiErrorStatus, ApiError>;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
next.config.mjs
(1 hunks)src/api/index.ts
(1 hunks)src/app/layout.tsx
(3 hunks)
🔇 Additional comments (5)
src/app/layout.tsx (1)
26-26
: 타입 서명 변경이 잘 되었습니다!
React.ReactNode에서 ReactNode로의 단순화는 좋은 개선입니다.
next.config.mjs (3)
7-16
: SVG 처리를 위한 웹팩 설정이 적절함
SVG 파일 처리를 위한 웹팩 설정이 올바르게 구성되어 있습니다. @svgr/webpack
을 사용한 접근 방식이 적절합니다.
3-4
:
React Strict Mode 비활성화에 대한 재고 필요
Strict Mode를 비활성화하면 개발 중에 잠재적인 문제를 조기에 발견하기 어려울 수 있습니다. 특별한 이유가 없다면 활성화 상태를 유지하는 것이 좋습니다.
5-5
: 실험적 기능 사용에 대한 검토 필요
forceSwcTransforms
를 활성화하면 성능이 향상될 수 있지만, 예기치 않은 동작이 발생할 수 있습니다. 프로덕션 환경에서 충분한 테스트가 필요합니다.
✅ Verification successful
Let me analyze the results. The search shows:
- The
forceSwcTransforms
is only used innext.config.mjs
- SWC-related entries in
pnpm-lock.yaml
are mostly dependencies and platform-specific packages - No error logs or issues related to SWC transforms were found
- Next.js version 14.2.18 is being used, which has stable SWC support
Given this information, I can now provide the final response:
SWC 변환 기능은 안전하게 사용 가능합니다
Next.js 14.2.18 버전에서는 SWC 변환이 안정적으로 지원되며, 프로덕션 환경에서도 안전하게 사용할 수 있습니다. 코드베이스에서 SWC 관련 문제점이 발견되지 않았습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if there are any SWC related issues in the codebase
rg -i "swc|transform" -g "!{node_modules,build,dist}/*"
Length of output: 18783
src/api/index.ts (1)
2-2
: Sentry 통합이 적절히 구현됨
에러 추적을 위한 Sentry 통합이 잘 구현되어 있습니다.
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.
좋았던 점
- 전체적으로 타입에 대한 신경이 많이 보여서 좋았습니다.
- Props 도 interface 로 각 컴포넌트 덩어리마다 적절하게 정의된 것 같아요!
- API 랑 합쳐진걸 빨리 보고 싶어요 흫ㅎㅎ곻긓
아쉬운 점
- syntax 에 있어서 "상수" 들은 대문자로 가도 괜찮을 것 같아요
- 예를 들어
const form = ...
const layout: Array[......] = ...
- 이런 친구들은 절대상수값들인데 FORM / LAYOUT 으로 처리해도 괜찮지 않을까요?
- 오히려 다른 "변수" 랑 한 번에 구분되어서 좋을 것 같아요.
- 예를 들어
- slack 에서 공유드린 API 호출 부를 점진적으로 따로 분리하는게 좋지 않을까 합니다!
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: 4
🔭 Outside diff range comments (1)
src/components/common/Button.tsx (1)
Line range hint
19-26
: 컴포넌트 구현 개선사항이 있습니다.className 문자열이 너무 길고 가독성이 떨어집니다. 또한 재사용 가능한 스타일들이 인라인으로 정의되어 있습니다.
다음과 같이 개선해보세요:
+const BASE_STYLES = 'bg-primary-main hover:bg-primary-sub disabled:bg-border-sub disabled:cursor-not-allowed text-bg-main font-bold'; export const Button = ({ form = 'SMALL', size, children, ...rest }: IProp) => ( <button - className={`bg-primary-main hover:bg-primary-sub disabled:bg-border-sub disabled:cursor-not-allowed text-bg-main font-bold ${FORMS[form]} ${SIZES[size]}`} + className={`${BASE_STYLES} ${FORMS[form]} ${SIZES[size]}`} {...rest} > {children} </button> );
🧹 Nitpick comments (8)
src/components/common/Button.tsx (2)
4-11
: 인터페이스 문서화가 필요합니다.인터페이스의 각 속성에 대한 JSDoc 문서화를 추가하면 컴포넌트 사용자들이 더 쉽게 이해할 수 있을 것 같습니다.
다음과 같이 문서화를 추가해보세요:
interface IProp extends DetailedHTMLProps< ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement > { + /** 버튼의 형태를 지정합니다. */ form?: keyof typeof FORMS; + /** 버튼의 크기를 지정합니다. */ size: sizeType; }
14-17
: 스타일 상수의 관리 방식 개선이 필요합니다.현재 Tailwind CSS 클래스들이 직접 문자열로 관리되고 있어 유지보수가 어려울 수 있습니다.
다음과 같이 개별 스타일 속성별로 분리하여 관리하는 것을 고려해보세요:
-const FORMS = { - LARGE: 'h-[55px] rounded-sm', - SMALL: 'pl-[20px] pr-[20px] w-fit h-8 rounded-[4px]', -}; +const FORMS = { + LARGE: { + height: 'h-[55px]', + rounded: 'rounded-sm', + }, + SMALL: { + padding: 'pl-[20px] pr-[20px]', + size: 'w-fit h-8', + rounded: 'rounded-[4px]', + }, +} as const;src/constants/screens.ts (1)
1-5
: 타입 안전성과 상수 이름을 개선하세요다음과 같은 개선사항을 제안합니다:
- 타입 안전성을 위해 타입 정의를 추가하세요
- 상수 이름을 더 명확하게 작성하세요 (DSK, TBL, MBI 대신 DESKTOP, TABLET, MOBILE)
+ type ScreenSizes = { + DESKTOP: number; + TABLET: number; + MOBILE: number; + }; + - export const SCREENS = { + export const SCREENS: ScreenSizes = { - DSK: 1512, - TBL: 1194, - MBI: 834, + DESKTOP: 1512, + TABLET: 1194, + MOBILE: 834, };tailwind.config.ts (1)
14-16
: safelist 속성의 용도를 문서화하세요SIZES 값들을 safelist에 추가한 이유와 용도를 주석으로 설명하면 좋을 것 같습니다.
theme: { colors: COLORS, screens: SCREENS }, plugins: [], + // SIZES의 모든 값을 safelist에 추가하여 동적으로 생성되는 클래스들이 제거되지 않도록 보장합니다 safelist: Object.values(SIZES),
src/app/(auth-required)/main/page.tsx (3)
4-7
: 메타데이터 설명을 더 구체적으로 개선하세요SEO 최적화를 위해 description에 더 구체적인 정보를 포함하는 것이 좋습니다. 예를 들어, 어떤 종류의 통계를 볼 수 있는지 명시하면 좋을 것 같습니다.
export const metadata: Metadata = { title: '대시보드', - description: '각종 Velog 통계를 볼 수 있는 대시보드', + description: 'Velog 포스트의 조회수, 좋아요, 게시물 수 등 다양한 통계를 한눈에 볼 수 있는 대시보드', };
9-9
: 변수 명명 규칙을 개선해주세요
datas
는 문법적으로 올바르지 않습니다.data
또는posts
로 변경하는 것이 좋습니다.- map 함수의 파라미터
i
는 의미를 알 수 없습니다.post
등 더 명확한 이름을 사용해주세요.-const datas = [ +const posts = [ ... -{datas?.map((i) => <Section key={i.id} {...i} />)} +{posts?.map((post) => <Section key={post.id} {...post} />)}Also applies to: 69-69
58-73
: 컴포넌트 구조 개선이 필요합니다
- Summary 컴포넌트의 하드코딩된 값들을 실제 데이터로 교체해야 합니다.
- 복잡한 Tailwind 클래스들을 상수로 분리하거나 컴포넌트로 추상화하는 것이 좋습니다.
- 로딩 상태와 에러 처리가 누락되어 있습니다.
추가로 다음 사항들도 고려해보세요:
- 데이터 페치 로직 추가
- 스켈레톤 UI 구현
- 에러 바운더리 적용
src/components/auth-required/Header.tsx (1)
11-15
: 네비게이션 설정을 상수 파일로 분리하세요현재 하드코딩된 네비게이션 항목들을 별도의 상수 파일로 분리하고, 경로도 상수화하는 것이 좋습니다.
+// src/constants/navigation.ts +export const ROUTES = { + MAIN: '/main', + LEADERBOARDS: '/leaderboards', + COMPARE: '/compare', +} as const; + +export const NAVIGATION_ITEMS = [ + { icon: 'Analytics' as const, title: '내 통계', path: ROUTES.MAIN }, + { icon: 'LeaderBoards' as const, title: '리더보드', path: ROUTES.LEADERBOARDS }, + { icon: 'Compare' as const, title: '통계 비교', path: ROUTES.COMPARE }, +] as const;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
src/api/index.ts
(2 hunks)src/app/(auth-required)/layout.tsx
(1 hunks)src/app/(auth-required)/main/page.tsx
(1 hunks)src/app/layout.tsx
(2 hunks)src/components/auth-required/Header.tsx
(1 hunks)src/components/auth-required/main/Section.tsx
(1 hunks)src/components/auth-required/main/Summary/SidebarContent.tsx
(1 hunks)src/components/auth-required/main/Summary/index.tsx
(1 hunks)src/components/common/Button.tsx
(1 hunks)src/components/common/Input.tsx
(2 hunks)src/constants/colors.ts
(1 hunks)src/constants/screens.ts
(1 hunks)src/constants/sizes.ts
(1 hunks)tailwind.config.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
- src/constants/sizes.ts
- src/constants/colors.ts
- src/app/(auth-required)/layout.tsx
- src/components/auth-required/main/Summary/SidebarContent.tsx
- src/components/auth-required/main/Section.tsx
- src/components/auth-required/main/Summary/index.tsx
- src/components/common/Input.tsx
- src/app/layout.tsx
- src/api/index.ts
🧰 Additional context used
🪛 Biome (1.9.4)
tailwind.config.ts
[error] 6-6: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
🔇 Additional comments (1)
src/components/common/Button.tsx (1)
1-3
: 상수 관리 방식이 개선되었습니다!
사이즈 관련 상수들을 별도의 파일로 분리하여 중앙 집중식으로 관리하는 것은 좋은 접근 방식입니다. 이는 유지보수성을 향상시키고 일관성 있는 스타일 관리를 가능하게 합니다.
const SCREENS: Record<string, string> = {}; | ||
|
||
Object.entries(CUSTOMSCREENS).forEach((i) => (SCREENS[i[0]] = i[1] + 'px')); |
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
타입 정의를 개선하고 더 명확한 객체 생성 패턴을 사용하세요
SCREENS 객체의 타입 정의가 너무 광범위하며, 객체 생성 패턴을 개선할 수 있습니다.
- const SCREENS: Record<string, string> = {};
-
- Object.entries(CUSTOMSCREENS).forEach((i) => (SCREENS[i[0]] = i[1] + 'px'));
+ type ScreenConfig = {
+ [K in keyof typeof CUSTOMSCREENS]: `${typeof CUSTOMSCREENS[K]}px`;
+ };
+
+ const SCREENS: ScreenConfig = Object.fromEntries(
+ Object.entries(CUSTOMSCREENS).map(([key, value]) => [key, `${value}px`])
+ ) as ScreenConfig;
📝 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 SCREENS: Record<string, string> = {}; | |
Object.entries(CUSTOMSCREENS).forEach((i) => (SCREENS[i[0]] = i[1] + 'px')); | |
type ScreenConfig = { | |
[K in keyof typeof CUSTOMSCREENS]: `${typeof CUSTOMSCREENS[K]}px`; | |
}; | |
const SCREENS: ScreenConfig = Object.fromEntries( | |
Object.entries(CUSTOMSCREENS).map(([key, value]) => [key, `${value}px`]) | |
) as ScreenConfig; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 6-6: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
const datas = [ | ||
{ | ||
id: '129036-123512-590731-048113', | ||
title: '2024 스탠다드 회고록', | ||
date: '2024-12-15T13:06.16.325Z', | ||
total_views: 40234, | ||
before_views: 10, | ||
views: 31, | ||
likes: 200, | ||
}, | ||
{ | ||
id: '693518-067013-681903-406913', | ||
title: 'React의 내부 구조에 대해...', | ||
date: '2024-12-01T03:36.01.112Z', | ||
total_views: 1032, | ||
before_views: 30, | ||
views: 12, | ||
likes: 140, | ||
}, | ||
{ | ||
id: '050213-601471-693861-106831', | ||
title: '아파트 아파트, 아파트 아파트, 아파트 아파트, 어, 어허 어허', | ||
date: '2024-11-30T03:36.01.112Z', | ||
total_views: 1032, | ||
before_views: 30, | ||
views: 12, | ||
likes: 140, | ||
}, | ||
{ | ||
id: '692961-701396-070918-185602', | ||
title: 'Tree shaking이란 무엇일까?', | ||
date: '2024-11-29T03:36.01.112Z', | ||
total_views: 1032, | ||
before_views: 30, | ||
views: 12, | ||
likes: 140, | ||
}, | ||
{ | ||
id: '105321-691031-601301-681031', | ||
title: | ||
'"use client"가 CSR이라고 알고 있는 거 아니죠? Next.js 면접질문 정복', | ||
date: '2024-11-28T03:36.01.112Z', | ||
total_views: 1032, | ||
before_views: 30, | ||
views: 12, | ||
likes: 140, | ||
}, | ||
]; |
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
데이터 관리 방식 개선이 필요합니다
다음과 같은 개선사항을 제안드립니다:
- 하드코딩된 데이터를 별도의 파일로 분리
- ISO 8601 날짜 형식의 오타 수정 (
T13:06.16.325Z
→T13:06:16.325Z
) - 긴 제목에 대한 말줄임표 처리 로직 추가 필요
-const datas = [ ... ]
+import { dashboardData } from '@/mocks/dashboard';
+
+const datas = dashboardData;
Committable suggestion skipped: line range outside the PR's diff.
const defaultStyle = | ||
'w-[180px] h-[65px] transition-all duration-300 shrink-0 max-MBI:w-[65px] '; | ||
const navigateStyle = 'gap-5 flex items-center justify-center cursor-pointer '; | ||
|
||
export const Header = () => { | ||
const width = useResponsive(); | ||
const path = usePathname(); | ||
const textStyle = (currentPath: string) => | ||
`${currentPath === path ? 'text-text-main' : 'text-text-alt'} text-[20px] shrink-0 transition-all duration-300 max-TBL:text-[18px] max-MBI:hidden `; |
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
반응형 디자인 구현을 개선하세요
- 매직 넘버(180px, 65px 등)를 상수로 분리하세요.
- 복잡한 조건부 스타일링을 유틸리티 함수로 분리하세요.
- 브레이크포인트 값들을 중앙화된 설정으로 관리하세요.
+const HEADER_SIZES = {
+ DESKTOP: 180,
+ MOBILE: 65,
+} as const;
+
+const getNavigationWidth = (isMobile: boolean) =>
+ isMobile ? HEADER_SIZES.MOBILE : HEADER_SIZES.DESKTOP;
Committable suggestion skipped: line range outside the PR's diff.
<div className={defaultStyle + navigateStyle}> | ||
<Image | ||
width={35} | ||
height={35} | ||
className="rounded-full" | ||
src="/profile.jpg" | ||
alt="" | ||
/> | ||
<span className={textStyle('username')}>스탠다드</span> | ||
</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.
프로필 섹션의 접근성과 데이터 관리를 개선하세요
- 프로필 이미지의 alt 텍스트가 비어있습니다.
- 프로필 이미지 경로가 하드코딩되어 있습니다.
- 사용자 이름이 하드코딩되어 있습니다.
<Image
width={35}
height={35}
className="rounded-full"
src="/profile.jpg"
- alt=""
+ alt={`${username}님의 프로필 사진`}
/>
-<span className={textStyle('username')}>스탠다드</span>
+<span className={textStyle('username')}>{username}</span>
Committable suggestion skipped: line range outside the PR's diff.
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.
잘봤습니다!
좋았던 점
- 컴포넌트 구조가 일관되고 재사용 가능하게 설계되어 좋은 거 같습니다!
메인 페이지를 제작하였습니다!
Summary by CodeRabbit
새로운 기능
useResponsive
가 도입되었습니다.Header
,Section
,Summary
,BarContent
,SidebarContent
가 추가되었습니다.COLORS
,SIZES
,SCREENS
가 도입되었습니다.버그 수정
문서화
구성 변경
종속성 업데이트