Skip to content

Comments

code rabbit test 3#369

Closed
Hyeonjun0527 wants to merge 20 commits intomainfrom
develop
Closed

code rabbit test 3#369
Hyeonjun0527 wants to merge 20 commits intomainfrom
develop

Conversation

@Hyeonjun0527
Copy link
Member

@Hyeonjun0527 Hyeonjun0527 commented Feb 3, 2026

🌱 연관된 이슈

☘️ 작업 내용

code rabbit test 3

🍀 참고사항

스크린샷 (선택)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 투표 결과 조회, 댓글 작성 시 로그인 필수
    • 스터디 히스토리 탭에 인증 필수 조건 추가
    • 파트너 연락처 정보 모달에 실명 표시 개선
    • 댓글 페이지네이션 기능 구현
  • 버그 수정

    • 레이아웃 높이 문제 개선 (고정 높이 → 동적 높이)
    • 이미지 URL 형식 지원 확대 (blob, data URI)
  • 스타일

    • 아바타 컴포넌트 개선
    • 로그인 필요 시 시각적 피드백 추가

@Hyeonjun0527 Hyeonjun0527 self-assigned this Feb 3, 2026
@vercel
Copy link

vercel bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
study-platform-client-dev Ready Ready Preview, Comment Feb 3, 2026 6:51pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

🎯 전체 요약

Walkthrough

이 변경사항은 인증 기반의 접근 제어 추가, ProfileAvatar에서 UserAvatar로의 컴포넌트 마이그레이션, 스레드 및 댓글 API의 페이지네이션 지원, 그리고 파트너 데이터의 nullable 타입 변경을 포함합니다. CodeRabbit 자동 리뷰 설정도 추가되었으며, 레이아웃 및 스타일 조정이 함께 이루어졌습니다.

Changes

Cohort / File(s) Summary
Configuration & Build Setup
.coderabbit.yaml, next.config.ts
CodeRabbit 자동 리뷰 설정 추가 및 비프로덕션 환경에서 localhost 이미지 원격 패턴 조건부 지원.
Authentication & Authorization Gating
src/app/(service)/home/home-content.tsx, src/components/home/tab-navigation.tsx, src/components/voting/voting-detail-view.tsx, src/features/study/one-to-one/archive/model/use-archive-actions.ts, src/features/study/one-to-one/archive/ui/archive-filters.tsx, src/features/study/one-to-one/archive/ui/archive-tab-client.tsx
투표, 히스토리 탭, 보관함 기능에 대한 인증 확인 및 접근 제어 추가. 미인증 사용자에게 로그인 모달 표시.
Avatar Component Migration
src/components/ui/profile-avatar.tsx, src/components/ui/avatar/index.tsx, src/components/study-history/study-history-row.tsx, src/features/study/one-to-one/ui/one-on-one-page.tsx, src/features/study/one-to-one/hall-of-fame/ui/hall-of-fame-tab-client.tsx
ProfileAvatar 컴포넌트 삭제 및 UserAvatar로 마이그레이션. blob: 및 data: URL 지원 추가. 크기 타입을 문자열에서 숫자로 변경.
Pagination & API Refactoring
src/features/study/group/channel/api/get-comments.ts, src/features/study/group/channel/api/get-threads.ts, src/features/study/group/channel/api/types.ts, src/features/study/group/channel/model/use-channel-query.ts, src/features/study/group/channel/ui/comment-section.tsx, src/features/study/group/channel/ui/sub-comments.tsx
API 응답 타입을 배열 기반에서 PaginatedResponse 구조로 변경. 페이지네이션 파라미터 및 에러 처리 추가. 클라이언트 측 페이지네이션 상태 관리.
Type System Updates
src/features/auth/model/types.ts, src/features/study/interview/api/interview-types.ts, src/types/hall-of-fame.ts, src/types/study-history.ts
SignUpResponse의 토큰을 optional로 변경. DailyStudyDetail에 인터뷰어/이인 실명 필드 추가. Ranker 및 MVPTeamMember의 profileImage 타입을 ProfileImage로 정제. StudyHistoryContent 및 StudyHistoryItem의 partner를 nullable로 변경.
Partner Data & Real Name Enhancement
src/entities/user/ui/user-phone-number-copy-modal.tsx, src/features/study/one-to-one/history/ui/study-history-tab-client.tsx, src/features/study/one-to-one/schedule/ui/today-study-card.tsx
UserPhoneNumberCopyModal에 선택적 realName 속성 추가. 모달 헤더 및 본문 구조 개선. 파트너 데이터에 실명 필드 전파.
Layout & Styling Adjustments
src/app/(service)/layout.tsx, src/widgets/home/header.tsx, src/components/card/voting-card.tsx
body 및 컨테이너의 높이를 h-screen에서 min-h-screen으로 변경. 헤더 구조 재구성 및 내부 컨테이너 추가. 투표 카드 주석 수정.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

다양한 테마(인증 게이팅, 컴포넌트 마이그레이션, API 페이지네이션, 타입 시스템 업데이트)에 걸친 광범위한 변경이지만, 각 코호트는 일관성 있는 패턴을 따릅니다. 페이지네이션 API 변경은 여러 파일에 영향을 미치지만 명확한 구조를 가지고 있으며, 인증 게이팅은 반복적인 패턴입니다. 타입 변경 및 nullable 업데이트도 비교적 간단합니다.

Poem

🐰 프로필 아바타가 사라지고 새로운 친구가 찾아왔네,
페이지네이션의 춤을 추며 인증의 문을 지키고,
실명으로 더 가까워진 파트너와의 만남,
레이아웃은 유연하게, 타입은 안전하게!
변화의 물결 위에서 코드는 더욱 강해진다. 🌟

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive 제목 '코드래빗 테스트 3'은 변경사항의 주요 내용을 반영하지 않으며, 실질적인 정보를 전달하지 않는 모호한 표현입니다. PR의 실질적인 변경 내용을 명확히 반영하는 제목으로 수정하세요. 예: '인증 기반 기능 제어 및 프로필 이미지 개선' 또는 '페이지네이션 및 사용자 인증 기반 UI 개선'과 같이 주요 변경 사항을 구체적으로 설명하는 제목을 제안합니다.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/app/(service)/layout.tsx (1)

35-37: ⚠️ Potential issue | 🟠 Major

Clarity 초기화가 Server Component에서 실행되지 않습니다.

이 파일은 Server Component입니다 (export const metadata와 비동기 렌더링 사용). 서버에서는 typeof window !== 'undefined' 조건이 항상 false이므로 Clarity.init()이 절대 호출되지 않습니다.

Clarity를 올바르게 초기화하려면 별도의 Client Component를 만들어 useEffect 내에서 초기화해야 합니다.

🐛 Client Component로 분리하는 수정 제안

새 파일 src/components/analytics/clarity-init.tsx 생성:

'use client';

import Clarity from '@microsoft/clarity';
import { useEffect } from 'react';

export default function ClarityInit({ projectId }: { projectId: string }) {
  useEffect(() => {
    Clarity.init(projectId);
  }, [projectId]);

  return null;
}

그리고 레이아웃에서 사용:

-  if (typeof window !== 'undefined' && CLARITY_PROJECT_ID) {
-    Clarity.init(CLARITY_PROJECT_ID);
-  }
+  // Clarity initialization moved to ClarityInit component

   return (
     <html lang="ko">
       <head>{GTM_ID && <GoogleTagManager gtmId={GTM_ID} />}</head>
       <body className={clsx(pretendard.className, 'min-h-screen w-screen')}>
         <MainProvider>
+          {CLARITY_PROJECT_ID && <ClarityInit projectId={CLARITY_PROJECT_ID} />}
           <PageViewTracker />
src/components/voting/voting-detail-view.tsx (1)

254-262: ⚠️ Potential issue | 🟡 Minor

프로덕션 코드에 디버깅용 console.log가 남아있습니다.

디버깅용 로그가 프로덕션 환경에서도 출력됩니다. 개발 환경에서만 출력되도록 조건을 추가하거나 제거해주세요.

-  // 디버깅용 로그 추가
-  console.log('Voting Data:', {
-    myVote: voting.myVote,
-    hasVoted,
-    endsAt: voting.endsAt,
-    rawIsActive: voting.isActive,
-    isActiveByEndsAt,
-    isActive,
-  });
src/features/study/one-to-one/schedule/ui/today-study-card.tsx (1)

63-69: ⚠️ Potential issue | 🔴 Critical

realName prop이 PartnerInfo 컴포넌트에 전달되지 않습니다.

partner 객체에 realName 필드를 추가했지만 (라인 34-36), PartnerInfo 컴포넌트 호출 시 해당 prop을 전달하지 않았습니다. 이로 인해 PartnerInfo 내부의 realName이 항상 undefined가 되어 realName ?? name fallback이 항상 name을 사용하게 됩니다.

🐛 누락된 prop 전달 수정 제안
         <PartnerInfo
           id={partner.id}
           name={partner.name}
+          realName={partner.realName}
           image={partner.image}
           phoneNumber={partner.tel}
           isInterviewee={partner.id === todayStudyData.intervieweeId}
         />
🤖 Fix all issues with AI agents
In `@next.config.ts`:
- Line 2: Remove the internal import "RemotePattern" from
'next/dist/shared/lib/image-config' and instead let Next.js infer the types via
the NextConfig export (use the NextConfig object/type rather than an internal
RemotePattern), and modify the images.remotePatterns array in the exported
NextConfig so that both localhost patterns (the two patterns matching
localhost:1337) are only included when isProd is false (i.e., guard them with
the isProd check or conditionally push them into images.remotePatterns when not
in production) so local-image access is disabled in production.

In `@src/app/`(service)/home/home-content.tsx:
- Around line 16-25: The server-side tab rendering logic in renderTabContent
uses getServerCookie('memberId') and isNumeric(memberIdStr) which can disagree
with the client-side useAuth().isAuthenticated check; update the server-side
check to mirror the client-side authentication (e.g., verify accessToken/role or
call the same server auth utility used by useAuth) before rendering
StudyHistoryTab, or alternatively ensure StudyHistoryTab performs its own
authorization check and falls back to StudyTab if unauthorized; change the logic
around getServerCookie/memberIdStr in renderTabContent so it uses the unified
auth verification (or delegates to StudyHistoryTab) to prevent guest users with
a memberId cookie from seeing history content.

In `@src/components/study-history/study-history-row.tsx`:
- Around line 38-55: Change the trigger element for UserProfileModal from a
non-semantic div to a semantic button to improve accessibility: replace the div
used as the trigger with a <button> element (retain the existing className,
onClick={(e) => e.stopPropagation()} handler, and children including UserAvatar
and partner.name) so UserProfileModal's trigger prop continues to receive the
same element; ensure the button has type="button" and any necessary aria-labels
if the visual text isn't sufficient. This affects the UserProfileModal usage in
the component where partner is rendered (the trigger prop passed into
UserProfileModal).

In `@src/features/study/group/channel/model/use-channel-query.ts`:
- Around line 46-48: useThreadsQuery currently forwards whatever `page` it
receives into `getThreads` (after subtracting 1) which allows invalid
zero/negative pages to produce invalid requests; validate/sanitize `page` at the
hook boundary by computing a safePage = Math.max(1, page || 1), use that
safePage for the queryKey (e.g., ['get-threads', groupStudyId, safePage, size])
and pass getThreads({ groupStudyId, page: safePage - 1, size }) so the hook
never sends page < 0 to getThreads; update any local variable names (safePage)
and ensure enabled remains !!groupStudyId.

In `@src/features/study/group/channel/ui/comment-section.tsx`:
- Line 50: Remove the stray console.log(data.content, 'data') from
CommentSection (the debug line printing data.content) to avoid noisy/PII logs;
either delete the line entirely or replace it with a proper application logger
call (e.g., logger.debug) that does not print raw user content, keeping any
necessary non-sensitive context only.
- Around line 68-70: The component currently returns null when either isLoading
or !allCommentsLoaded is true, hiding the entire comment section until every
subcomment finishes; change the logic to only gate on the thread-level loading
flag so subthreads can render while their replies load: remove the
!allCommentsLoaded check from the early return (keep only isLoading) in the
section that references isLoading and allCommentsLoaded (the block using
useThreadsQuery and rendering SubComments), relying on each SubComments
component to handle its own loading state.

In `@src/features/study/group/channel/ui/sub-comments.tsx`:
- Line 32: Remove the stray debug console.log in
src/features/study/group/channel/ui/sub-comments.tsx — specifically the
console.log(data, 'subcomment') call; either delete it or replace it with a
proper, environment-guarded logger (e.g., only log in development) or use
existing app logging utilities so that sensitive data isn't printed to the
console.
🧹 Nitpick comments (6)
src/components/card/voting-card.tsx (1)

93-94: 주석과 코드 동작이 일치하지 않습니다.

주석은 일반적인 "투표 결과 미리보기"로 변경되었지만, 실제 코드는 여전히 hasVoted 조건으로 투표한 경우에만 표시됩니다. 주석을 코드 동작에 맞게 수정하는 것이 좋습니다.

-      {/* 간단한 투표 결과 미리보기 */}
+      {/* 투표한 경우에만 간단한 투표 결과 미리보기 */}
src/types/hall-of-fame.ts (1)

15-16: import 문을 파일 상단으로 이동하세요.

import 문이 파일 중간에 위치해 있습니다. TypeScript에서는 동작하지만, 일반적인 컨벤션은 모든 import를 파일 상단에 배치하는 것입니다.

♻️ 제안된 수정
 // 명예의 전당 API 타입 정의
+
+import type { ProfileImage } from '@/entities/user/api/types';

 export interface ApiResponse<T> {
   statusCode: number;
   timestamp: string;
   content: T;
   message: string;
 }

 export interface Job {
   job?: string; // Enum 값 (예: "IT_PRACTITIONER_GAME_DEV")
   description?: string; // 설명 (예: "게임 개발자")
 }

-import type { ProfileImage } from '@/entities/user/api/types';
-
 export interface Ranker {
src/types/study-history.ts (1)

1-1: 타입 정의 파일에서 'use client' 제거를 고려하세요.

이 파일은 TypeScript 타입 정의만 포함하고 있어 런타임에 존재하지 않습니다. 'use client' 지시어는 클라이언트 컴포넌트에만 필요하며, 타입 파일에는 불필요합니다.

♻️ 제안된 수정
-'use client';
-
 // 백엔드 API 응답 구조에 맞춘 타입 정의
src/features/study/one-to-one/archive/model/use-archive-actions.ts (1)

5-6: import와 interface 사이에 빈 줄을 추가하세요.

코드 가독성을 위해 import 문과 다른 선언 사이에 빈 줄을 추가하는 것이 좋습니다.

♻️ 제안된 수정
 import { useAuth } from '@/hooks/common/use-auth';
+
 interface ArchiveViewTarget {
src/features/study/one-to-one/archive/ui/archive-tab-client.tsx (1)

82-96: 인증 가드 중복을 고려해 주세요.

handleLikehandleLibraryBookmark에서 isAuthenticated 체크가 있지만, useArchiveActions 내부의 toggleLiketoggleBookmark에서 이미 동일한 가드를 수행합니다. 중복 가드는 방어적 프로그래밍으로 볼 수도 있지만, 불필요한 코드일 수도 있습니다.

♻️ 중복 제거 제안 (선택적)
   const handleLike = (e: React.MouseEvent, id: number) => {
     e.stopPropagation();
-    if (!isAuthenticated) return;
     toggleLike(id);
   };

   const handleView = (item: ArchiveItem) => {
     openAndRecordView(item);
   };

   const handleLibraryBookmark = (e: React.MouseEvent, id: number) => {
     e.stopPropagation();
-    if (!isAuthenticated) return;
     toggleBookmark(id);
   };
src/features/study/group/channel/ui/comment-section.tsx (1)

33-49: 답글을 전부 병렬 호출하면 요청 수가 급증할 수 있습니다.

페이지당 스레드 수가 많아질수록 댓글 API가 N+1 형태로 호출되어 초기 로딩과 서버 부하가 커질 수 있습니다. replyCount를 서버에서 내려주거나, 답글을 펼칠 때만 조회하는 방식으로 전환을 고려해주세요.

@@ -1,4 +1,7 @@
import type { NextConfig } from 'next';
import type { RemotePattern } from 'next/dist/shared/lib/image-config';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the current state of next.config.ts
cd /tmp && find . -name "next.config.ts" -o -name "next.config.js" | head -5

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

# List repository files to understand structure
git ls-files | grep -E "(next\.config|tsconfig)" | head -10

Repository: code-zero-to-one/study-platform-client

Length of output: 110


🏁 Script executed:

# Look for next.config file
fd -t f "next\.config" . --max-results 5

Repository: code-zero-to-one/study-platform-client

Length of output: 98


🏁 Script executed:

cat -n next.config.ts

Repository: code-zero-to-one/study-platform-client

Length of output: 3202


🌐 Web query:

Next.js RemotePattern type import next/dist/shared/lib/image-config official documentation

💡 Result:

There is no official Next.js documentation recommending (or supporting) importing RemotePattern from the internal path next/dist/shared/lib/image-config. That path is internal/unstable (can change between releases) and is not part of the public API surface.

Officially documented approach: type your config via NextConfig, and just write images.remotePatterns per the docs. [1]

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      { protocol: "https", hostname: "example.com", port: "", pathname: "/**", search: "" },
    ],
  },
};

export default nextConfig;

If you want a reusable type without importing internal modules, derive it from NextConfig:

import type { NextConfig } from "next";

export type RemotePattern =
  NonNullable<NonNullable<NextConfig["images"]>["remotePatterns"]>[number];

Official docs references for remotePatterns: Next.js Image component docs (remotePatterns section). [1]
(Also see the images config option docs page.) [2]

Sources:
[1] https://nextjs.org/docs/app/api-reference/components/image
[2] https://nextjs.org/docs/pages/api-reference/config/next-config-js/images


🌐 Web query:

Next.js images.remotePatterns type definition Next.js 15

💡 Result:

In Next.js 15, images.remotePatterns is typed as an array of remote pattern objects, and (starting in Next.js 15.3.0) it also accepts URL objects. (nextjs.org)

A practical TypeScript shape is:

type RemotePattern = {
  protocol?: 'http' | 'https'
  hostname: string
  port?: string
  pathname?: string
  search?: string
}

type ImagesConfig = {
  remotePatterns?: Array<RemotePattern | URL> // URL supported in >= 15.3.0
}

Notes:

  • With the object form, search is optional; omitting it allows any querystring. (nextjs.org)
  • With the new URL(...) form, if you don’t include a search in the URL, the search becomes '' (i.e., querystrings are not allowed). (nextjs.org)

Citations:


내부 타입 임포트 제거 및 로컬 이미지 패턴을 비프로덕션으로 한정하세요.

현재 next/dist/shared/lib/image-config는 Next.js 공식 공개 API가 아닌 내부 경로로, 버전 업 시 변경될 수 있습니다. 또한 localhost:1337 패턴(47-52줄)이 무조건 포함되어 있어 프로덕션에서도 로컬 서비스 접근이 가능합니다. 사용자 입력 이미지 URL이 있을 경우 SSRF 위험이 생깁니다.

공식 문서에서 권장하는 방식으로 NextConfig에서 타입을 유도하고, 두 localhost 패턴을 모두 isProd 조건으로 보호하세요.

🔧 제안 변경
-import type { RemotePattern } from 'next/dist/shared/lib/image-config';
+type RemotePattern = NonNullable<
+  NonNullable<NextConfig['images']>['remotePatterns']
+>[number];

 const isProd = process.env.NODE_ENV === 'production';
+const localRemotePatterns: RemotePattern[] = isProd
+  ? []
+  : [
+      {
+        protocol: 'http',
+        hostname: 'localhost',
+        port: '8080',
+        pathname: '/**',
+      },
+      {
+        protocol: 'http',
+        hostname: 'localhost',
+        port: '1337',
+        pathname: '/uploads/**',
+      },
+    ];

   images: {
     remotePatterns: [
       {
         protocol: 'https',
         hostname: 'img1.kakaocdn.net',
         pathname: '/**',
       },
       {
         protocol: 'https',
         hostname: 'lh3.googleusercontent.com',
         pathname: '/**',
       },
       {
         protocol: 'https',
         hostname: 'test-api.zeroone.it.kr',
         pathname: '/**',
       },
       {
         protocol: 'https',
         hostname: 'api.zeroone.it.kr',
         pathname: '/**',
       },
       {
         protocol: 'https',
         hostname: 'www.zeroone.it.kr',
         pathname: '/**',
       },
-      ...(isProd
-        ? ([] as RemotePattern[])
-        : ([
-            {
-              protocol: 'http',
-              hostname: 'localhost',
-              port: '8080',
-              pathname: '/**',
-            },
-          ] as RemotePattern[])),
-      {
-        protocol: 'http',
-        hostname: 'localhost',
-        port: '1337',
-        pathname: '/uploads/**',
-      },
+      ...localRemotePatterns,
       {
         protocol: 'https',
         hostname: 'test-blog.zeroone.it.kr',
         pathname: '/uploads/**',
       },
     ],
   },

Also applies to: 37-52

🤖 Prompt for AI Agents
In `@next.config.ts` at line 2, Remove the internal import "RemotePattern" from
'next/dist/shared/lib/image-config' and instead let Next.js infer the types via
the NextConfig export (use the NextConfig object/type rather than an internal
RemotePattern), and modify the images.remotePatterns array in the exported
NextConfig so that both localhost patterns (the two patterns matching
localhost:1337) are only included when isProd is false (i.e., guard them with
the isProd check or conditionally push them into images.remotePatterns when not
in production) so local-image access is disabled in production.

Comment on lines +16 to +25
const isHistoryTab = activeTab === 'history';
const memberIdStr = isHistoryTab ? await getServerCookie('memberId') : null;
const isLoggedIn = !!memberIdStr && isNumeric(memberIdStr);

const renderTabContent = () => {
switch (activeTab) {
case 'study':
return <StudyTab />;
case 'history':
return <StudyHistoryTab />;
return isLoggedIn ? <StudyHistoryTab /> : <StudyTab />;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

클라이언트와 서버 간 인증 확인 로직 불일치 가능성이 있습니다.

tab-navigation.tsx에서는 useAuth().isAuthenticated && getCookie('memberId')로 탭 가시성을 결정하지만, 여기서는 getServerCookie('memberId') && isNumeric(memberIdStr)만 확인합니다.

useAuthaccessToken 유효성과 ROLE_GUEST 여부까지 검사하므로, 서버에서 memberId 쿠키만 확인하면 다음과 같은 엣지 케이스가 발생할 수 있습니다:

  • 게스트 사용자가 memberId 쿠키를 가진 경우: 탭은 숨겨지지만 직접 URL 접근 시 StudyHistoryTab이 렌더링됨

서버에서도 동일한 인증 수준의 검증을 적용하거나, StudyHistoryTab 내부에서 추가 권한 검증을 수행하는 것을 권장합니다.

🤖 Prompt for AI Agents
In `@src/app/`(service)/home/home-content.tsx around lines 16 - 25, The
server-side tab rendering logic in renderTabContent uses
getServerCookie('memberId') and isNumeric(memberIdStr) which can disagree with
the client-side useAuth().isAuthenticated check; update the server-side check to
mirror the client-side authentication (e.g., verify accessToken/role or call the
same server auth utility used by useAuth) before rendering StudyHistoryTab, or
alternatively ensure StudyHistoryTab performs its own authorization check and
falls back to StudyTab if unauthorized; change the logic around
getServerCookie/memberIdStr in renderTabContent so it uses the unified auth
verification (or delegates to StudyHistoryTab) to prevent guest users with a
memberId cookie from seeing history content.

Comment on lines +38 to +55
{partner ? (
<UserProfileModal
memberId={partner.id}
trigger={
<div
className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80"
onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지
>
<UserAvatar
image={partner.profileImage || undefined}
alt={partner.name}
size={32}
className="h-8 w-8"
/>
<span className="text-text-default truncate font-medium">
{partner.name}
</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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/study-history/study-history-row.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 5796


🏁 Script executed:

find . -name "*user-profile-modal*" -type f

Repository: code-zero-to-one/study-platform-client

Length of output: 127


🏁 Script executed:

cat -n src/entities/user/ui/user-profile-modal.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 13462


🏁 Script executed:

find . -name "*modal*" -path "*/ui/*" -type f | grep -E "\.(tsx|ts)$" | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 1129


🏁 Script executed:

find . -path "*/ui/modal*" -type f | grep -v node_modules

Repository: code-zero-to-one/study-platform-client

Length of output: 152


🏁 Script executed:

cat -n src/components/ui/modal/index.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 5202


트리거 요소를 의미론적으로 더 나은 button 요소로 변경해주세요.
현재 div는 Radix UI의 asChild 처리로 기본 키보드 접근성을 지원하지만, 시맨틱 HTML 사용이 스크린 리더 및 접근성 표준에 더 부합합니다.

제안 수정안
-              <div
-                className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80"
-                onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지
-              >
+              <button
+                type="button"
+                className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80"
+                onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지
+              >
                 <UserAvatar
                   image={partner.profileImage || undefined}
                   alt={partner.name}
                   size={32}
                   className="h-8 w-8"
                 />
                 <span className="text-text-default truncate font-medium">
                   {partner.name}
                 </span>
-              </div>
+              </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{partner ? (
<UserProfileModal
memberId={partner.id}
trigger={
<div
className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80"
onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지
>
<UserAvatar
image={partner.profileImage || undefined}
alt={partner.name}
size={32}
className="h-8 w-8"
/>
<span className="text-text-default truncate font-medium">
{partner.name}
</span>
</div>
{partner ? (
<UserProfileModal
memberId={partner.id}
trigger={
<button
type="button"
className="flex cursor-pointer items-center gap-100 transition-opacity hover:opacity-80"
onClick={(e) => e.stopPropagation()} // 행 클릭 이벤트(링크 이동) 방지
>
<UserAvatar
image={partner.profileImage || undefined}
alt={partner.name}
size={32}
className="h-8 w-8"
/>
<span className="text-text-default truncate font-medium">
{partner.name}
</span>
</button>
🤖 Prompt for AI Agents
In `@src/components/study-history/study-history-row.tsx` around lines 38 - 55,
Change the trigger element for UserProfileModal from a non-semantic div to a
semantic button to improve accessibility: replace the div used as the trigger
with a <button> element (retain the existing className, onClick={(e) =>
e.stopPropagation()} handler, and children including UserAvatar and
partner.name) so UserProfileModal's trigger prop continues to receive the same
element; ensure the button has type="button" and any necessary aria-labels if
the visual text isn't sufficient. This affects the UserProfileModal usage in the
component where partner is rendered (the trigger prop passed into
UserProfileModal).

Comment on lines +46 to +48
queryKey: ['get-threads', groupStudyId, page, size],
queryFn: () => getThreads({ groupStudyId, page: page - 1, size }),
enabled: !!groupStudyId,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/features/study/group/channel/model/use-channel-query.ts | head -70

Repository: code-zero-to-one/study-platform-client

Length of output: 2617


🏁 Script executed:

# Check the full function signature and any validation
rg -A 20 "useThreadsQuery|UseThreadsQueryParams" src/features/study/group/channel/model/use-channel-query.ts

Repository: code-zero-to-one/study-platform-client

Length of output: 859


🏁 Script executed:

# Look for type definition of UseThreadsQueryParams
rg "UseThreadsQueryParams|interface.*UseThreadsQueryParams|type.*UseThreadsQueryParams" -A 5 src/

Repository: code-zero-to-one/study-platform-client

Length of output: 1146


🏁 Script executed:

# Search for usages of useThreadsQuery to understand how page is passed
rg "useThreadsQuery" --type ts --type tsx -B 2 -A 2

Repository: code-zero-to-one/study-platform-client

Length of output: 109


🏁 Script executed:

# Search for usages of useThreadsQuery to see how page parameter is passed
rg "useThreadsQuery" -B 2 -A 2 --type ts

Repository: code-zero-to-one/study-platform-client

Length of output: 1278


🏁 Script executed:

# Also search in tsx files differently
fd -e tsx -e ts | xargs grep -l "useThreadsQuery" | head -10

Repository: code-zero-to-one/study-platform-client

Length of output: 197


🏁 Script executed:

# Check all usages more broadly
rg "useThreadsQuery\(" -B 3 -A 3

Repository: code-zero-to-one/study-platform-client

Length of output: 736


🏁 Script executed:

# Examine comment-section.tsx to see how page is initialized and managed
cat -n src/features/study/group/channel/ui/comment-section.tsx | head -150

Repository: code-zero-to-one/study-platform-client

Length of output: 5780


🏁 Script executed:

# Search for page state and its initialization in comment-section.tsx
rg "page|useState" src/features/study/group/channel/ui/comment-section.tsx -B 1 -A 1 | head -50

Repository: code-zero-to-one/study-platform-client

Length of output: 682


🏁 Script executed:

# Check the Pagination component to see if it validates page values
fd -name "pagination*" -type f

Repository: code-zero-to-one/study-platform-client

Length of output: 312


🏁 Script executed:

# Find and examine the Pagination component
rg "export.*Pagination|function.*Pagination|const.*Pagination" -A 15 src/

Repository: code-zero-to-one/study-platform-client

Length of output: 5364


🏁 Script executed:

# Check the setPage usage in comment-section.tsx - look for the full pagination section
sed -n '150,200p' src/features/study/group/channel/ui/comment-section.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 622


🏁 Script executed:

# Examine the Pagination component implementation
cat -n src/components/ui/pagination/index.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 5727


🏁 Script executed:

# Also check if there's a helper function for page handling
rg "getVisibleMiddlePages|onChangePage" src/components/ui/pagination/ -A 5

Repository: code-zero-to-one/study-platform-client

Length of output: 3348


useThreadsQuery에 페이지 값 최소값 검증 추가 필요

현재 Pagination 컴포넌트는 page < 1인 값을 전달하지 않도록 제어하고 있으나, useThreadsQuery 훅 자체에는 입력 값 검증이 없습니다. 만약 훅을 직접 호출할 때 page: 0 또는 음수 값이 전달되면 getThreads에 유효하지 않은 페이지 요청(page: -1 등)이 전달됩니다. 입력값 검증을 추가하여 안전성을 확보하세요.

제안 수정안
export const useThreadsQuery = ({
  groupStudyId,
  page = 1,
  size = 10,
}: UseThreadsQueryParams) => {
+  const safePage = Math.max(1, page);
   return useQuery({
-    queryKey: ['get-threads', groupStudyId, page, size],
-    queryFn: () => getThreads({ groupStudyId, page: page - 1, size }),
+    queryKey: ['get-threads', groupStudyId, safePage, size],
+    queryFn: () => getThreads({ groupStudyId, page: safePage - 1, size }),
     enabled: !!groupStudyId,
     placeholderData: keepPreviousData,
   });
 };
🤖 Prompt for AI Agents
In `@src/features/study/group/channel/model/use-channel-query.ts` around lines 46
- 48, useThreadsQuery currently forwards whatever `page` it receives into
`getThreads` (after subtracting 1) which allows invalid zero/negative pages to
produce invalid requests; validate/sanitize `page` at the hook boundary by
computing a safePage = Math.max(1, page || 1), use that safePage for the
queryKey (e.g., ['get-threads', groupStudyId, safePage, size]) and pass
getThreads({ groupStudyId, page: safePage - 1, size }) so the hook never sends
page < 0 to getThreads; update any local variable names (safePage) and ensure
enabled remains !!groupStudyId.

(sum, q) => sum + (q.data?.totalElements ?? 0),
0,
);
console.log(data.content, 'data');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

디버그 로그는 제거하는 편이 좋습니다.

운영 콘솔 노이즈/개인정보 노출 위험을 줄이기 위해 정리해주세요.

🧹 제안 수정안
-  console.log(data.content, 'data');
📝 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
console.log(data.content, 'data');
🤖 Prompt for AI Agents
In `@src/features/study/group/channel/ui/comment-section.tsx` at line 50, Remove
the stray console.log(data.content, 'data') from CommentSection (the debug line
printing data.content) to avoid noisy/PII logs; either delete the line entirely
or replace it with a proper application logger call (e.g., logger.debug) that
does not print raw user content, keeping any necessary non-sensitive context
only.

Comment on lines +68 to +70
// threads 로딩 중이거나 답글 로딩 중이면 대기
if (isLoading || !allCommentsLoaded) return null;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cd src/features/study/group/channel/ui && head -100 comment-section.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 3023


🏁 Script executed:

cd src/features/study/group/channel/ui && sed -n '70,150p' comment-section.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 2664


🏁 Script executed:

cd src/features/study/group/channel/ui && cat -n sub-comments.tsx | head -80

Repository: code-zero-to-one/study-platform-client

Length of output: 2522


🏁 Script executed:

cd src/features/study/group/channel/model && grep -A 10 "useThreadsQuery\|useCommentsQuery\|getComments" use-channel-query.ts | head -60

Repository: code-zero-to-one/study-platform-client

Length of output: 1347


🏁 Script executed:

cd src/features/study/group/channel/ui && grep -r "Skeleton\|Loading\|Spinner" . --include="*.tsx" | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 498


전체 섹션을 숨기지 말고 스레드 로딩만 확인하세요.

현재 코드는 allCommentsLoaded가 false인 동안 (즉, 모든 답글 로딩이 완료될 때까지) 전체 댓글 섹션을 null로 반환합니다. 그러나:

  1. useThreadsQueryplaceholderData: keepPreviousData를 사용하여 이전 데이터를 유지합니다.
  2. SubComments 컴포넌트는 이미 개별적으로 로딩 상태를 처리하고 있습니다 (if (isLoading) return null;).
  3. 페이지 전환 시 전체 섹션이 공백이 되어 깜빡임이 발생합니다.

!allCommentsLoaded 조건을 제거하고 스레드 로딩만 확인하면, 답글이 개별적으로 로딩되는 동안에도 스레드는 표시되어 UX가 개선됩니다.

✅ 제안 수정안
-  if (isLoading || !allCommentsLoaded) return null;
+  if (isLoading) return null;
🤖 Prompt for AI Agents
In `@src/features/study/group/channel/ui/comment-section.tsx` around lines 68 -
70, The component currently returns null when either isLoading or
!allCommentsLoaded is true, hiding the entire comment section until every
subcomment finishes; change the logic to only gate on the thread-level loading
flag so subthreads can render while their replies load: remove the
!allCommentsLoaded check from the early return (keep only isLoading) in the
section that references isLoading and allCommentsLoaded (the block using
useThreadsQuery and rendering SubComments), relying on each SubComments
component to handle its own loading state.

refetch: commentsRefetch,
} = useCommentsQuery(groupStudyId, threadId);

console.log(data, 'subcomment');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

디버그 로그는 제거하는 편이 좋습니다.

콘솔 노이즈 및 개인정보 노출 위험을 줄이기 위해 정리해주세요.

🧹 제안 수정안
-  console.log(data, 'subcomment');
📝 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
console.log(data, 'subcomment');
🤖 Prompt for AI Agents
In `@src/features/study/group/channel/ui/sub-comments.tsx` at line 32, Remove the
stray debug console.log in src/features/study/group/channel/ui/sub-comments.tsx
— specifically the console.log(data, 'subcomment') call; either delete it or
replace it with a proper, environment-guarded logger (e.g., only log in
development) or use existing app logging utilities so that sensitive data isn't
printed to the console.

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