Skip to content

Comments

release 26.2.4 - 제로원 사내 스터디 외부 공개화 및 제로원 대표 멘토 홍보 실험#371

Merged
seong-jin-jo merged 20 commits intomainfrom
develop
Feb 3, 2026
Merged

release 26.2.4 - 제로원 사내 스터디 외부 공개화 및 제로원 대표 멘토 홍보 실험#371
seong-jin-jo merged 20 commits intomainfrom
develop

Conversation

@seong-jin-jo
Copy link
Contributor

@seong-jin-jo seong-jin-jo commented Feb 3, 2026

🌱 연관된 이슈

☘️ 작업 내용

🍀 참고사항

스크린샷 (선택)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • "나의 소중한 스터디" 섹션 추가: 참여 중인 스터디를 한눈에 확인
    • 인증 기반 기능 강화: 로그인 상태에 따른 스터디 기록 및 투표 기능 접근 제어
  • 개선 사항

    • 스터디 목록 필터링 개선: 모집 중인 스터디 기본 필터
    • 스터디 파트너 정보 표시 개선 및 연락 기능 UI 업데이트
    • 댓글 및 스레드 페이지네이션 지원
    • 레이아웃 및 스크롤 동작 개선
  • 버그 수정

    • 파트너 정보 누락 시 처리 개선
    • 이미지 URL 유효성 검사 강화

Hyeonjun0527 and others added 20 commits February 1, 2026 17:38
명예의 전당에서 프사 안보임. 아카이브, 스터디 기록이 안뜸.
직무 유저가 설정한 그대로 표기되도록 개선.
fix: 명예의전당, 아카이브, 스터디 기록에서 2가지 버그 픽스
…-fix-3

fix: 비회원일때 생기는 각종 버그 수정
댓글 개수 제대로 안 나오는 부분 수정
url 기반으로 판단하는게 확장성있는 판단일까
…urney-path

fix : sprint3 스터디 퍼널 개선
@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:56pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

개요

CodeRabbit 자동 리뷰 구성, 레이아웃 개선, 인증 제어 추가, 컴포넌트 리팩토링, API 응답 구조 변경, 파트너 정보 핸들링을 포함한 광범위한 업데이트를 도입합니다.

변경 사항

그룹 / 파일 요약
구성 및 빌드 설정
.coderabbit.yaml, .prettierignore, next.config.ts
CodeRabbit 자동 리뷰 워크플로우 및 도구 통합 설정 추가; Prettier 무시 목록 업데이트; 프로덕션 환경에서 localhost:8080 원격 이미지 패턴을 조건부로 제외.
레이아웃 및 헤더 구조
src/app/(service)/layout.tsx, src/widgets/home/header.tsx
전체 화면 높이에서 최소 높이로 전환하여 가로 스크롤 지원 활성화; 헤더를 유동형 외부 컨테이너 및 중앙 정렬된 내부 컨테이너로 재구성.
인증 게이팅
src/app/(service)/home/home-content.tsx, src/components/home/tab-navigation.tsx, src/features/voting/voting-detail-view.tsx, src/features/study/one-to-one/archive/*
로그인 상태에 따라 히스토리 탭, 투표 UI, 아카이브 북마크 기능의 가시성 및 상호작용 제어; 로그인 프롬프트 오버레이 추가.
아바타 컴포넌트 리팩토링
src/components/ui/profile-avatar.tsx (제거됨), src/components/ui/avatar/index.tsx, src/features/study/one-to-one/hall-of-fame/ui/hall-of-fame-tab-client.tsx, src/features/study/one-to-one/ui/one-on-one-page.tsx
ProfileAvatar 컴포넌트 제거; UserAvatar로 마이그레이션하여 프롭 시그니처 변경 (src → image, 문자열 크기 → 숫자); blob: 및 data: URL 지원 추가.
API 응답 구조 및 페이지네이션
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
GetCommentsResponse[] 및 GetThreadsResponse[]를 PaginatedCommentsResponse 및 PaginatedThreadsResponse로 변경; getThreads에 페이지 및 크기 파라미터 추가; 응답 추출 경로 조정.
댓글 섹션 및 스레드 관리
src/features/study/group/channel/ui/comment-section.tsx, src/features/study/group/channel/ui/sub-comments.tsx
useQueries로 병렬 스레드별 댓글 로딩 추가; 페이지네이션 지원 추가; 오류 핸들링 및 전체 댓글 카운터 개선; 데이터 안전성 강화.
파트너 정보 및 실명 처리
src/components/study-history/study-history-row.tsx, src/entities/user/ui/user-phone-number-copy-modal.tsx, src/features/study/one-to-one/schedule/ui/today-study-card.tsx, src/features/study/one-to-one/history/ui/study-history-tab-client.tsx, src/types/study-history.ts
partner를 nullable로 변경; UserPhoneNumberCopyModal에 선택적 realName 프롭 추가; today-study-card에서 interviewerRealName/intervieweeRealName 전파; 모달 제목 및 레이아웃 개선.
스터디 필터링 및 섹션
src/components/filtering/study-filter.tsx, src/components/pages/group-study-list-page.tsx, src/components/pages/premium-study-list-page.tsx, src/components/section/my-participating-studies-section.tsx
recruiting 필터 기본값을 false에서 true로 변경; 모집 중 연구 필터링 로직 역전; MyParticipatingStudiesSection 신규 컴포넌트 추가 (3개 항목 제한, 빈 상태 및 로딩 표시 포함).
타입 및 데이터 구조 업데이트
src/features/auth/model/types.ts, src/features/study/group/api/group-study-types.ts, src/features/study/interview/api/interview-types.ts, src/features/study/one-to-one/archive/model/use-archive-actions.ts, src/types/hall-of-fame.ts
SignUpResponse 토큰을 선택사항으로 변경; MemberStudyListRequest 및 MemberStudyItem에 PREMIUM_STUDY 타입 추가; DailyStudyDetail에 실명 필드 추가; 아카이브 액션에 인증 상태 노출.
기타 UI 및 검증
src/components/card/voting-card.tsx, src/features/study/group/model/use-member-study-list-query.ts
투표 카드 주석 업데이트; memberId 검증을 위한 쿼리 활성화 조건 추가.

예상 코드 리뷰 노력

🎯 3 (중간 복잡도) | ⏱️ ~25분

🐰 신규 기능들이 쏙쏙 들어오고,
파트너 실명으로 반갑게 인사하고,
페이지네이션은 춤을 추며,
인증의 문이 무섭지 않게,
아바타는 옷을 갈아입었네! 🎨✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 릴리스 버전(26.2.4)과 두 가지 주요 기능(사내 스터디 공개화 및 멘토 홍보 실험)을 명확하게 요약하고 있으며, 변경사항의 핵심 의도를 잘 반영합니다.

✏️ 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.

@seong-jin-jo seong-jin-jo merged commit 7d843a9 into main Feb 3, 2026
16 of 17 checks passed
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: 8

Caution

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

⚠️ Outside diff range comments (2)
src/features/study/one-to-one/schedule/ui/today-study-card.tsx (1)

63-69: ⚠️ Potential issue | 🟠 Major

realName prop이 PartnerInfo에 전달되지 않았습니다.

partner.realName이 34-36번 라인에서 계산되지만, PartnerInfo 호출 시 전달되지 않습니다. 이로 인해 모달에서 항상 닉네임(name)이 표시되고 실명(realName)은 표시되지 않습니다.

🐛 누락된 prop 전달 수정 제안
         <PartnerInfo
           id={partner.id}
           name={partner.name}
+          realName={partner.realName}
           image={partner.image}
           phoneNumber={partner.tel}
           isInterviewee={partner.id === todayStudyData.intervieweeId}
         />
src/features/study/one-to-one/archive/ui/archive-tab-client.tsx (1)

55-123: ⚠️ Potential issue | 🟡 Minor

로그아웃 후 ‘북마크만 보기’ 상태가 남을 수 있습니다.
비로그인 시에도 showBookmarkedOnly가 유지되면 쿼리가 북마크 필터를 계속 적용해 빈 화면/401을 유발할 수 있어요. 인증 해제 시 상태를 리셋하거나 쿼리 파라미터에 인증 조건을 넣는 편이 안전합니다.

✅ 제안 변경
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
@@
-  const { toggleBookmark, toggleLike, openAndRecordView, isAuthenticated } =
-    useArchiveActions();
+  const { toggleBookmark, toggleLike, openAndRecordView, isAuthenticated } =
+    useArchiveActions();
+
+  useEffect(() => {
+    if (!isAuthenticated) setShowBookmarkedOnly(false);
+  }, [isAuthenticated]);
@@
-  const archiveParams = {
+  const archiveParams = {
     page: currentPage - 1,
     size: ITEMS_PER_PAGE,
     sort: librarySort,
     search: debouncedSearchTerm || undefined,
-    bookmarkedOnly: showBookmarkedOnly || undefined,
+    bookmarkedOnly: isAuthenticated && showBookmarkedOnly ? true : undefined,
   };
🤖 Fix all issues with AI agents
In `@next.config.ts`:
- Line 2: Replace the internal import of RemotePattern from
'next/dist/shared/lib/image-config' by deriving RemotePattern from the public
NextConfig type (use NextConfig and compute RemotePattern as
Exclude<NonNullable<NextConfig["images"]>["remotePatterns"][number], URL>) and
update any type annotations that reference RemotePattern in next.config.ts to
use this derived type; also review the remotePatterns entries and wrap the
localhost:1337 pattern (the block around the remotePatterns entries currently
including localhost:1337, lines ~47-52) with the same isProd conditional used
for localhost:8080 so local-only patterns are guarded by the environment check.

In `@src/components/section/my-participating-studies-section.tsx`:
- Around line 41-116: The empty-state shows prematurely because the component
only checks isLoading from useGetStudies; update the empty-state logic to also
consider the loading state or presence of data from useMemberStudyListQuery
(useMemberStudyListQuery -> myStudiesData) so that participatingStudies is
treated as "not ready" until myStudiesData is loaded; specifically, add a
loading flag (or check myStudiesData !== undefined) from useMemberStudyListQuery
and only render the empty view when both useGetStudies (isLoading) and the
member query are finished (or when myStudiesData is known and
participatingStudies is empty), adjusting the conditions around
participatingStudies, participatingStudyIds, and the existing isLoading check to
prevent showing the "없음" screen while member data is still loading.

In `@src/features/auth/model/types.ts`:
- Around line 5-6: Update the middleware type to match the model's optional
tokens: change the declaration that currently enforces accessToken: string (in
middleware.ts where SignUpResponse.content is used) so both accessToken and
refreshToken are optional (accessToken?: string; refreshToken?: string) to
mirror src/features/auth/model/types.ts; locate the type or interface in
middleware.ts that describes the sign-up response content and adjust those
property signatures and any dependent type assertions to accept undefined.

In `@src/features/study/group/channel/api/types.ts`:
- Around line 88-93: Add optional pagination fields to the comments request and
forward them when calling the API: update the GetCommentsRequest interface to
include page?: number and size?: number, then modify the code in get-comments.ts
to extract and pass page and size from the request (same pattern as
get-threads.ts) when calling the comments endpoint so the pageable parameters
required by the OpenAPI spec are sent.

In `@src/features/study/group/channel/ui/comment-section.tsx`:
- Around line 45-49: The UI mixes per-page reply counts with a backend total
(using commentQueries and q.data?.totalElements in totalReplyCount), causing the
displayed total to change by page; update totalReplyCount (and the similar uses
around the other occurrences) to use a consistent metric — either: 1) use the
current-page counts only by summing q.data?.content.length (or the per-page item
array) from each commentQueries entry, or 2) use a single backend-provided
aggregate field (e.g., commentsTotal.totalElements or replyCount) and display
that instead; pick one approach, replace all uses of q.data?.totalElements
accordingly (referencing totalReplyCount and commentQueries) so the UI shows a
stable, consistent total.
- Line 50: Remove the leftover debug console.log in comment-section.tsx (the
console.log(data.content, 'data') call) — either delete the statement entirely
or replace it with the app's proper logging utility if structured logs are
required; ensure no stray debug logging remains in the CommentSection component
or related render/update handlers.

In `@src/features/study/group/channel/ui/sub-comments.tsx`:
- Line 32: Remove the leftover debug console.log in the sub-comments UI: delete
the console.log(data, 'subcomment') call inside the SubComments component (or
whatever component/function in
src/features/study/group/channel/ui/sub-comments.tsx that references `data`) so
no debug output is emitted in production; if you need runtime tracing keep a
conditional logger behind a debug flag or use the existing app logging utility
instead.

In `@src/features/study/one-to-one/archive/model/use-archive-actions.ts`:
- Around line 33-39: openAndRecordView currently early-returns when
isAuthenticated is false which skips recording views for anonymous users; update
openAndRecordView (and the recordView invocation) so anonymous views are sent to
the backend if the API supports it — either remove the isAuthenticated guard in
openAndRecordView so recordView(target.id) always runs, or add a guest-specific
call (e.g., recordGuestView or recordView with a guest flag) and call that when
!isAuthenticated; ensure you only change logic around the useCallback block and
the recordView invocation to preserve the existing window.open behavior.
🧹 Nitpick comments (10)
src/entities/user/ui/user-phone-number-copy-modal.tsx (1)

53-54: 인라인 스타일 값 대신 디자인 토큰 사용 고려.

text-[12px]가 53번과 66번 라인에서 사용되고 있습니다. 프로젝트에 해당하는 폰트 사이즈 디자인 토큰이 있다면 일관성을 위해 사용하는 것이 좋습니다.

src/features/study/one-to-one/archive/ui/archive-tab-client.tsx (1)

81-96: 인증 체크 중복은 선택적으로 정리해도 됩니다.
useArchiveActions 내부에서 이미 인증 가드를 하고 있어 핸들러에서의 if (!isAuthenticated) return;은 중복입니다. 로그인 유도 UX를 여기서 넣을 계획이 없다면 단일 책임을 위해 훅에만 두는 것도 고려해 보세요.

♻️ 예시 정리
   const handleLike = (e: React.MouseEvent, id: number) => {
     e.stopPropagation();
-    if (!isAuthenticated) return;
     toggleLike(id);
   };
@@
   const handleLibraryBookmark = (e: React.MouseEvent, id: number) => {
     e.stopPropagation();
-    if (!isAuthenticated) return;
     toggleBookmark(id);
   };
src/types/hall-of-fame.ts (1)

15-16: import 문을 파일 상단으로 이동 권장

현재 import 문이 인터페이스 정의 사이에 위치해 있습니다. 일반적인 JavaScript/TypeScript 컨벤션에 따라 모든 import 문은 파일 상단에 배치하는 것이 좋습니다.

📦 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/features/study/one-to-one/hall-of-fame/ui/hall-of-fame-tab-client.tsx (1)

203-210: 중복 패턴을 헬퍼 함수로 추출 고려

동일한 이미지 URL 추출 패턴(profileImage?.resizedImages?.[0]?.resizedImageUrl)이 여러 곳에서 사용됩니다. 재사용성과 유지보수성을 위해 헬퍼 함수로 추출하는 것을 고려해 보세요.

♻️ 헬퍼 함수 추출 제안
// 유틸리티 함수로 추출
const getProfileImageUrl = (profileImage: ProfileImage | null): string | undefined => {
  return profileImage?.resizedImages?.[0]?.resizedImageUrl ?? undefined;
};

// 사용 예시
<UserAvatar
  image={getProfileImageUrl(ranker.profileImage)}
  alt={ranker.nickname}
  size={48}
/>
src/components/study-history/study-history-row.tsx (1)

46-50: 중복 크기 지정 제거 고려

size={32} prop이 이미 32x32 픽셀 크기를 설정하므로, className="h-8 w-8" (32px)은 중복입니다. 동일하게 Line 60-64의 fallback 아바타에도 해당됩니다.

🧹 중복 className 제거
 <UserAvatar
   image={partner.profileImage || undefined}
   alt={partner.name}
   size={32}
-  className="h-8 w-8"
 />
src/types/study-history.ts (1)

1-1: 타입 정의 파일에서 'use client' 지시어 제거 고려.

이 파일은 순수 TypeScript 타입 정의만 포함하고 있습니다. 타입은 컴파일 타임에 제거되므로 런타임에 영향을 미치지 않아 'use client' 지시어가 불필요합니다.

♻️ 제안된 수정
-'use client';
-
 // 백엔드 API 응답 구조에 맞춘 타입 정의
src/components/voting/voting-detail-view.tsx (1)

522-541: 비로그인 결과 블러 처리에 포인터 차단/aria 숨김을 추가하면 접근성이 좋아집니다.

현재는 시각적 블러만 적용되어 있어 키보드/스크린리더 접근이 남을 수 있습니다.

♿️ 제안 변경안
-            <div className="relative">
-              <div className={cn(!isAuthenticated && 'blur-[6px]')}>
+            <div className="relative">
+              <div
+                className={cn(!isAuthenticated && 'blur-[6px] pointer-events-none')}
+                aria-hidden={!isAuthenticated}
+              >
                 <VoteResultsChart
                   options={votingOptions}
                   myVote={voting.myVote || undefined}
                   totalVotes={voting.totalVotes}
                 />
               </div>
               {!isAuthenticated && (
-                <div className="absolute inset-0 flex items-center justify-center">
+                <div className="absolute inset-0 flex items-center justify-center pointer-events-auto">
                   <LoginModal
                     openTrigger={
src/components/section/my-participating-studies-section.tsx (1)

23-39: 임시 구현 TODO는 이슈/백로그로 추적해 두는 걸 권장합니다.

코드 주석만으로 남기면 누락되기 쉬워서, 이슈로 분리해두면 좋겠습니다.
Based on learnings: The component src/components/section/my-participating-studies-section.tsx is a temporary implementation that should be refactored or removed when a proper backend API becomes available.

src/features/study/group/channel/api/get-comments.ts (1)

4-18: 페이지네이션 파라미터 누락 확인 필요

getCommentsPaginatedCommentsResponse를 반환하지만, getThreads와 달리 API 요청 시 pagesize 파라미터를 전달하지 않습니다. 서버가 기본값을 사용하거나 전체 댓글을 반환한다면 문제없지만, 페이지네이션이 필요하다면 일관성을 위해 파라미터 추가를 고려해주세요.

♻️ getThreads와 일관성 있게 수정하는 예시
 export const getComments = async (
-  param: GetCommentsRequest,
+  param: GetCommentsRequest & { page?: number; size?: number },
 ): Promise<PaginatedCommentsResponse> => {
-  const { groupStudyId, threadId } = param;
+  const { groupStudyId, threadId, page = 0, size = 10 } = param;

   try {
     const { data } = await axiosInstance.get(
       `group-studies/${groupStudyId}/threads/${threadId}/comments`,
+      { params: { page, size } },
     );
src/features/study/group/channel/model/use-channel-query.ts (1)

73-79: useCommentsQuery도 페이지네이션 지원 고려

useThreadsQuery는 페이지네이션을 지원하도록 업데이트되었지만, useCommentsQuery는 여전히 단순 파라미터만 받고 있습니다. getCommentsPaginatedCommentsResponse를 반환하므로, 댓글 수가 많아질 경우를 대비해 일관성 있게 페이지네이션을 지원하는 것이 좋을 수 있습니다.

현재 요구사항에서 댓글은 전체 로드가 의도된 것이라면 무시해도 됩니다.

@@ -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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "next.config.ts" -o -name "next.config.js" | head -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 15 RemotePattern type public API import

💡 Result:

Next.js 15 does not expose a public RemotePattern type you can import directly (e.g. there’s no import type { RemotePattern } from 'next').

Use the public NextConfig type and derive it from images.remotePatterns:

import type { NextConfig } from "next";

type RemotePattern =
  Exclude<NonNullable<NextConfig["images"]>["remotePatterns"][number], URL>;

Notes:

  • remotePatterns is configured under images in next.config.*. [1]
  • Since Next.js 15.3.0, remotePatterns also supports URL objects, which is why the Exclude<..., URL> is useful if you only want the object-shape pattern type. [2]

Sources: [1] [2]


🌐 Web query:

Next.js next/dist/shared/lib/image-config internal API alternative

💡 Result:

next/dist/shared/lib/image-config is not a public API (it can change/break across Next.js versions). The supported alternatives depend on what you were using it for:

  1. If you need the image config values (deviceSizes, imageSizes, formats, qualities, etc.)
  • Use the public images options in next.config.js and treat them as the source of truth. Next.js documents the allowed shape and the defaults (e.g., default deviceSizes, imageSizes, formats, etc.). [1] [2]
  1. If you need the computed <img> props / srcSet generation
  • Use the public getImageProps API from next/image instead of reimplementing logic using internal config modules. [3]
  1. If you were importing defaults (e.g., “imageConfigDefault”)
  • There is no supported “import defaults” API; copy the defaults from the docs into your own constants (or explicitly set them in next.config.js so they’re stable for your app). The docs list the defaults for deviceSizes, imageSizes, and qualities (and note qualities becomes required starting in Next.js 16). [3]

Sources: Next.js images config reference and validation shape [2] [1], next/image docs including defaults + getImageProps [3].


next/dist/shared/lib/image-config은 내부 API이므로 공개 타입을 사용하세요

next/dist/shared/lib/image-config는 Next.js의 공개 API가 아니어서 향후 버전에서 변경될 수 있습니다. NextConfig 타입으로부터 RemotePattern 타입을 유도하여 사용하세요:

import type { NextConfig } from 'next';

type RemotePattern =
  Exclude<NonNullable<NextConfig["images"]>["remotePatterns"][number], URL>;

그리고 localhost:1337 패턴(47-52줄)도 localhost:8080처럼 isProd 조건으로 감싸는 것을 검토하세요.

🤖 Prompt for AI Agents
In `@next.config.ts` at line 2, Replace the internal import of RemotePattern from
'next/dist/shared/lib/image-config' by deriving RemotePattern from the public
NextConfig type (use NextConfig and compute RemotePattern as
Exclude<NonNullable<NextConfig["images"]>["remotePatterns"][number], URL>) and
update any type annotations that reference RemotePattern in next.config.ts to
use this derived type; also review the remotePatterns entries and wrap the
localhost:1337 pattern (the block around the remotePatterns entries currently
including localhost:1337, lines ~47-52) with the same isProd conditional used
for localhost:8080 so local-only patterns are guarded by the environment check.

Comment on lines +41 to +116
// 내가 참여중인 스터디 ID 목록만 가져오기
// React Hooks 규칙: hooks는 항상 같은 순서로 호출되어야 하므로 early return 전에 호출
// memberId가 없으면 enabled: false로 설정하여 실제 API 호출은 하지 않음
const { data: myStudiesData } = useMemberStudyListQuery({
memberId: memberId ?? 0,
studyType: classification,
studyStatus: 'NOT_COMPLETED', // 진행 중과 모집 중 모두 포함
inProgressPage: 1,
inProgressPageSize: 100, // 충분히 많이 가져오기
completedPage: 1,
completedPageSize: 1,
});

// 일반 스터디 목록 가져오기 (카드에 필요한 완전한 정보를 위해)
const { data: allStudiesData, isLoading } = useGetStudies({
classification,
page: 1,
pageSize: 100, // 충분히 많이 가져와서 필터링
recruiting: undefined, // 모든 상태 포함 (진행 중, 모집 중 모두)
});

// 내가 참여중인 스터디 ID Set 생성 (IN_PROGRESS, RECRUITING)
const participatingStudyIds = useMemo(() => {
if (!myStudiesData?.notCompleted?.content) return new Set<number>();

const filtered = myStudiesData.notCompleted.content.filter(
(study) =>
study.status === 'IN_PROGRESS' || study.status === 'RECRUITING',
);

// GROUP_STUDY인 경우 type 필드로 추가 필터링
const classificationFiltered =
classification === 'GROUP_STUDY'
? filtered.filter((study) => study.type === 'GROUP_STUDY')
: filtered; // PREMIUM_STUDY는 type 필드에 없을 수 있으므로 일단 모두 포함

return new Set(classificationFiltered.map((study) => study.studyId));
}, [myStudiesData?.notCompleted?.content, classification]);

// 내가 참여중인 스터디만 필터링 (최대 3개)
const participatingStudies = useMemo(() => {
if (!allStudiesData?.content || participatingStudyIds.size === 0) {
return [];
}

// 내가 참여중인 스터디 ID에 해당하는 스터디만 필터링
const filtered = allStudiesData.content.filter((study) =>
participatingStudyIds.has(study.basicInfo?.groupStudyId ?? 0),
);

return filtered.slice(0, 3); // 최대 3개만 표시
}, [allStudiesData?.content, participatingStudyIds]);

// 비회원은 표시하지 않음 (hooks 호출 후 early return)
if (!memberId) {
return null;
}

// 로딩 중
if (isLoading) {
return (
<section className="mb-600">
<div className="mb-400 flex items-center justify-between">
<h2 className="font-designer-24b text-text-default">
내가 참여중인 스터디
</h2>
</div>
<div className="flex h-[400px] items-center justify-center">
<span className="text-text-subtle">로딩 중...</span>
</div>
</section>
);
}

// 스터디가 없는 경우 빈 상태 표시
if (participatingStudies.length === 0 && !isLoading) {
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

내 스터디 목록 로딩 중에도 빈 상태가 노출될 수 있습니다.

useMemberStudyListQuery 로딩 상태를 고려하지 않아, allStudiesData만 먼저 로드되면 참여 스터디가 있는데도 “없음” 화면이 뜰 수 있어요.

🛠️ 제안 변경안
-  const { data: myStudiesData } = useMemberStudyListQuery({
+  const { data: myStudiesData, isLoading: isMyStudiesLoading } =
+    useMemberStudyListQuery({
     memberId: memberId ?? 0,
     studyType: classification,
     studyStatus: 'NOT_COMPLETED', // 진행 중과 모집 중 모두 포함
     inProgressPage: 1,
     inProgressPageSize: 100, // 충분히 많이 가져오기
     completedPage: 1,
     completedPageSize: 1,
   });

@@
-  if (isLoading) {
+  if (isLoading || isMyStudiesLoading) {
     return (
       <section className="mb-600">
@@
-  if (participatingStudies.length === 0 && !isLoading) {
+  if (
+    participatingStudies.length === 0 &&
+    !isLoading &&
+    !isMyStudiesLoading
+  ) {
     return (
       <section className="mb-600">
🤖 Prompt for AI Agents
In `@src/components/section/my-participating-studies-section.tsx` around lines 41
- 116, The empty-state shows prematurely because the component only checks
isLoading from useGetStudies; update the empty-state logic to also consider the
loading state or presence of data from useMemberStudyListQuery
(useMemberStudyListQuery -> myStudiesData) so that participatingStudies is
treated as "not ready" until myStudiesData is loaded; specifically, add a
loading flag (or check myStudiesData !== undefined) from useMemberStudyListQuery
and only render the empty view when both useGetStudies (isLoading) and the
member query are finished (or when myStudiesData is known and
participatingStudies is empty), adjusting the conditions around
participatingStudies, participatingStudyIds, and the existing isLoading check to
prevent showing the "없음" screen while member data is still loading.

Comment on lines +5 to +6
accessToken?: string;
refreshToken?: string;
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:

# SignUpResponse 타입 정의 확인
cat src/features/auth/model/types.ts

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

Length of output: 506


🏁 Script executed:

# SignUpResponse 사용처 검색
rg "SignUpResponse" --type ts --type tsx -C 3

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

Length of output: 109


🏁 Script executed:

# accessToken 접근 패턴 확인
rg "\.accessToken" --type ts --type tsx -C 2

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

Length of output: 109


🏁 Script executed:

# refreshToken 접근 패턴 확인
rg "\.refreshToken" --type ts --type tsx -C 2

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

Length of output: 109


🏁 Script executed:

# 파일 구조 확인
find src/features/auth -type f -name "*.ts" -o -name "*.tsx" | head -20

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

Length of output: 909


🏁 Script executed:

# SignUpResponse 사용처 검색 (파일 타입 없이)
rg "SignUpResponse" -C 3

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

Length of output: 1393


🏁 Script executed:

# accessToken 접근 패턴 확인
rg "\.accessToken" -C 2

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

Length of output: 5334


🏁 Script executed:

# refreshToken 접근 패턴 확인
rg "\.refreshToken" -C 2

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

Length of output: 529


🏁 Script executed:

# content.accessToken 접근 패턴
rg "content\.accessToken" -C 2

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

Length of output: 774


🏁 Script executed:

# 회원가입 API 호출 부분 확인
cat src/features/auth/api/auth.ts

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

Length of output: 873


🏁 Script executed:

# middleware.ts 전체 내용 확인 - accessToken 타입 관련 부분
cat -n src/middleware.ts | grep -A 10 -B 10 "accessToken"

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

Length of output: 4728


🏁 Script executed:

# middleware.ts 파일 크기 확인
wc -l src/middleware.ts

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

Length of output: 103


middleware.ts에서 타입 정의 불일치 수정 필요

SignUpResponse.content.accessTokenrefreshToken이 선택적(optional)이지만, src/middleware.ts 54번 줄의 타입 정의에서는 accessToken: string (필수)로 지정되어 있습니다.

- const data: { content: { accessToken: string } } = await response.json();
+ const data: { content: { accessToken?: string } } = await response.json();

현재 코드는 런타임에 if (newAccessToken) 체크로 작동하지만, 타입 안전성이 깨져 있습니다. src/features/auth/model/types.ts의 타입 정의와 일치하도록 수정하세요.

참고: 회원가입 응답을 처리하는 sign-up-modal.tsx에서는 이미 선택적 토큰을 올바르게 처리하고 있습니다.

🤖 Prompt for AI Agents
In `@src/features/auth/model/types.ts` around lines 5 - 6, Update the middleware
type to match the model's optional tokens: change the declaration that currently
enforces accessToken: string (in middleware.ts where SignUpResponse.content is
used) so both accessToken and refreshToken are optional (accessToken?: string;
refreshToken?: string) to mirror src/features/auth/model/types.ts; locate the
type or interface in middleware.ts that describes the sign-up response content
and adjust those property signatures and any dependent type assertions to accept
undefined.

Comment on lines +88 to +93

export interface PaginatedCommentsResponse {
content: GetCommentsResponse[];
totalElements: number;
totalPages: number;
}
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:

rg -n "getComments" --type ts -C 3

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

Length of output: 8294


🏁 Script executed:

cat -n src/features/study/group/channel/api/types.ts | head -100

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

Length of output: 2743


🏁 Script executed:

rg -n "GetThreadsRequest" --type ts -A 5 -B 1

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

Length of output: 1451


🏁 Script executed:

cat -n src/features/study/group/channel/api/get-comments.ts

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

Length of output: 860


🏁 Script executed:

cat -n src/features/study/group/channel/api/get-threads.ts

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

Length of output: 884


GetCommentsRequestpagesize 파라미터 추가 필요

GetThreadsRequest와 달리 GetCommentsRequest에는 페이지네이션 파라미터가 없으며, get-comments.ts에서도 이를 API에 전달하지 않고 있습니다. 반면 get-threads.tspagesize를 추출하여 요청에 포함시킵니다. OpenAPI 스펙에서도 comments 엔드포인트는 pageable 파라미터를 요구하므로, GetCommentsRequestpage?size?를 추가하고 get-comments.ts에서 이를 전달하도록 수정해야 합니다.

🤖 Prompt for AI Agents
In `@src/features/study/group/channel/api/types.ts` around lines 88 - 93, Add
optional pagination fields to the comments request and forward them when calling
the API: update the GetCommentsRequest interface to include page?: number and
size?: number, then modify the code in get-comments.ts to extract and pass page
and size from the request (same pattern as get-threads.ts) when calling the
comments endpoint so the pageable parameters required by the OpenAPI spec are
sent.

Comment on lines +45 to +49
// 총 댓글 수 (메인 댓글 + 답글)
const totalReplyCount = commentQueries.reduce(
(sum, q) => sum + (q.data?.totalElements ?? 0),
0,
);
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

댓글 총합이 페이지마다 달라질 수 있어 표시가 혼동될 수 있습니다.

totalElements는 전체 스레드 수인데, 답글 합계는 현재 페이지 것만 더해져 수치가 페이지마다 변합니다. 범위를 맞추거나(현재 페이지 기준) 백엔드 집계값을 쓰는 편이 안전합니다.

🧮 현재 페이지 기준으로 맞추는 간단한 수정안
-  const totalCommentCount = (data?.totalElements ?? 0) + totalReplyCount;
+  const totalCommentCount =
+    (data?.content?.length ?? 0) + totalReplyCount;

Also applies to: 51-52, 95-95

🤖 Prompt for AI Agents
In `@src/features/study/group/channel/ui/comment-section.tsx` around lines 45 -
49, The UI mixes per-page reply counts with a backend total (using
commentQueries and q.data?.totalElements in totalReplyCount), causing the
displayed total to change by page; update totalReplyCount (and the similar uses
around the other occurrences) to use a consistent metric — either: 1) use the
current-page counts only by summing q.data?.content.length (or the per-page item
array) from each commentQueries entry, or 2) use a single backend-provided
aggregate field (e.g., commentsTotal.totalElements or replyCount) and display
that instead; pick one approach, replace all uses of q.data?.totalElements
accordingly (referencing totalReplyCount and commentQueries) so the UI shows a
stable, consistent total.

(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 leftover debug console.log in comment-section.tsx (the
console.log(data.content, 'data') call) — either delete the statement entirely
or replace it with the app's proper logging utility if structured logs are
required; ensure no stray debug logging remains in the CommentSection component
or related render/update handlers.

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
leftover debug console.log in the sub-comments UI: delete the console.log(data,
'subcomment') call inside the SubComments component (or whatever
component/function in src/features/study/group/channel/ui/sub-comments.tsx that
references `data`) so no debug output is emitted in production; if you need
runtime tracing keep a conditional logger behind a debug flag or use the
existing app logging utility instead.

Comment on lines 33 to +39
const openAndRecordView = useCallback(
(target: ArchiveViewTarget) => {
window.open(target.link, '_blank');
if (!isAuthenticated) return;
recordView(target.id);
},
[recordView],
[isAuthenticated, recordView],
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:

# First, let's examine the file and surrounding context
cat -n src/features/study/one-to-one/archive/model/use-archive-actions.ts | head -60

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

Length of output: 1906


🏁 Script executed:

# Search for backend API calls related to view recording
rg -i "recordview|view.*record|record.*view" --type ts --type tsx -B 2 -A 2

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

Length of output: 109


🏁 Script executed:

# Check for any guest/anonymous view handling mechanisms
rg -i "guest|anonymous|unauthenticated.*view|view.*guest" --type ts --type tsx -B 2 -A 2

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

Length of output: 109


🏁 Script executed:

# Look for the recordView function definition and backend call
ast-grep --pattern 'const recordView = $_'

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

Length of output: 64


🏁 Script executed:

# Search for any analytics or tracking related to view counts
rg -i "analytics|tracking.*view|view.*count" --type ts --type tsx | head -20

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

Length of output: 109


🏁 Script executed:

# Find the use-view-mutation file
fd "use-view-mutation" -type f

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

Length of output: 252


🏁 Script executed:

# Search for recordView or view-related mutations
rg "recordView|useRecordArchiveViewMutation" -t js -A 10

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

Length of output: 230


🏁 Script executed:

# Look for backend API endpoints related to views
rg "archive.*view|view.*api|/api.*view" -t js | head -20

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

Length of output: 230


🏁 Script executed:

# Check for any guest tracking or analytics
rg "guest|anonymous" -B 2 -A 2 | head -30

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

Length of output: 507


🏁 Script executed:

# Find the use-view-mutation file more directly
find . -name "*view-mutation*" -o -name "*use-view*"

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

Length of output: 148


🏁 Script executed:

# Search for the mutation import/implementation without type filter
rg "useRecordArchiveViewMutation" -A 15

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

Length of output: 4183


🏁 Script executed:

# Look for API endpoints related to archive views
rg "archive.*view|recordView" -A 5 -B 2

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

Length of output: 3964


🏁 Script executed:

# Check the full directory structure around the archive folder
ls -la src/features/study/one-to-one/archive/model/

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

Length of output: 549


🏁 Script executed:

# Read the complete use-view-mutation.ts file
cat -n src/features/study/one-to-one/archive/model/use-view-mutation.ts

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

Length of output: 2688


🏁 Script executed:

# Read the complete record-view.ts file
cat -n src/features/study/one-to-one/archive/api/record-view.ts

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

Length of output: 280


🏁 Script executed:

# Check if there are any TODO/FIXME comments about guest views or view recording
rg "TODO|FIXME|guest.*view|view.*guest" -B 2 -A 2

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

Length of output: 4666


🏁 Script executed:

# Search for any backend-related documentation or comments about views
rg "조회.*수|view.*count|view.*record" -B 1 -A 1 | head -30

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

Length of output: 10159


🏁 Script executed:

# Search for any API documentation or backend-related files
find . -name "*.md" -o -name "*.txt" | head -20

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

Length of output: 852


🏁 Script executed:

# Look for any environment configuration or API constants that might reveal endpoint behavior
rg "archive.*endpoint|api.*config|api.*constant" -i -B 2 -A 2

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

Length of output: 50395


🏁 Script executed:

# Check if there's any documentation about the /archive/{id}/view endpoint
rg "archive.*view|/archive.*view" -i -B 2 -A 2

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

Length of output: 9880


🏁 Script executed:

# Verify if the axiosInstance has authentication interceptors that would block unauthenticated requests
cat -n src/api/client/axios.ts | head -50

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

Length of output: 1864


🏁 Script executed:

# Search for any backend-related comments or documentation in the archive feature
find src/features/study/one-to-one/archive -type f -name "*.ts" -o -name "*.tsx" | xargs cat | grep -i "backend\|api\|guest\|unauthent" -B 2 -A 2

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

Length of output: 2311


비로그인 사용자의 조회수 기록이 누락됩니다.

현재 코드는 if (!isAuthenticated) return; 조건으로 비로그인 사용자의 조회 기록 API 호출을 방지합니다. 공개 콘텐츠의 통계를 정확히 집계하려면 백엔드가 비로그인 뷰를 지원하는지 확인하고, 지원한다면 프론트엔드 가드를 제거하거나 별도의 게스트 기록 경로 추가를 검토하세요.

🤖 Prompt for AI Agents
In `@src/features/study/one-to-one/archive/model/use-archive-actions.ts` around
lines 33 - 39, openAndRecordView currently early-returns when isAuthenticated is
false which skips recording views for anonymous users; update openAndRecordView
(and the recordView invocation) so anonymous views are sent to the backend if
the API supports it — either remove the isAuthenticated guard in
openAndRecordView so recordView(target.id) always runs, or add a guest-specific
call (e.g., recordGuestView or recordView with a guest flag) and call that when
!isAuthenticated; ensure you only change logic around the useCallback block and
the recordView invocation to preserve the existing window.open behavior.

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