release 26.2.4 - 제로원 사내 스터디 외부 공개화 및 제로원 대표 멘토 홍보 실험#371
Conversation
명예의 전당에서 프사 안보임. 아카이브, 스터디 기록이 안뜸. 직무 유저가 설정한 그대로 표기되도록 개선.
fix: 명예의전당, 아카이브, 스터디 기록에서 2가지 버그 픽스
…-fix-3 fix: 비회원일때 생기는 각종 버그 수정
fix: 스크롤 문제 해결
댓글 개수 제대로 안 나오는 부분 수정
url 기반으로 판단하는게 확장성있는 판단일까
…urney-path fix : sprint3 스터디 퍼널 개선
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 Walkthrough개요CodeRabbit 자동 리뷰 구성, 레이아웃 개선, 인증 제어 추가, 컴포넌트 리팩토링, API 응답 구조 변경, 파트너 정보 핸들링을 포함한 광범위한 업데이트를 도입합니다. 변경 사항
예상 코드 리뷰 노력🎯 3 (중간 복잡도) | ⏱️ ~25분 시
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
realNameprop이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 componentsrc/components/section/my-participating-studies-section.tsxis 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: 페이지네이션 파라미터 누락 확인 필요
getComments가PaginatedCommentsResponse를 반환하지만,getThreads와 달리 API 요청 시page와size파라미터를 전달하지 않습니다. 서버가 기본값을 사용하거나 전체 댓글을 반환한다면 문제없지만, 페이지네이션이 필요하다면 일관성을 위해 파라미터 추가를 고려해주세요.♻️ 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는 여전히 단순 파라미터만 받고 있습니다.getComments가PaginatedCommentsResponse를 반환하므로, 댓글 수가 많아질 경우를 대비해 일관성 있게 페이지네이션을 지원하는 것이 좋을 수 있습니다.현재 요구사항에서 댓글은 전체 로드가 의도된 것이라면 무시해도 됩니다.
| @@ -1,4 +1,7 @@ | |||
| import type { NextConfig } from 'next'; | |||
| import type { RemotePattern } from 'next/dist/shared/lib/image-config'; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "next.config.ts" -o -name "next.config.js" | head -5Repository: code-zero-to-one/study-platform-client
Length of output: 98
🏁 Script executed:
cat -n next.config.tsRepository: 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:
remotePatternsis configured underimagesinnext.config.*. [1]- Since Next.js 15.3.0,
remotePatternsalso supportsURLobjects, which is why theExclude<..., 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:
- If you need the image config values (deviceSizes, imageSizes, formats, qualities, etc.)
- Use the public
imagesoptions innext.config.jsand treat them as the source of truth. Next.js documents the allowed shape and the defaults (e.g., defaultdeviceSizes,imageSizes,formats, etc.). [1] [2]
- If you need the computed
<img>props /srcSetgeneration
- Use the public
getImagePropsAPI fromnext/imageinstead of reimplementing logic using internal config modules. [3]
- 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.jsso they’re stable for your app). The docs list the defaults fordeviceSizes,imageSizes, andqualities(and notequalitiesbecomes 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.
| // 내가 참여중인 스터디 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) { |
There was a problem hiding this comment.
내 스터디 목록 로딩 중에도 빈 상태가 노출될 수 있습니다.
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.
| accessToken?: string; | ||
| refreshToken?: string; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# SignUpResponse 타입 정의 확인
cat src/features/auth/model/types.tsRepository: code-zero-to-one/study-platform-client
Length of output: 506
🏁 Script executed:
# SignUpResponse 사용처 검색
rg "SignUpResponse" --type ts --type tsx -C 3Repository: code-zero-to-one/study-platform-client
Length of output: 109
🏁 Script executed:
# accessToken 접근 패턴 확인
rg "\.accessToken" --type ts --type tsx -C 2Repository: code-zero-to-one/study-platform-client
Length of output: 109
🏁 Script executed:
# refreshToken 접근 패턴 확인
rg "\.refreshToken" --type ts --type tsx -C 2Repository: 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 -20Repository: code-zero-to-one/study-platform-client
Length of output: 909
🏁 Script executed:
# SignUpResponse 사용처 검색 (파일 타입 없이)
rg "SignUpResponse" -C 3Repository: code-zero-to-one/study-platform-client
Length of output: 1393
🏁 Script executed:
# accessToken 접근 패턴 확인
rg "\.accessToken" -C 2Repository: code-zero-to-one/study-platform-client
Length of output: 5334
🏁 Script executed:
# refreshToken 접근 패턴 확인
rg "\.refreshToken" -C 2Repository: code-zero-to-one/study-platform-client
Length of output: 529
🏁 Script executed:
# content.accessToken 접근 패턴
rg "content\.accessToken" -C 2Repository: code-zero-to-one/study-platform-client
Length of output: 774
🏁 Script executed:
# 회원가입 API 호출 부분 확인
cat src/features/auth/api/auth.tsRepository: 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.tsRepository: code-zero-to-one/study-platform-client
Length of output: 103
middleware.ts에서 타입 정의 불일치 수정 필요
SignUpResponse.content.accessToken과 refreshToken이 선택적(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.
|
|
||
| export interface PaginatedCommentsResponse { | ||
| content: GetCommentsResponse[]; | ||
| totalElements: number; | ||
| totalPages: number; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "getComments" --type ts -C 3Repository: code-zero-to-one/study-platform-client
Length of output: 8294
🏁 Script executed:
cat -n src/features/study/group/channel/api/types.ts | head -100Repository: code-zero-to-one/study-platform-client
Length of output: 2743
🏁 Script executed:
rg -n "GetThreadsRequest" --type ts -A 5 -B 1Repository: code-zero-to-one/study-platform-client
Length of output: 1451
🏁 Script executed:
cat -n src/features/study/group/channel/api/get-comments.tsRepository: code-zero-to-one/study-platform-client
Length of output: 860
🏁 Script executed:
cat -n src/features/study/group/channel/api/get-threads.tsRepository: code-zero-to-one/study-platform-client
Length of output: 884
GetCommentsRequest에 page와 size 파라미터 추가 필요
GetThreadsRequest와 달리 GetCommentsRequest에는 페이지네이션 파라미터가 없으며, get-comments.ts에서도 이를 API에 전달하지 않고 있습니다. 반면 get-threads.ts는 page와 size를 추출하여 요청에 포함시킵니다. OpenAPI 스펙에서도 comments 엔드포인트는 pageable 파라미터를 요구하므로, GetCommentsRequest에 page?와 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.
| // 총 댓글 수 (메인 댓글 + 답글) | ||
| const totalReplyCount = commentQueries.reduce( | ||
| (sum, q) => sum + (q.data?.totalElements ?? 0), | ||
| 0, | ||
| ); |
There was a problem hiding this comment.
댓글 총합이 페이지마다 달라질 수 있어 표시가 혼동될 수 있습니다.
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'); |
There was a problem hiding this comment.
디버그 로그는 제거가 필요합니다.
🧹 제안 변경안
- 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.
| 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'); |
There was a problem hiding this comment.
디버그 로그는 릴리스 전에 제거해주세요.
콘솔 출력이 사용자 환경에 남습니다.
🧹 제안 변경안
- 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.
| 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.
| const openAndRecordView = useCallback( | ||
| (target: ArchiveViewTarget) => { | ||
| window.open(target.link, '_blank'); | ||
| if (!isAuthenticated) return; | ||
| recordView(target.id); | ||
| }, | ||
| [recordView], | ||
| [isAuthenticated, recordView], |
There was a problem hiding this comment.
🧩 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 -60Repository: 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 2Repository: 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 2Repository: 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 -20Repository: code-zero-to-one/study-platform-client
Length of output: 109
🏁 Script executed:
# Find the use-view-mutation file
fd "use-view-mutation" -type fRepository: 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 10Repository: 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 -20Repository: 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 -30Repository: 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 15Repository: 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 2Repository: 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.tsRepository: 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.tsRepository: 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 2Repository: 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 -30Repository: 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 -20Repository: 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 2Repository: 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 2Repository: 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 -50Repository: 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 2Repository: 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.
🌱 연관된 이슈
☘️ 작업 내용
🍀 참고사항
스크린샷 (선택)
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
버그 수정