Skip to content

Conversation

six-standard
Copy link
Member

@six-standard six-standard commented Dec 17, 2024

Capture-2024-12-18-001301

메인 페이지를 제작하였습니다!

Summary by CodeRabbit

  • 새로운 기능

    • Sentry를 통한 오류 추적 기능이 추가되었습니다.
    • 사용자 이벤트 추적을 위한 새로운 기능이 도입되었습니다.
    • 대시보드 페이지 및 레이아웃 구성 요소가 추가되었습니다.
    • 반응형 디자인을 위한 새로운 훅 useResponsive가 도입되었습니다.
    • 새로운 구성 요소 Header, Section, Summary, BarContent, SidebarContent가 추가되었습니다.
    • 색상 및 크기 관련 상수 COLORS, SIZES, SCREENS가 도입되었습니다.
  • 버그 수정

    • 오류 처리 및 상태 관리를 개선했습니다.
  • 문서화

    • 구성 파일 및 상수에 대한 설명이 추가되었습니다.
  • 구성 변경

    • Tailwind CSS 구성 및 TypeScript 설정이 업데이트되었습니다.
  • 종속성 업데이트

    • 여러 패키지가 추가 및 제거되었습니다.

놀랍게도 현재 Next 15는 RC가 아니라는거~
+ 로그인 테스트 추가 (엣지케이스만)
괄호가 붙으면 URL로써 인식되지 않는데, 이 때문에 첫 페이지가 두개인 오류 발생
+ ToastContainer 추가
Copy link

Copy link

coderabbitai bot commented Dec 17, 2024

개요

워크스루

이 풀 리퀘스트는 프로젝트의 구조와 구성을 대대적으로 개선하고 있습니다. ESLint 구성을 업데이트하고, Next.js 애플리케이션의 새로운 레이아웃과 컴포넌트를 추가하며, Sentry를 통한 오류 추적을 도입했습니다. 또한 상수, 유틸리티 함수, 훅을 재구성하여 코드의 모듈성과 유지보수성을 향상시켰습니다.

변경 사항

파일 주요 변경 사항
.eslintrc.json 삭제됨
eslint.config.mjs ESLint 구성 업데이트, 새로운 플러그인 및 규칙 추가
next.config.mjs Sentry 통합, Webpack SVG 처리 추가
package.json 의존성 업데이트 및 재구성
src/app/(auth-required)/* 새로운 레이아웃 및 페이지 컴포넌트 추가
src/components/* 새로운 컴포넌트 및 유틸리티 추가
src/constants/* 색상, 크기, 화면 상수 추가

시퀀스 다이어그램

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
Loading

시 (토끼의 관점)

코드의 숲에 새 바람이 불어와
ESLint와 Sentry가 춤을 추네
컴포넌트들은 반짝이고
상수들은 노래하며
우리의 앱은 더욱 강해지네! 🐰✨

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. We would love to hear your feedback on Discord.


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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-dom

Length 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: 임포트 구문 개선이 필요합니다

임포트 구문을 다음과 같이 그룹화하여 가독성을 향상시키는 것이 좋습니다:

  1. 외부 라이브러리
  2. Next.js 관련
  3. 내부 컴포넌트/유틸리티
'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

접근성 및 반응형 디자인 개선이 필요합니다

현재 구현에서 다음과 같은 접근성 문제가 있습니다:

  1. 입력 필드에 시각적으로만 보이는 플레이스홀더에 의존
  2. 고정된 높이(h-[480px])로 인한 반응형 이슈 가능성
  3. 스크린 리더 사용자를 위한 적절한 레이블 부재

다음과 같이 수정하는 것을 제안드립니다:

-<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

토큰 처리에 대한 보안 강화가 필요합니다

토큰 입력 처리 시 다음과 같은 보안 개선사항을 고려해주세요:

  1. 토큰 형식 유효성 검사
  2. 입력값 sanitization
  3. 브라우저 자동완성 비활성화

다음과 같은 수정을 제안드립니다:

 <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

에러 객체의 타입을 확인하여 안전한 처리 필요

현재 errResponse 또는 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

접근성 및 타입 안전성 개선 필요

다음 사항들을 개선하면 좋을 것 같습니다:

  1. 접근성 속성 추가
  2. children 타입 제한 완화
  3. 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 issue

mutation 에러 처리 로직이 누락되었습니다.

기존의 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' 값을 요구하고 있으나, primarydestructive는 '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_DSNNEXT_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
fi

Length 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" || true

Length 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.ts

Length of output: 2798

src/hooks/useResponsive.ts (2)

4-5: ⚠️ Potential issue

타입 정의 및 초기값 설정 개선 필요

  1. NodeJS 타입을 사용하기 위해 @types/node를 import 해야 합니다.
  2. 초기 너비값 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"
fi

Length 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.json

Length of output: 2485

src/components/auth-required/main/Summary/SidebarContent.tsx (1)

10-11: 🛠️ Refactor suggestion

CSS 구현 및 접근성 개선 필요

현재 CSS 구현과 접근성 측면에서 개선이 필요합니다:

  1. CSS after 콘텐츠를 aria-label로 보완
  2. 증가량 표시를 위한 별도의 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 데이터가 있습니다. 다음과 같은 개선사항을 제안드립니다:

  1. 데이터 타입 정의 추가
  2. mock 데이터를 별도의 파일로 분리
  3. 실제 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

접근성 및 설정 개선 필요

  1. 프로필 이미지에 대한 적절한 대체 텍스트가 필요합니다.
  2. 프로필 이미지 경로를 환경 설정으로 관리하는 것이 좋습니다.
+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.

Copy link

@coderabbitai coderabbitai bot left a 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: 에러 처리 로직 단순화 필요

현재 에러 처리 로직이 다소 복잡합니다. 다음과 같은 개선을 제안합니다:

  1. 에러 타입 명확화
  2. 에러 처리 로직 모듈화
  3. 상수로 에러 메시지 분리
+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

📥 Commits

Reviewing files that changed from the base of the PR and between 6b26988 and d0f4785.

⛔ 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: ⚠️ Potential issue

React Strict Mode 비활성화에 대한 재고 필요

Strict Mode를 비활성화하면 개발 중에 잠재적인 문제를 조기에 발견하기 어려울 수 있습니다. 특별한 이유가 없다면 활성화 상태를 유지하는 것이 좋습니다.


5-5: 실험적 기능 사용에 대한 검토 필요

forceSwcTransforms를 활성화하면 성능이 향상될 수 있지만, 예기치 않은 동작이 발생할 수 있습니다. 프로덕션 환경에서 충분한 테스트가 필요합니다.

✅ Verification successful

Let me analyze the results. The search shows:

  1. The forceSwcTransforms is only used in next.config.mjs
  2. SWC-related entries in pnpm-lock.yaml are mostly dependencies and platform-specific packages
  3. No error logs or issues related to SWC transforms were found
  4. 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 통합이 잘 구현되어 있습니다.

Copy link
Member

@Nuung Nuung left a 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 호출 부를 점진적으로 따로 분리하는게 좋지 않을까 합니다!

Copy link

@coderabbitai coderabbitai bot left a 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: 타입 안전성과 상수 이름을 개선하세요

다음과 같은 개선사항을 제안합니다:

  1. 타입 안전성을 위해 타입 정의를 추가하세요
  2. 상수 이름을 더 명확하게 작성하세요 (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: 변수 명명 규칙을 개선해주세요

  1. datas는 문법적으로 올바르지 않습니다. data 또는 posts로 변경하는 것이 좋습니다.
  2. 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: 컴포넌트 구조 개선이 필요합니다

  1. Summary 컴포넌트의 하드코딩된 값들을 실제 데이터로 교체해야 합니다.
  2. 복잡한 Tailwind 클래스들을 상수로 분리하거나 컴포넌트로 추상화하는 것이 좋습니다.
  3. 로딩 상태와 에러 처리가 누락되어 있습니다.

추가로 다음 사항들도 고려해보세요:

  • 데이터 페치 로직 추가
  • 스켈레톤 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

📥 Commits

Reviewing files that changed from the base of the PR and between d0f4785 and 7c82b0e.

📒 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: 상수 관리 방식이 개선되었습니다!

사이즈 관련 상수들을 별도의 파일로 분리하여 중앙 집중식으로 관리하는 것은 좋은 접근 방식입니다. 이는 유지보수성을 향상시키고 일관성 있는 스타일 관리를 가능하게 합니다.

Comment on lines +4 to +6
const SCREENS: Record<string, string> = {};

Object.entries(CUSTOMSCREENS).forEach((i) => (SCREENS[i[0]] = i[1] + 'px'));
Copy link

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.

Suggested change
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)

Comment on lines +9 to +56
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,
},
];
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

데이터 관리 방식 개선이 필요합니다

다음과 같은 개선사항을 제안드립니다:

  1. 하드코딩된 데이터를 별도의 파일로 분리
  2. ISO 8601 날짜 형식의 오타 수정 (T13:06.16.325ZT13:06:16.325Z)
  3. 긴 제목에 대한 말줄임표 처리 로직 추가 필요
-const datas = [ ... ]
+import { dashboardData } from '@/mocks/dashboard';
+
+const datas = dashboardData;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +17 to +25
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 `;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

반응형 디자인 구현을 개선하세요

  1. 매직 넘버(180px, 65px 등)를 상수로 분리하세요.
  2. 복잡한 조건부 스타일링을 유틸리티 함수로 분리하세요.
  3. 브레이크포인트 값들을 중앙화된 설정으로 관리하세요.
+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.

Comment on lines +50 to +59
<div className={defaultStyle + navigateStyle}>
<Image
width={35}
height={35}
className="rounded-full"
src="/profile.jpg"
alt=""
/>
<span className={textStyle('username')}>스탠다드</span>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

프로필 섹션의 접근성과 데이터 관리를 개선하세요

  1. 프로필 이미지의 alt 텍스트가 비어있습니다.
  2. 프로필 이미지 경로가 하드코딩되어 있습니다.
  3. 사용자 이름이 하드코딩되어 있습니다.
 <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.

Copy link

@HA0N1 HA0N1 left a comment

Choose a reason for hiding this comment

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

잘봤습니다!

좋았던 점

  • 컴포넌트 구조가 일관되고 재사용 가능하게 설계되어 좋은 거 같습니다!

@Nuung Nuung merged commit 179dbd5 into main Dec 22, 2024
1 check passed
@Nuung Nuung deleted the feature/dashboard branch December 22, 2024 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants