Skip to content

토스트 타입 추가, 재량 평가 UX 수정, 스터디 카드 뱃지 UI 및 로직 수정#403

Open
HA-SEUNG-JEONG wants to merge 12 commits intodevelopfrom
fix/study
Open

토스트 타입 추가, 재량 평가 UX 수정, 스터디 카드 뱃지 UI 및 로직 수정#403
HA-SEUNG-JEONG wants to merge 12 commits intodevelopfrom
fix/study

Conversation

@HA-SEUNG-JEONG
Copy link
Contributor

@HA-SEUNG-JEONG HA-SEUNG-JEONG commented Feb 26, 2026

🌱 연관된 이슈

☘️ 작업 내용

  • '준비중인 기능입니다' 토스트 성격에 맞게 info 타입 추가
  • 재량 평가 3회 완료 후 UI로 별도 안내
  • 스터디 카드 내 카운트다운 뱃지 UI 및 로직 수정
  • 스터디 개설 모달에서 질문 입력 오류 메시지 추가

🍀 참고사항

스크린샷 (선택)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 스터디 카드에 조회수 표시 추가
    • 스터디 상세에 플로팅 실시간 정보 바(티커) 추가
  • 개선사항

    • "마감 임박(ENDING_SOON)" 상태 추가 및 신청/배지/필터 반영
    • 완료된 스터디에 그레이스케일 오버레이 적용
    • 폼 필드 문자 제한 prop을 maxCharCount로 표준화
    • 이미지 업로드 방식 개선 및 이전 object URL 해제 처리
    • 토스트에 정보(info) 타입 아이콘 추가

@HA-SEUNG-JEONG HA-SEUNG-JEONG self-assigned this Feb 26, 2026
@vercel
Copy link

vercel bot commented Feb 26, 2026

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

Project Deployment Actions Updated (UTC)
study-platform-client-dev Error Error Feb 26, 2026 5:59pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

여러 레이아웃의 수평 오버플로우 처리 변경, FormField 프로퍼티명 표준화(counterMax → maxCharCount), ENDING_SOON 상태 추가 및 관련 UI/타입 반영, StudyActiveTicker 단순화, 이미지 업로드 및 모달 제출 흐름 일부 리팩토링이 포함된 UI·타입 변경입니다.

Changes

Cohort / File(s) Summary
레이아웃 오버플로우 처리
src/app/(admin)/layout.tsx, src/app/(landing)/layout.tsx, src/app/(service)/layout.tsx
루트/내부 컨테이너의 overflow-x 클래스 변경(overflow-x-hidden 추가 / overflow-auto 제거).
블로그 및 서비스 페이지 레이아웃
src/app/(service)/insights/ui/blog-detail-page.tsx, src/components/pages/group-study-detail-page.tsx, src/components/pages/premium-study-detail-page.tsx
커버 이미지 컨테이너 폭/위치 조정 및 StudyActiveTicker 추가로 플로팅 정보 바 렌더링 구조 변경.
그룹 스터디 상태 타입 확장
src/features/study/group/api/group-study-types.ts, src/features/study/group/const/group-study-const.ts
GroupStudyStatus에 ENDING_SOON 추가 및 라벨('마감 임박') 추가; BasicInfoDetail.deletedAt에서 null 제거.
FormField 및 TextAreaInput 프로퍼티 표준화
src/components/ui/form/form-field.tsx, src/components/modals/*, src/components/voting/voting-create-modal.tsx, src/features/my-page/*, src/features/study/group/*, src/features/study/interview/*
공개 Prop counterMaxmaxCharCount로 변경(인터페이스 및 사용 지점 일괄 업데이트).
스터디 카드 및 필터 개선
src/components/card/study-card.tsx, src/components/filtering/study-filter.tsx
StudyCard에 viewCount?: number 추가, 완료 스터디 그레이스케일 오버레이 및 뷰카운트 표시; 필터에서 중앙화된 상수(ROLE_OPTIONS_UI, STUDY_METHOD_LABELS) 사용.
StudyActiveTicker 컴포넌트 리팩토링
src/components/ui/study-active-ticker.tsx
startDate 기반 카운트다운 제거, 회전 배너(아이콘+메시지)로 대체, props 변경(startDate 제거, className 추가).
스터디 상태별 조건부 렌더링 업데이트
src/components/ui/study-card-countdown-badge.tsx, src/components/summary/study-info-summary.tsx, src/features/my-page/ui/my-study-info-card.tsx, src/components/section/my-participating-studies-section.tsx
ENDING_SOON을 RECRUITING과 동등하게 처리하도록 뱃지·버튼·필터 로직 확장.
모달 및 폼 제출 흐름 리팩토링
src/components/modals/discretionary-evaluation-modal.tsx, src/components/modals/question-modal.tsx, src/components/section/inquiry-section.tsx
재량평가 제출 흐름에 isPending/onSubmitSuccess 추가, 이미지 업로드를 FormData·multipart로 변경, InquirySection에서 onOpenModal 제거.
이미지 업로드 및 UI 컴포넌트 개선
src/components/ui/image-upload-input.tsx, src/components/ui/toast.tsx
image-upload-input에 cva 변형 적용(드래그 상태 관리), toast에 info variant·Info 아이콘 추가.
폼 검증 및 기타 개선
src/features/study/group/model/group-study-form.schema.ts, src/features/study/group/ui/*, src/components/section/group-study-info-section.tsx
interviewPost 최소 1개 검증 추가, EVALUATION_COUNT 상수 도입 및 사용, 전역 폼 루트 에러 렌더링 추가, 일부 섹션에서 StudyActiveTicker 제거 대체.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25분

Possibly related PRs

Suggested labels

refactoring

🐰 상태는 늘어나고, 필드는 바뀌며,
토글은 회전하고, 업로드는 춤추네.
오버플로우 숨기고, 카운트는 반짝이며,
마감 임박의 메아리 들려오니,
토끼가 축하의 당근을 들고 뛴다! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 PR 제목이 변경 사항의 주요 내용을 명확하게 요약합니다. 토스트 타입 추가, 재량 평가 UX 수정, 스터디 카드 뱃지 UI/로직 수정이라는 세 가지 핵심 변경 사항을 간결하게 나열하고 있습니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/study

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@HA-SEUNG-JEONG HA-SEUNG-JEONG changed the title 스터디 퍼널 개선 토스트 타입 추가, 재량 평가 UX 수정, 스터디 카드 뱃지 UI 및 로직 수정 Feb 26, 2026
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 (2)
src/components/modals/edit-homework-modal.tsx (1)

130-137: ⚠️ Potential issue | 🟡 Minor

maxCharCountmaxLength 값 불일치

maxCharCount={5000}으로 설정되어 있지만, TextAreaInputmaxLength={1000}과 스키마의 .max(1000) 유효성 검사가 1000자로 제한되어 있습니다. 이로 인해 카운터 UI에 잘못된 최대값이 표시됩니다.

📝 값 일치를 위한 수정 제안
  <FormField<EditHomeworkFormValues, 'textContent'>
    name="textContent"
    label="과제 상세 내용"
    direction="vertical"
    required
-   maxCharCount={5000}
+   maxCharCount={1000}
    showCounterRight={false}
  >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/modals/edit-homework-modal.tsx` around lines 130 - 137, The
displayed maxCharCount (maxCharCount={5000}) disagrees with the actual input
limit (TextAreaInput id="textContent" has maxLength={1000} and the validation
schema uses .max(1000)); fix by making these values consistent: either change
maxCharCount to 1000 to match TextAreaInput.maxLength and the schema, or
increase TextAreaInput.maxLength and the schema .max(...) to 5000 if you intend
a 5000‑char limit—update maxCharCount, TextAreaInput maxLength, and the
validation rule together so all three (maxCharCount, TextAreaInput
id="textContent" maxLength, and the schema .max) match.
src/components/modals/create-mission-modal.tsx (1)

28-31: ⚠️ Potential issue | 🟡 Minor

guide 필드의 유효성 검사와 UI 제한값 불일치 (기존 이슈)

Zod 스키마에서 guide 필드는 .max(1000)으로 제한되어 있지만, maxCharCount={5000}maxLength={5000}으로 설정되어 있습니다. 이로 인해 사용자가 1000자 이상 5000자 이하를 입력하면 UI에서는 허용하지만 폼 제출 시 유효성 검사에서 실패할 수 있습니다.

이 불일치가 의도된 것인지 확인해 주세요. 의도된 것이 아니라면 값을 일치시키는 것이 좋습니다.

📝 값 일치를 위한 수정 제안

스키마를 5000으로 변경하거나:

  guide: z
    .string()
    .min(1, '수행 가이드를 입력해주세요.')
-   .max(1000, '1000자 이하로 작성가능합니다.'),
+   .max(5000, '5000자 이하로 작성가능합니다.'),

또는 UI 제한을 1000으로 변경:

  <FormField<CreateMissionFormValues, 'guide'>
    ...
-   maxCharCount={5000}
+   maxCharCount={1000}
    ...
  >
    <TextAreaInput
      ...
-     maxLength={5000}
+     maxLength={1000}
      ...
    />

Also applies to: 220-220, 226-226

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/modals/create-mission-modal.tsx` around lines 28 - 31, The Zod
schema for the guide field (guide: z.string().min(1).max(1000)) conflicts with
the UI limits (maxCharCount={5000}, maxLength={5000}) in
create-mission-modal.tsx; update them to match by either increasing the Zod max
to 5000 or reducing the UI props maxCharCount and maxLength to 1000 so
validation and UI are consistent, and apply the same change for the other
occurrences mentioned (lines around 220 and 226) where the UI props are set;
ensure you update the guide field definition and the corresponding
input/textarea props together and run the form validation flow to confirm
submission succeeds at the intended limit.
🧹 Nitpick comments (3)
src/app/(service)/layout.tsx (1)

41-48: overflow-x-hidden 중복 적용에 대한 검토 필요.

html 요소(Line 41)와 내부 div 컨테이너(Line 48) 모두에 overflow-x-hidden이 적용되어 있습니다. 기능적으로는 문제없지만, 부모 요소에서 이미 가로 스크롤을 숨기고 있으므로 내부 컨테이너의 overflow-x-hidden은 불필요할 수 있습니다.

의도적으로 이중 방어를 적용한 것이라면 현재 상태로 유지해도 무방합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/layout.tsx around lines 41 - 48, Remove the duplicated
overflow-x-hidden by deleting it from the inner container: the html element
already has className="overflow-x-hidden", so update the inner div (the element
with className="flex min-h-screen w-full flex-col overflow-x-hidden") to remove
overflow-x-hidden (leaving className="flex min-h-screen w-full flex-col"); if
you intended defensive duplication instead, leave as-is and add a comment
explaining the reason.
src/components/ui/study-active-ticker.tsx (1)

62-62: 동적 class 병합은 clsx + tailwind-merge로 통일해 주세요.

Line [62]처럼 템플릿 문자열로 합치면 충돌 클래스 정리가 어려워집니다.

♻️ 제안 코드
+import { clsx } from 'clsx';
 import { Eye, Flame } from 'lucide-react';
 import { useEffect, useState } from 'react';
+import { twMerge } from 'tailwind-merge';
@@
-    <div className={`rounded-200 bg-transparent pt-400 pb-150 ${className}`}>
+    <div
+      className={twMerge(
+        clsx('rounded-200 bg-transparent pt-400 pb-150', className),
+      )}
+    >

As per coding guidelines src/**/*.{ts,tsx}: Style components using Tailwind CSS 4 with @tailwindcss/postcss plugin, clsx, tailwind-merge, and class-variance-authority (CVA) utilities.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/study-active-ticker.tsx` at line 62, Replace the
template-string class concatenation on the div with a merged class approach
using clsx and tailwind-merge: build the static classes ("rounded-200
bg-transparent pt-400 pb-150") and the incoming className via clsx, then pass
the result through twMerge before assigning to the div; update the JSX in the
component (the div that currently uses `className={`rounded-200 bg-transparent
pt-400 pb-150 ${className}`}`) to use twMerge(clsx(...)) so conflicting Tailwind
classes are resolved.
src/components/modals/discretionary-evaluation-modal.tsx (1)

12-17: Zod 스키마에 최대 길이 검증 추가 권장

EVALUATION_CONTENT_MAX_LENGTH를 상수로 추출한 것은 좋습니다. 하지만 Zod 스키마에는 최소 길이(min(1))만 검증하고 최대 길이는 검증하지 않습니다. TextAreaInputmaxLength 속성은 클라이언트 측에서만 동작하므로, 스키마 레벨에서도 검증을 추가하면 더 안전합니다.

♻️ 수정 제안
 const DiscretionaryEvaluationFormSchema = z.object({
-  content: z.string().min(1, '평가 내역을 입력해주세요.'),
+  content: z
+    .string()
+    .min(1, '평가 내역을 입력해주세요.')
+    .max(EVALUATION_CONTENT_MAX_LENGTH, `평가 내역은 ${EVALUATION_CONTENT_MAX_LENGTH}자를 초과할 수 없습니다.`),
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/modals/discretionary-evaluation-modal.tsx` around lines 12 -
17, DiscretionaryEvaluationFormSchema currently enforces only min length for the
content field; add a max length check using the existing
EVALUATION_CONTENT_MAX_LENGTH constant so the schema validates content.length <=
EVALUATION_CONTENT_MAX_LENGTH (e.g., chain .max(...) onto the z.string() for the
content property) and provide an appropriate error message; update
DiscretionaryEvaluationFormSchema to reference EVALUATION_CONTENT_MAX_LENGTH to
keep validation consistent with TextAreaInput.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/modals/question-modal.tsx`:
- Around line 85-88: The code uses new URL(uploadUrl) which throws for relative
paths; update the URL parsing logic around uploadUrl/relativePath used before
calling axiosInstanceForMultipart.put so it handles both absolute and relative
URLs — detect if uploadUrl is absolute (e.g., starts with http/https) and parse
with new URL(uploadUrl) to build relativePath, otherwise treat uploadUrl as
already-relative (or construct a URL with a base like window.location.origin)
and then derive the same pathname+search string; ensure
axiosInstanceForMultipart.put receives the correct relativePath regardless of
uploadUrl format.

In `@src/components/pages/group-study-detail-page.tsx`:
- Around line 167-174: You’re passing a startDate prop to StudyActiveTicker even
though StudyActiveTickerProps (in src/components/ui/study-active-ticker.tsx)
doesn’t define it; remove the startDate={studyDetail.basicInfo.startDate} from
the StudyActiveTicker usage in group-study-detail-page.tsx, or if the ticker
must display the start date, add startDate to the StudyActiveTickerProps
interface and implement its handling inside StudyActiveTicker (adjust prop types
and rendering accordingly).

In `@src/components/pages/premium-study-detail-page.tsx`:
- Around line 76-77: Remove the stray debug log that references an undefined
variable: delete the console.log({ basic }) call in
premium-study-detail-page.tsx (or replace it with a valid variable/reference if
the intent was to log something else). Ensure no other uses of the undefined
identifier `basic` remain in the component (eg. in PremiumStudyDetailPage or
related render logic) so the TypeScript build error is resolved.
- Around line 169-175: The StudyActiveTicker invocation is passing a
non-existent prop startDate (studyDetail.basicInfo.startDate); remove this prop
from the JSX in premium-study-detail-page.tsx so the component only receives
approvedCount and maxMembersCount (and any of the accepted props viewCount or
className if needed), or alternatively add startDate to the StudyActiveTicker
prop interface in src/components/ui/study-active-ticker.tsx and handle it inside
StudyActiveTicker — update either the usage (remove startDate) or the component
prop types/implementation (add startDate) to make them consistent.

In `@src/components/ui/study-active-ticker.tsx`:
- Line 9: Remove the unused startDate prop from the component props interface in
study-active-ticker.tsx and update all call sites to stop passing startDate;
then refactor className concatenations to use clsx (and tailwind-merge or cva if
needed) instead of template strings: replace the outer div that currently uses
`className={`rounded-200 bg-transparent pt-400 pb-150 ${className}`}` with
clsx/tn merge combining "rounded-200 bg-transparent pt-400 pb-150" and the
incoming className prop, and replace the inner element that computes classes
based on `index === currentIndex` (currently using a ternary in a template
string) with clsx that conditionally applies 'translate-y-0 opacity-100' when
index === currentIndex and 'translate-y-full opacity-0' otherwise; ensure you
import clsx (and twMerge/cva if chosen) and update the props/interface and any
tests or callers accordingly.

In `@src/features/study/group/api/group-study-types.ts`:
- Line 132: BasicInfoDetail's deletedAt is currently typed as string but must
match the Timestamps interface which defines deletedAt as string | null; update
the deletedAt property on the BasicInfoDetail type to be string | null so the
two interfaces are consistent (check the BasicInfoDetail declaration and compare
to the Timestamps interface to confirm the change).
- Around line 18-22: The OpenAPI-generated enum
GroupStudyBasicInfoResponseDtoStatusEnum is missing 'ENDING_SOON', causing type
mismatches with the local GroupStudyStatus and comparisons like status ===
'ENDING_SOON' in components (study-card-countdown-badge,
my-participating-studies-section, my-study-info-card); fix by either
regenerating the OpenAPI DTOs so the schema includes the ENDING_SOON value
(re-run the generator so GroupStudyBasicInfoResponseDtoStatusEnum contains
'ENDING_SOON') or add a small adapter that maps the generated DTO to your local
shape (create a function that accepts GroupStudyBasicInfoResponseDto,
converts/extends its status to the local GroupStudyStatus including
'ENDING_SOON', and use that adapter where components read basicInfo.status).

---

Outside diff comments:
In `@src/components/modals/create-mission-modal.tsx`:
- Around line 28-31: The Zod schema for the guide field (guide:
z.string().min(1).max(1000)) conflicts with the UI limits (maxCharCount={5000},
maxLength={5000}) in create-mission-modal.tsx; update them to match by either
increasing the Zod max to 5000 or reducing the UI props maxCharCount and
maxLength to 1000 so validation and UI are consistent, and apply the same change
for the other occurrences mentioned (lines around 220 and 226) where the UI
props are set; ensure you update the guide field definition and the
corresponding input/textarea props together and run the form validation flow to
confirm submission succeeds at the intended limit.

In `@src/components/modals/edit-homework-modal.tsx`:
- Around line 130-137: The displayed maxCharCount (maxCharCount={5000})
disagrees with the actual input limit (TextAreaInput id="textContent" has
maxLength={1000} and the validation schema uses .max(1000)); fix by making these
values consistent: either change maxCharCount to 1000 to match
TextAreaInput.maxLength and the schema, or increase TextAreaInput.maxLength and
the schema .max(...) to 5000 if you intend a 5000‑char limit—update
maxCharCount, TextAreaInput maxLength, and the validation rule together so all
three (maxCharCount, TextAreaInput id="textContent" maxLength, and the schema
.max) match.

---

Nitpick comments:
In `@src/app/`(service)/layout.tsx:
- Around line 41-48: Remove the duplicated overflow-x-hidden by deleting it from
the inner container: the html element already has className="overflow-x-hidden",
so update the inner div (the element with className="flex min-h-screen w-full
flex-col overflow-x-hidden") to remove overflow-x-hidden (leaving
className="flex min-h-screen w-full flex-col"); if you intended defensive
duplication instead, leave as-is and add a comment explaining the reason.

In `@src/components/modals/discretionary-evaluation-modal.tsx`:
- Around line 12-17: DiscretionaryEvaluationFormSchema currently enforces only
min length for the content field; add a max length check using the existing
EVALUATION_CONTENT_MAX_LENGTH constant so the schema validates content.length <=
EVALUATION_CONTENT_MAX_LENGTH (e.g., chain .max(...) onto the z.string() for the
content property) and provide an appropriate error message; update
DiscretionaryEvaluationFormSchema to reference EVALUATION_CONTENT_MAX_LENGTH to
keep validation consistent with TextAreaInput.

In `@src/components/ui/study-active-ticker.tsx`:
- Line 62: Replace the template-string class concatenation on the div with a
merged class approach using clsx and tailwind-merge: build the static classes
("rounded-200 bg-transparent pt-400 pb-150") and the incoming className via
clsx, then pass the result through twMerge before assigning to the div; update
the JSX in the component (the div that currently uses `className={`rounded-200
bg-transparent pt-400 pb-150 ${className}`}`) to use twMerge(clsx(...)) so
conflicting Tailwind classes are resolved.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60bcced and ba9d911.

📒 Files selected for processing (35)
  • src/app/(admin)/layout.tsx
  • src/app/(landing)/layout.tsx
  • src/app/(service)/insights/ui/blog-detail-page.tsx
  • src/app/(service)/layout.tsx
  • src/components/card/study-card.tsx
  • src/components/filtering/study-filter.tsx
  • src/components/modals/create-mission-modal.tsx
  • src/components/modals/discretionary-evaluation-modal.tsx
  • src/components/modals/edit-homework-modal.tsx
  • src/components/modals/edit-mission-modal.tsx
  • src/components/modals/question-modal.tsx
  • src/components/modals/submit-homework-modal.tsx
  • src/components/pages/group-study-detail-page.tsx
  • src/components/pages/premium-study-detail-page.tsx
  • src/components/section/group-study-info-section.tsx
  • src/components/section/inquiry-section.tsx
  • src/components/section/my-participating-studies-section.tsx
  • src/components/summary/study-info-summary.tsx
  • src/components/ui/form/form-field.tsx
  • src/components/ui/image-upload-input.tsx
  • src/components/ui/study-active-ticker.tsx
  • src/components/ui/study-card-countdown-badge.tsx
  • src/components/ui/toast.tsx
  • src/components/voting/voting-create-modal.tsx
  • src/features/my-page/ui/my-study-info-card.tsx
  • src/features/my-page/ui/profile-edit-modal.tsx
  • src/features/my-page/ui/profile-info-edit-modal.tsx
  • src/features/study/group/api/group-study-types.ts
  • src/features/study/group/const/group-study-const.ts
  • src/features/study/group/model/group-study-form.schema.ts
  • src/features/study/group/ui/delete-group-study-member.tsx
  • src/features/study/group/ui/group-notice-modal.tsx
  • src/features/study/group/ui/group-study-member-item.tsx
  • src/features/study/group/ui/step/step3-group.tsx
  • src/features/study/interview/ui/study-done-modal.tsx
💤 Files with no reviewable changes (1)
  • src/components/section/inquiry-section.tsx

Comment on lines +85 to +88
const url = new URL(uploadUrl);
const relativePath = url.pathname.replace(/^\/api\/v1\//, '') + url.search;

await axiosInstanceForMultipart.put(relativePath, formData);
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 read the file at the specified lines
cat -n src/components/modals/question-modal.tsx | sed -n '80,95p'

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

Length of output: 677


🏁 Script executed:

# Search for uploadUrl/imageUploadUrl type definitions and usage
rg -n "imageUploadUrl|uploadUrl" -C 5 src

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

Length of output: 6949


🏁 Script executed:

# Check for API response types and interfaces
rg -n "type.*Upload|interface.*Upload" -C 3 src

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

Length of output: 1075


🏁 Script executed:

# Search for API documentation or backend specs
fd -e md -e yaml -e json . | xargs grep -l "imageUploadUrl\|uploadUrl" 2>/dev/null | head -20

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

Length of output: 135


🏁 Script executed:

# Look for test files that might show expected API responses
fd -e test.ts -e spec.ts -e test.tsx | xargs grep -l "imageUploadUrl\|uploadUrl" 2>/dev/null

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

Length of output: 66


🏁 Script executed:

# Check for any backend configuration or API response examples
rg -n "https://|http://|/api/v1" src/features/study/group/api/question-api.ts -C 10

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

Length of output: 64


🏁 Script executed:

# Check if there are any comments or JSDoc about the URL format
rg -n "imageUploadUrl" src -B 3 -A 3

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

Length of output: 1219


🏁 Script executed:

# Check the comparison code in group-study-form-modal.tsx that uses fetch directly
cat -n src/features/study/group/ui/group-study-form-modal.tsx | sed -n '155,165p'

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

Length of output: 435


🏁 Script executed:

# Search for any axios or fetch usage with uploadUrl/imageUploadUrl
rg -n "axiosInstanceForMultipart\|fetch.*uploadUrl\|fetch.*imageUploadUrl" src -B 2 -A 2

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

Length of output: 64


🏁 Script executed:

# Check if there are environment variables or base URL configs that might indicate URL format
rg -n "BASE_URL|baseURL|NEXT_PUBLIC" src -C 2 | head -30

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

Length of output: 1723


Line 85의 URL 파싱이 상대 경로에서 실패할 수 있습니다.

new URL(uploadUrl)uploadUrl이 상대 경로면 TypeError를 던집니다. 백엔드 API 계약에서 URL 형식(절대/상대)을 명시하지 않으므로, 상대 URL을 반환하는 경우 이미지 업로드가 항상 실패합니다.

수정 제안
-    const url = new URL(uploadUrl);
-    const relativePath = url.pathname.replace(/^\/api\/v1\//, '') + url.search;
+    const parsedUrl = /^https?:\/\//.test(uploadUrl)
+      ? new URL(uploadUrl)
+      : new URL(uploadUrl, window.location.origin);
+    const relativePath =
+      parsedUrl.pathname.replace(/^\/api\/v1\//, '') + parsedUrl.search;

     await axiosInstanceForMultipart.put(relativePath, formData);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/modals/question-modal.tsx` around lines 85 - 88, The code uses
new URL(uploadUrl) which throws for relative paths; update the URL parsing logic
around uploadUrl/relativePath used before calling axiosInstanceForMultipart.put
so it handles both absolute and relative URLs — detect if uploadUrl is absolute
(e.g., starts with http/https) and parse with new URL(uploadUrl) to build
relativePath, otherwise treat uploadUrl as already-relative (or construct a URL
with a base like window.location.origin) and then derive the same
pathname+search string; ensure axiosInstanceForMultipart.put receives the
correct relativePath regardless of uploadUrl format.

Comment on lines +167 to +174
{/* 플로팅 정보 바 */}
<div className="mt-500 w-[1164px]">
<StudyActiveTicker
approvedCount={studyDetail.basicInfo.approvedCount}
maxMembersCount={studyDetail.basicInfo.maxMembersCount}
startDate={studyDetail.basicInfo.startDate}
/>
</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 | 🔴 Critical

StudyActiveTicker에 존재하지 않는 startDate prop 전달

premium-study-detail-page.tsx와 동일한 문제입니다. StudyActiveTicker 컴포넌트는 startDate prop을 지원하지 않습니다 (src/components/ui/study-active-ticker.tsx의 인터페이스 참조).

🐛 수정 제안
      <div className="mt-500 w-[1164px]">
        <StudyActiveTicker
          approvedCount={studyDetail.basicInfo.approvedCount}
          maxMembersCount={studyDetail.basicInfo.maxMembersCount}
-          startDate={studyDetail.basicInfo.startDate}
        />
      </div>
📝 Committable suggestion

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

Suggested change
{/* 플로팅 정보 바 */}
<div className="mt-500 w-[1164px]">
<StudyActiveTicker
approvedCount={studyDetail.basicInfo.approvedCount}
maxMembersCount={studyDetail.basicInfo.maxMembersCount}
startDate={studyDetail.basicInfo.startDate}
/>
</div>
{/* 플로팅 정보 바 */}
<div className="mt-500 w-[1164px]">
<StudyActiveTicker
approvedCount={studyDetail.basicInfo.approvedCount}
maxMembersCount={studyDetail.basicInfo.maxMembersCount}
/>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/pages/group-study-detail-page.tsx` around lines 167 - 174,
You’re passing a startDate prop to StudyActiveTicker even though
StudyActiveTickerProps (in src/components/ui/study-active-ticker.tsx) doesn’t
define it; remove the startDate={studyDetail.basicInfo.startDate} from the
StudyActiveTicker usage in group-study-detail-page.tsx, or if the ticker must
display the start date, add startDate to the StudyActiveTickerProps interface
and implement its handling inside StudyActiveTicker (adjust prop types and
rendering accordingly).

Comment on lines +169 to +175
<div className="mt-500 w-[1164px]">
<StudyActiveTicker
approvedCount={studyDetail.basicInfo.approvedCount}
maxMembersCount={studyDetail.basicInfo.maxMembersCount}
startDate={studyDetail.basicInfo.startDate}
/>
</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 | 🔴 Critical

StudyActiveTicker에 존재하지 않는 startDate prop 전달

StudyActiveTicker 컴포넌트의 인터페이스(src/components/ui/study-active-ticker.tsx 참조)에는 startDate prop이 정의되어 있지 않습니다. 현재 컴포넌트는 approvedCount, maxMembersCount, viewCount, className만 받습니다.

🐛 수정 제안
      <div className="mt-500 w-[1164px]">
        <StudyActiveTicker
          approvedCount={studyDetail.basicInfo.approvedCount}
          maxMembersCount={studyDetail.basicInfo.maxMembersCount}
-          startDate={studyDetail.basicInfo.startDate}
        />
      </div>
📝 Committable suggestion

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

Suggested change
<div className="mt-500 w-[1164px]">
<StudyActiveTicker
approvedCount={studyDetail.basicInfo.approvedCount}
maxMembersCount={studyDetail.basicInfo.maxMembersCount}
startDate={studyDetail.basicInfo.startDate}
/>
</div>
<div className="mt-500 w-[1164px]">
<StudyActiveTicker
approvedCount={studyDetail.basicInfo.approvedCount}
maxMembersCount={studyDetail.basicInfo.maxMembersCount}
/>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/pages/premium-study-detail-page.tsx` around lines 169 - 175,
The StudyActiveTicker invocation is passing a non-existent prop startDate
(studyDetail.basicInfo.startDate); remove this prop from the JSX in
premium-study-detail-page.tsx so the component only receives approvedCount and
maxMembersCount (and any of the accepted props viewCount or className if
needed), or alternatively add startDate to the StudyActiveTicker prop interface
in src/components/ui/study-active-ticker.tsx and handle it inside
StudyActiveTicker — update either the usage (remove startDate) or the component
prop types/implementation (add startDate) to make them consistent.

approvedCount: number;
maxMembersCount: number;
startDate: string; // YYYY-MM-DD
startDate: 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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# 먼저 파일의 전체 내용을 확인하여 prop 정의와 사용 여부를 파악
cat -n src/components/ui/study-active-ticker.tsx

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

Length of output: 2760


🏁 Script executed:

# 호출부 검색
rg "<StudyActiveTicker" src --type tsx --type ts -C3

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

Length of output: 109


🏁 Script executed:

# props 인터페이스 확인
rg -A20 "interface.*Props" src/components/ui/study-active-ticker.tsx

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

Length of output: 605


🏁 Script executed:

rg "StudyActiveTicker" src -t ts -t tsx -C3

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

Length of output: 109


🏁 Script executed:

# 또는 파일 내용으로 검색
rg "StudyActiveTicker" src

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

Length of output: 707


🏁 Script executed:

rg -B2 -A10 "StudyActiveTicker" src/components/pages/premium-study-detail-page.tsx

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

Length of output: 1297


🏁 Script executed:

rg -B2 -A10 "StudyActiveTicker" src/components/pages/group-study-detail-page.tsx

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

Length of output: 1363


인터페이스에서 미사용 startDate prop을 제거하고 className 병합을 clsx로 리팩터링하세요.

startDate는 인터페이스에 정의되어 있으나 함수 매개변수에서 구조 분해되지 않으며 컴포넌트 본문에서 사용되지 않습니다. 호출부에서는 이미 전달하고 있으므로 인터페이스에서 제거 후 호출부 수정이 필요합니다.

또한 62번 줄과 67-71번 줄의 className 동적 결합이 템플릿 문자열을 사용하고 있으므로, 가이드라인에 따라 clsxtailwind-merge 또는 class-variance-authority 사용으로 변경하세요.

수정 대상 코드
// 제거할 부분 (line 9)
startDate: string;

// 리팩터링 필요 (line 62)
<div className={`rounded-200 bg-transparent pt-400 pb-150 ${className}`}>

// 리팩터링 필요 (lines 67-71)
className={`absolute inset-0 flex items-center gap-150 transition-all duration-500 ${
  index === currentIndex
    ? 'translate-y-0 opacity-100'
    : 'translate-y-full opacity-0'
}`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/study-active-ticker.tsx` at line 9, Remove the unused
startDate prop from the component props interface in study-active-ticker.tsx and
update all call sites to stop passing startDate; then refactor className
concatenations to use clsx (and tailwind-merge or cva if needed) instead of
template strings: replace the outer div that currently uses
`className={`rounded-200 bg-transparent pt-400 pb-150 ${className}`}` with
clsx/tn merge combining "rounded-200 bg-transparent pt-400 pb-150" and the
incoming className prop, and replace the inner element that computes classes
based on `index === currentIndex` (currently using a ternary in a template
string) with clsx that conditionally applies 'translate-y-0 opacity-100' when
index === currentIndex and 'translate-y-full opacity-0' otherwise; ensure you
import clsx (and twMerge/cva if chosen) and update the props/interface and any
tests or callers accordingly.

Comment on lines +18 to +22
export type GroupStudyStatus =
| 'RECRUITING'
| 'ENDING_SOON'
| 'IN_PROGRESS'
| 'COMPLETED';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# 1. 로컬 GroupStudyStatus 타입 정의 확인
echo "== Local GroupStudyStatus type ==" 
cat -n src/features/study/group/api/group-study-types.ts | head -30

echo -e "\n== Check if file exists and full content if small =="
wc -l src/features/study/group/api/group-study-types.ts

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

Length of output: 1281


🏁 Script executed:

# 2. OpenAPI 생성 타입 파일 찾기
echo "== Finding OpenAPI generated files =="
fd -t f "\.ts$" src/api/openapi/ 2>/dev/null | head -20 || echo "Directory not found"

# Alternative: search for group-study related files
echo -e "\n== Finding group-study related API files =="
find . -name "*group-study*" -type f 2>/dev/null | grep -E "\.(ts|tsx)$" || echo "No files found"

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

Length of output: 6587


🏁 Script executed:

# 3. 다운스트림 컴포넌트 확인
echo "== Checking downstream component files =="
fd -t f "study-info-summary.tsx" 2>/dev/null
fd -t f "study-card.tsx" 2>/dev/null

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

Length of output: 363


🏁 Script executed:

# 4. ENDING_SOON 사용처 검색
echo "== Searching for ENDING_SOON usage =="
rg "ENDING_SOON" --type ts --type tsx -n 2>/dev/null | head -30 || echo "Not found"

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

Length of output: 119


🏁 Script executed:

# 5. OpenAPI 관련 상태 타입 검색
echo "== Searching for status type definitions in OpenAPI =="
rg "type.*Status|status.*\:" src/ -t ts -t tsx -B2 -A2 2>/dev/null | head -50 || echo "Not found"

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

Length of output: 136


🏁 Script executed:

# OpenAPI GroupStudyFullResponseDto 확인
echo "== Checking GroupStudyFullResponseDto ==​"
cat -n src/api/openapi/models/group-study-full-response-dto.ts | head -50

echo -e "\n== File size check =="
wc -l src/api/openapi/models/group-study-full-response-dto.ts

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

Length of output: 1553


🏁 Script executed:

# 다운스트림 컴포넌트에서 상태 비교 확인
echo "== study-info-summary.tsx around line 148 =="
sed -n '140,160p' src/components/summary/study-info-summary.tsx

echo -e "\n== study-card.tsx around line 136 =="
sed -n '130,150p' src/components/card/study-card.tsx

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

Length of output: 1634


🏁 Script executed:

# OpenAPI 모델들에서 status 필드 찾기
echo "== Searching status in OpenAPI models =="
rg "status" src/api/openapi/models/group-study*.ts -A2 -B2 | head -80

echo -e "\n== Searching for StatusEnum or similar =="
rg "enum.*Status|Status.*=" src/api/openapi/models/group-study*.ts -B2 -A5 | head -80

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

Length of output: 12084


🏁 Script executed:

# GroupStudyListItemDto와 다른 주요 모델들 확인
echo "== GroupStudyListItemDto =="
cat -n src/api/openapi/models/group-study-list-item-dto.ts | head -60

echo -e "\n== GroupStudyDetailResponse =="
cat -n src/api/openapi/models/group-study-detail-response.ts | head -60

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

Length of output: 2252


🏁 Script executed:

# Full verification of the mismatch
echo "== OpenAPI GroupStudyBasicInfoResponseDtoStatusEnum full definition =="
cat -n src/api/openapi/models/group-study-basic-info-response-dto.ts | grep -A5 "GroupStudyBasicInfoResponseDtoStatusEnum"

echo -e "\n== Confirm downstream usage pattern =="
rg "basicInfo.*status|status.*ENDING_SOON" src/components/ src/features/ -B2 -A2 | head -40

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

Length of output: 3557


OpenAPI DTO의 상태 열거형이 ENDING_SOON을 포함하지 않아 타입 불일치가 발생합니다.

로컬 GroupStudyStatus 타입(라인 18-22)에 ENDING_SOON이 추가되었으나, OpenAPI 자동 생성 타입 GroupStudyBasicInfoResponseDtoStatusEnum'RECRUITING' | 'IN_PROGRESS' | 'COMPLETED'만 포함하고 있습니다. 이로 인해 다음 컴포넌트들에서 basicInfo.status === 'ENDING_SOON' 비교 시 TypeScript 타입 에러가 발생합니다:

  • study-card-countdown-badge.tsx: status === 'ENDING_SOON'
  • my-participating-studies-section.tsx: study.status === 'ENDING_SOON'
  • my-study-info-card.tsx: status === 'ENDING_SOON' (여러 곳)

해결 방법: OpenAPI 스키마의 상태 열거형을 재생성하여 ENDING_SOON을 포함하거나, 또는 DTO 응답을 감싸는 어댑터를 생성하여 status 필드를 확장하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/study/group/api/group-study-types.ts` around lines 18 - 22, The
OpenAPI-generated enum GroupStudyBasicInfoResponseDtoStatusEnum is missing
'ENDING_SOON', causing type mismatches with the local GroupStudyStatus and
comparisons like status === 'ENDING_SOON' in components
(study-card-countdown-badge, my-participating-studies-section,
my-study-info-card); fix by either regenerating the OpenAPI DTOs so the schema
includes the ENDING_SOON value (re-run the generator so
GroupStudyBasicInfoResponseDtoStatusEnum contains 'ENDING_SOON') or add a small
adapter that maps the generated DTO to your local shape (create a function that
accepts GroupStudyBasicInfoResponseDto, converts/extends its status to the local
GroupStudyStatus including 'ENDING_SOON', and use that adapter where components
read basicInfo.status).

rejectedCount: number;
kickedCount: number;
deletedAt: string | null;
deletedAt: 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:

wc -l src/features/study/group/api/group-study-types.ts

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

Length of output: 135


🏁 Script executed:

cat -n src/features/study/group/api/group-study-types.ts | sed -n '125,140p'

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

Length of output: 542


🏁 Script executed:

cat -n src/features/study/group/api/group-study-types.ts | sed -n '190,205p'

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

Length of output: 577


🏁 Script executed:

cat -n src/features/study/group/api/group-study-types.ts | sed -n '110,135p'

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

Length of output: 1002


🏁 Script executed:

cat -n src/features/study/group/api/group-study-types.ts | head -n 130 | tail -n 50

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

Length of output: 1989


🏁 Script executed:

rg "interface BasicInfo[^D]" src/features/study/group/api/group-study-types.ts -A 15

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

Length of output: 1435


🏁 Script executed:

rg "extends.*Timestamps|Timestamps.*extends" src/features/study/group/api/group-study-types.ts -B 2 -A 5

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

Length of output: 546


🏁 Script executed:

rg "BasicInfoDetail|DetailInfo" src/features/study/group/api/group-study-types.ts | head -20

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

Length of output: 535


🏁 Script executed:

cat -n src/features/study/group/api/group-study-types.ts | sed -n '1,70p'

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

Length of output: 2517


🏁 Script executed:

cat -n src/features/study/group/api/group-study-types.ts | sed -n '40,80p'

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

Length of output: 1396


BasicInfoDetaildeletedAt 타입을 Timestamps 인터페이스와 일치하도록 수정해 주세요.

Line 132의 deletedAt: string은 활성 스터디 응답의 null 값을 수용하지 못합니다. 동일 파일 Line 198의 Timestamps 인터페이스에서는 deletedAt: string | null로 정의되어 있어 타입 계약이 불일치합니다. BasicInfoDetaildeletedAtstring | null로 변경하여 일관성을 유지하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/study/group/api/group-study-types.ts` at line 132,
BasicInfoDetail's deletedAt is currently typed as string but must match the
Timestamps interface which defines deletedAt as string | null; update the
deletedAt property on the BasicInfoDetail type to be string | null so the two
interfaces are consistent (check the BasicInfoDetail declaration and compare to
the Timestamps interface to confirm the change).

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.

♻️ Duplicate comments (1)
src/components/pages/premium-study-detail-page.tsx (1)

168-172: ⚠️ Potential issue | 🔴 Critical

StudyActiveTicker에 지원되지 않는 startDate prop 전달을 제거해 주세요.

Line 171에서 startDate를 전달하고 있는데, 현재 StudyActiveTicker의 props와 불일치하여 타입체크/빌드 실패를 유발할 수 있습니다.

🐛 제안 수정안
       <StudyActiveTicker
         approvedCount={studyDetail.basicInfo.approvedCount}
         maxMembersCount={studyDetail.basicInfo.maxMembersCount}
-        startDate={studyDetail.basicInfo.startDate}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/pages/premium-study-detail-page.tsx` around lines 168 - 172,
The StudyActiveTicker invocation is passing an unsupported prop startDate
(StudyActiveTicker approvedCount, maxMembersCount are valid) which causes
type/build failures; remove the startDate={studyDetail.basicInfo.startDate} prop
from the StudyActiveTicker call so only approvedCount and maxMembersCount are
passed, and run the type checker to confirm the error is resolved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/components/pages/premium-study-detail-page.tsx`:
- Around line 168-172: The StudyActiveTicker invocation is passing an
unsupported prop startDate (StudyActiveTicker approvedCount, maxMembersCount are
valid) which causes type/build failures; remove the
startDate={studyDetail.basicInfo.startDate} prop from the StudyActiveTicker call
so only approvedCount and maxMembersCount are passed, and run the type checker
to confirm the error is resolved.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba9d911 and 2ab8b4e.

📒 Files selected for processing (1)
  • src/components/pages/premium-study-detail-page.tsx

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.

1 participant