멘토링 등록 저장 흐름, 공개 노출 분기, 핵심키워드 정책 안정화#439
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough멘토링 기능 전반을 대규모로 개편: 단계형 멘토 등록 흐름 도입, 경력 항목 및 스케줄 텍스트 드래프트 추가, 핵심 키워드 정규화·계약 도입, 공개 준비도 로직과 관련 UI/라우팅·검증/저장 흐름을 대거 변경·추가함. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant UI as 등록 UI (Form)
participant Controller as RegistrationController
participant Mapper as SettingsMapper
participant API as MentorAPI
participant Server as 서버
User->>UI: 단계별 입력 및 저장 요청
UI->>Controller: onSubmit(values)
Controller->>Mapper: buildMentorSettingsUpsertRequest({ values, registrationOptions, persistedPredefinedCoreKeywords })
Mapper->>Mapper: normalize careerEntries, core keywords, scheduleDrafts
Mapper->>API: upsertMyMentorSettings({ values, registrationOptions, persistedPredefinedCoreKeywords })
API->>Server: PUT /mentors/me (payload)
Server-->>API: 200 / validation error (detail)
alt 성공
API-->>Controller: result
Controller->>UI: show welcome modal / update state
else 검증오류
API-->>Controller: error(detail)
Controller->>Controller: resolveMentorRegistrationServerErrorTarget(detail)
Controller->>UI: focus invalid step / show field errors
end
sequenceDiagram
participant Client as 멘토 상세 페이지
participant API as Mentor API
participant Readiness as PublicReadiness 모듈
participant UI as 상세 UI
Client->>API: GET mentor profile
API-->>Client: profile + settings
Client->>Readiness: getMentorPublicReadiness(mentor)
Readiness->>Readiness: 체크 필수 항목(정보, 스케줄, 가격 등)
Readiness-->>Client: readiness 결과
alt 상세 잠김
Client->>UI: 렌더 잠금 오버레이 및 신청 비활성화
else 상세 공개
alt 신청 불가
Client->>UI: 신청 비활성화 안내
else 신청 가능
Client->>UI: 신청 CTA 활성화
end
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/utils/error-handler.ts (1)
500-531:⚠️ Potential issue | 🟠 Major
detail원문을 그대로 Sentry에 싣는 건 위험합니다.Line 500과 Line 531은 백엔드가 임의 구조로 채우는
detail을 그대로 로그/Sentry extra로 보냅니다. 이 필드에는 검증 실패 payload, 식별자, 내부 진단값이 섞일 수 있어서 개인정보 유출과 이벤트 크기 초과를 동시에 만들 수 있습니다. 허용한 키만 추리거나 마스킹/길이 제한 후 전송해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/error-handler.ts` around lines 500 - 531, The code currently forwards errorInfo-derived detail (from extractApiErrorDetail) directly into logData and Sentry extras; instead, implement a sanitizer function (e.g., sanitizeErrorDetail) that accepts the raw detail and returns a safe object by whitelisting allowed keys, masking sensitive values (IDs, PII), and truncating long strings/arrays to a max length, then replace usages of detail in the logData construction and inside Sentry.withScope (where scope.setExtra('detail', ... ) is called) with the sanitized result; ensure extractApiErrorDetail and errorInfo remain inputs but never send the raw detail to Sentry or external logs.src/features/mentoring/ui/registration/mentor-markdown-editor.tsx (1)
694-694:⚠️ Potential issue | 🟡 Minor임의의 Tailwind 값 사용이 금지되어 있습니다.
w-[180px]는 임의의 Tailwind 값입니다. 프로젝트 디자인 토큰을 사용하세요.As per coding guidelines: "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-markdown-editor.tsx` at line 694, Replace the forbidden Tailwind arbitrary value w-[180px] in the className on the element inside mentor-markdown-editor.tsx with the appropriate project design token class; locate the className string that currently reads "accent-background-brand-default w-[180px]" and swap w-[180px] for the corresponding design token (e.g., the project's width token such as w-44 / token name used across the codebase) or a utility class mapped from your design tokens so no arbitrary bracketed value remains.
🧹 Nitpick comments (23)
src/features/mentoring/ui/common/mentoring-guide-modal.tsx (1)
21-24:GUIDE_CONTENT의 description과 런타임 조건부 텍스트 간 중복이 있습니다.
GUIDE_CONTENT[1].description(line 23)에 텍스트를 정의했지만, 렌더링 시(line 82-84) 해당 항목의item.description을 사용하지 않고noteConsultationGuideLine을 포함한 별도의 템플릿 문자열로 완전히 대체됩니다. 이로 인해 line 23의 description은 실질적으로 사용되지 않는 dead code가 됩니다.가독성과 유지보수성을 위해 description을 placeholder로 단순화하거나, 조건부 로직을
GUIDE_CONTENT생성 시점으로 옮기는 것을 고려해 주세요.♻️ 제안: GUIDE_CONTENT를 함수로 변경하여 일관성 유지
-const GUIDE_CONTENT = [ +const getGuideContent = (noteConsultationEnabled: boolean) => { + const noteConsultationLine = noteConsultationEnabled + ? '- /note-consultation' + : '쪽지상담함은 현재 제공하지 않습니다.'; + + return [ { title: '멘토링이 뭔가요?', description: '멘토와 멘티를 1:1로 연결해 약속을 잡아주는 기능입니다...', }, { title: '관리 페이지는 어떻게 구분되나요?', description: - '유저(멘티) 관점 관리: 내가 신청한 멘토링 내역과 후기 작성을 관리합니다.\n- /my-study-review\n\n쪽지상담함과 멘토 운영 관리 화면은 현재 제공하지 않습니다.', + `유저(멘티) 관점 관리: 내가 신청한 멘토링 내역과 후기 작성을 관리합니다.\n- /my-study-review\n\n${noteConsultationLine}\n멘토 운영 관리 화면도 현재 제공하지 않습니다.`, }, // ... 나머지 항목들 -]; + ]; +};그 후 렌더링 시 조건부 로직 제거:
- {item.title === '관리 페이지는 어떻게 구분되나요?' - ? `유저(멘티) 관점 관리:...\n${noteConsultationGuideLine}\n...` - : item.description} + {item.description}Also applies to: 57-59, 82-84
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/common/mentoring-guide-modal.tsx` around lines 21 - 24, GUIDE_CONTENT currently contains dead/duplicated description text because the render logic replaces item.description with a conditional template using noteConsultationGuideLine; fix by making GUIDE_CONTENT a function (e.g., getGuideContent) that composes each entry's description up front using noteConsultationGuideLine (or leaves a simple placeholder if you prefer), then update the render to use item.description directly; ensure references to GUIDE_CONTENT, getGuideContent (or your chosen function name), item.description and noteConsultationGuideLine are updated so conditional logic lives in data creation, not in the render.src/features/admin/mentoring/model/use-admin-mentoring-overview-query.ts (1)
306-314: React Query 유사 인터페이스 유지에 대한 참고사항.
isLoading,isFetching,isError,error등 React Query 스타일의 반환 구조를 유지하여 하위 호환성을 보장하고 있습니다.isFetching: false하드코딩은 실제 비동기 fetch가 없으므로 의미론적으로 정확합니다. 다만, 향후 이 훅이 확장되어 실제 fetching이 필요해지면 인터페이스 재설계가 필요할 수 있습니다.훅 이름의 "Query" 접미사가 React Query 사용을 암시하지만 실제로는 파생 상태입니다. 코드베이스 전반의 네이밍 컨벤션과 일관성을 유지한다면 현재대로 유지해도 무방하나, 혼동을 줄이기 위해
useAdminMentoringOverview와 같은 이름을 고려해 볼 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/admin/mentoring/model/use-admin-mentoring-overview-query.ts` around lines 306 - 314, The hook's name implies a React Query hook while it actually returns derived state; rename the exported hook function from useAdminMentoringOverviewQuery to useAdminMentoringOverview (and update its default/named export and all import sites) to avoid confusion, keep the returned React Query-like shape (hasHydrated, isLoading, isFetching, isError, error, mentors, metrics) as-is for backward compatibility, and add a brief inline comment near hasHydrated/overviewState explaining that isFetching is intentionally static because no async fetch occurs.src/components/my-page/profile.tsx (1)
104-127: 전화번호 블록의 로딩/에러 상태 체크 일관성 검토 권장인증 배지(lines 96-101)와 미인증 프롬프트(lines 216-219)는
!resolvedIsLoading && !resolvedIsError조건을 체크하지만, 전화번호 블록은 이 조건이 없습니다.로딩 중일 때 캐시된 전화번호를 표시하는 것이 의도된 UX라면 현재 구현이 맞습니다. 다만 배지와 동일한 조건을 적용하면 더 일관된 상태 표시가 가능합니다.
🔧 일관성을 위한 선택적 수정
{!hidePhoneNumber && + !resolvedIsLoading && + !resolvedIsError && resolvedIsVerified && resolvedPhoneNumber && (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/my-page/profile.tsx` around lines 104 - 127, The phone-number render condition should match the badge/prompt loading/error checks for consistency: update the JSX conditional that currently uses {!hidePhoneNumber && resolvedIsVerified && resolvedPhoneNumber} to also require !resolvedIsLoading && !resolvedIsError so it becomes {!hidePhoneNumber && !resolvedIsLoading && !resolvedIsError && resolvedIsVerified && resolvedPhoneNumber}; locate this in the component where formatPhoneNumber(resolvedPhoneNumber) and setIsVerificationModalOpen are used and apply the same gating as the 인증 배지/미인증 프롬프트.src/features/admin/mentoring/ui/mentor-applications-page-client.tsx (1)
287-293: 서버 계약 완료 시 업데이트 추적 필요정산 정보 인증 필드가 정적 플레이스홀더("서버 정산 readiness 계약 필요")로 변경되었습니다. 서버 계약이 완료되면 이 부분을 실제 로직으로 대체해야 하므로, TODO 주석이나 이슈로 추적하는 것을 권장합니다.
💡 TODO 주석 추가 제안
<KeyValueRow label="정산 정보 인증" columnsClassName="grid-cols-[110px_minmax(0,1fr)]" valueAs="p" > + {/* TODO: 서버 정산 readiness 계약 완료 후 실제 verified 상태 표시로 대체 */} 서버 정산 readiness 계약 필요 </KeyValueRow>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/admin/mentoring/ui/mentor-applications-page-client.tsx` around lines 287 - 293, The static placeholder "서버 정산 readiness 계약 필요" inside the KeyValueRow used in the MentorApplicationsPageClient component should be made explicit as a TODO and wired for future dynamic data: replace the hardcoded text with a clear TODO comment (e.g., // TODO: replace with server-provided settlementVerification/status when server contract completes) and return a sensible fallback UI (e.g., "미인증" or a loading/placeholder state) while awaiting actual data; keep the KeyValueRow props (label="정산 정보 인증", columnsClassName, valueAs="p") unchanged and ensure the unique identifier KeyValueRow and the containing component (MentorApplicationsPageClient) are referenced in the comment so future work can find and implement the server-driven logic.src/features/admin/mentoring/model/mock-seed.ts (1)
60-65: seed의schedule과scheduleDrafts를 같은 상태로 맞춰 주세요.Line 60-65에서
schedule.weekly는 채워져 있는데scheduleDrafts는 항상 빈 값입니다. 이 함수가 server-like 값을 만드는 용도라면, draft를 source of truth로 읽는 폼/미리보기 경로에서 저장된 슬롯이 없는 것처럼 보여 실제 서버 상태와 다른 동작을 만들 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/admin/mentoring/model/mock-seed.ts` around lines 60 - 65, The seed currently sets schedule.weekly but leaves scheduleDrafts as createEmptyMentorScheduleDrafts(), causing mismatched server vs draft state; update the seeding so scheduleDrafts reflects the same data as schedule (e.g., populate scheduleDrafts from schedule.weekly) rather than always using createEmptyMentorScheduleDrafts(); locate the schedule and scheduleDrafts fields in the mock-seed and replace the empty-draft creation with a draft-created-from-schedule approach (use the existing weekly data or a helper to convert schedule.weekly into the draft shape).src/features/mentoring/ui/apply/mentoring-apply-route-client.tsx (1)
18-25: 사용되지 않는 prop을 제거하세요.
selectedTypeprop이 인터페이스에 정의되어 있지만 컴포넌트에서 사용되지 않습니다. 컴포넌트 구조분해에서 제외되었으므로 인터페이스에서도 제거해야 합니다.♻️ 미사용 prop 제거
interface MentoringApplyRouteClientProps { mentorId: number; - selectedType?: MentoringMethodType; }
MentoringMethodTypeimport도 더 이상 필요하지 않다면 함께 제거하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/apply/mentoring-apply-route-client.tsx` around lines 18 - 25, Remove the unused prop from the props interface: delete selectedType from interface MentoringApplyRouteClientProps and remove the MentoringMethodType import if it is no longer used elsewhere; leave the MentoringApplyRouteClient signature using only mentorId so the interface matches the component's destructuring.src/features/admin/mentoring/ui/mentor-registration-detail.tsx (2)
291-298: 중복 문자열 키 충돌 가능성이 있습니다.동일한 질문이 여러 개 존재할 경우
key={question}은 키 충돌을 일으킬 수 있습니다. 코딩 가이드라인에 따라 인덱스를 포함한 키를 사용하세요.♻️ 키 패턴 개선 제안
- {settings.interviewQuestions.map((question) => ( + {settings.interviewQuestions.map((question, index) => ( <p - key={question} + key={`${question}-${index}`} className="font-designer-14r text-text-default" >As per coding guidelines: "For optional ID fields in React keys, always use nullish coalescing with index fallback."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/admin/mentoring/ui/mentor-registration-detail.tsx` around lines 291 - 298, The mapped list uses key={question} which can collide if duplicate questions exist; update the map over settings.interviewQuestions in the MentorRegistrationDetail component (mentor-registration-detail.tsx) to include the index (e.g., map((question, index) => ...)) and set the key using a nullish-coalescing index fallback (use question ?? index or a combined string using question with index) so keys are stable and unique per list item.
223-233: 중복된 조건 검사를 단순화하세요.
hasContactInfo가 이미contactPhone.length > 0으로 정의되어 있으므로, 225번 라인의 내부 조건 검사는 항상 true입니다.♻️ 단순화 제안
{hasContactInfo && ( <Section title="연락처"> - {contactPhone.length > 0 && ( - <KeyValueRow - label="전화" - columnsClassName="grid-cols-[120px_minmax(0,1fr)]" - > - {`${settings?.contactCountryCode ?? ''} ${contactPhone}`.trim()} - </KeyValueRow> - )} + <KeyValueRow + label="전화" + columnsClassName="grid-cols-[120px_minmax(0,1fr)]" + > + {`${settings?.contactCountryCode ?? ''} ${contactPhone}`.trim()} + </KeyValueRow> </Section> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/admin/mentoring/ui/mentor-registration-detail.tsx` around lines 223 - 233, The nested condition is redundant because hasContactInfo is derived from contactPhone.length > 0; remove the inner check and render the KeyValueRow directly when hasContactInfo is true (or alternatively remove hasContactInfo and use contactPhone.length > 0 at the Section level). Update the block that uses hasContactInfo, KeyValueRow, and contactPhone so only a single conditional is used (keep Section and KeyValueRow as-is and use hasContactInfo to guard rendering the phone row).src/features/mentoring/ui/settings/weekly-schedule-grid.tsx (1)
128-131: 불필요한 삼항 연산자
timezone과slotUnitMinutes의 삼항 연산자가 항상 동일한 값을 반환합니다. 이는 의도된 방어적 코드일 수 있지만, 현재 상태에서는 불필요합니다.♻️ 단순화 제안
return { - timezone: value.timezone === 'Asia/Seoul' ? 'Asia/Seoul' : 'Asia/Seoul', - slotUnitMinutes: value.slotUnitMinutes === 30 ? 30 : 30, + timezone: 'Asia/Seoul', + slotUnitMinutes: 30, weekly: createWeekdayRecord((day) => {향후 다른 timezone/slotUnit 지원이 필요하면 그때 분기를 추가하면 됩니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/settings/weekly-schedule-grid.tsx` around lines 128 - 131, The ternary expressions for timezone and slotUnitMinutes always return the same value; remove the redundant conditional logic in the return object and set timezone to 'Asia/Seoul' and slotUnitMinutes to 30 directly (inside the same object that uses createWeekdayRecord for weekly) so the code reads simply and clearly; if you later need defensive branching, add explicit cases around the timezone/slotUnitMinutes assignments or prior validation before this return.src/features/mentoring/ui/mentor-directory/mentor-card.tsx (2)
158-158: 금지된 Tailwind 임의값 사용
min-h-[56px]는 임의값입니다. 프로젝트 커스텀 디자인 토큰을 사용해야 합니다.As per coding guidelines: "Forbidden: Tailwind arbitrary values."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/mentor-directory/mentor-card.tsx` at line 158, The div uses a forbidden Tailwind arbitrary value "min-h-[56px]"; replace that with the project's design token class (e.g., the equivalent token like "min-h-14" or the team's custom token class) inside the MentorCard component (look for the div with className "mb-125 min-h-[56px]") so it uses the approved design token instead of an arbitrary value.
125-125: 금지된 하드코딩 색상 사용
border-[#E5E7EB]는 하드코딩된 색상값입니다. 프로젝트 디자인 토큰을 사용해야 합니다.♻️ 디자인 토큰 대체 제안
- 'h-full cursor-pointer overflow-hidden border border-[`#E5E7EB`] bg-white', + 'h-full cursor-pointer overflow-hidden border border-border-subtle bg-white',As per coding guidelines: "Forbidden: Hard-coded colors and spacing values. Use only
@theme inlinetokens fromglobal.css."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/mentor-directory/mentor-card.tsx` at line 125, The class string in mentor-card.tsx currently uses a hard-coded color token ('border-[`#E5E7EB`]'); update the MentorCard component's className to use the project design token from global.css instead of the literal hex value (replace the hard-coded border color with the appropriate `@theme` inline token for borders, e.g., the theme border token used across the app), ensuring the rest of the utility classes ('h-full cursor-pointer overflow-hidden border bg-white') remain unchanged.src/features/mentoring/api/mentor-profile.mapper.ts (2)
195-203: enum-like 문자열 검증에in연산자 사용 권장코딩 가이드라인에 따르면 백엔드의 enum-like 문자열 타입에는
in연산자 가드를 사용해야 합니다.♻️ `in` 연산자 사용 제안
+const VALID_PUBLIC_READINESS_STAGES = { + DETAIL_PREPARING: true, + APPLY_PREPARING: true, + APPLY_READY: true, +} as const; + const normalizePublicReadinessStage = ( value: unknown, ): MentorProfile['publicReadinessStage'] => { - return value === 'DETAIL_PREPARING' || - value === 'APPLY_PREPARING' || - value === 'APPLY_READY' - ? value + return typeof value === 'string' && value in VALID_PUBLIC_READINESS_STAGES + ? (value as MentorProfile['publicReadinessStage']) : undefined; };As per coding guidelines: "For enum-like string types from backend, use
inoperator guard with fallback instead of simpleastype assertion."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/api/mentor-profile.mapper.ts` around lines 195 - 203, The current normalizePublicReadinessStage function uses direct equality checks; replace this with an 'in' operator guard against a defined enum-like object (e.g. const PublicReadinessStage = { DETAIL_PREPARING: 'DETAIL_PREPARING', APPLY_PREPARING: 'APPLY_PREPARING', APPLY_READY: 'APPLY_READY' }) and then in normalizePublicReadinessStage check typeof value === 'string' && value in PublicReadinessStage and return value (cast to MentorProfile['publicReadinessStage'] if needed) otherwise return undefined; update the function name normalizePublicReadinessStage to use that guard and reference MentorProfile['publicReadinessStage'] as the return type.
38-42: API 기본 경로 형식에 대한 의존성 검토 필요
MENTOR_ASSET_BASE_URL이 정규표현식/\/api\/v1\/?$/를 사용하여/api/v1경로를 제거합니다. API 기본 URL의 형식 변경(예:/api/v2/로의 업그레이드)이 발생하면 이 로직이 제대로 작동하지 않을 수 있습니다. 유사한 패턴이src/app/api/auth/clear-session/route.ts에도 존재하므로, API 버전 관리 전략 변경 시 이러한 모든 위치를 함께 검토하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/api/mentor-profile.mapper.ts` around lines 38 - 42, MENTOR_ASSET_BASE_URL currently strips only a trailing "/api/v1" which will break if the API version path changes; update the logic that builds MENTOR_ASSET_BASE_URL (the constant using process.env.NEXT_PUBLIC_API_PROD_BASE_URL / NEXT_PUBLIC_API_BASE_URL) to remove any trailing "/api" optionally followed by a version segment (e.g., use a pattern like "/api(/v\\d+)?/?$" or parse the URL and drop the pathname segments), and make the same change in the similar occurrence in src/app/api/auth/clear-session/route.ts so both places remain correct when API versioning changes.src/stores/useMentorDirectoryStore.ts (2)
59-68: 매직 넘버 기반 리뷰 식별 로직의 취약성
review.id === mentorId * 10은 리뷰 ID 생성 규칙에 의존하는 취약한 패턴입니다. ID 생성 로직이 변경되면 이 검사가 실패할 수 있습니다.♻️ 명시적 마커 사용 제안
+const GENERATED_WELCOME_REVIEW_MARKER = 'GENERATED_WELCOME'; + const isGeneratedWelcomeReview = ( mentorId: number, review: MentorProfile['reviews'][number], ) => { return ( - review.id === mentorId * 10 && + (review.id === mentorId * 10 || review.id === GENERATED_WELCOME_REVIEW_MARKER) && review.authorName === 'ZERO-ONE' && review.content === GENERATED_WELCOME_REVIEW_CONTENT ); };향후 리뷰 생성 시 명시적 마커를 사용하거나,
authorName과content조합만으로 식별하는 것이 더 안정적입니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/stores/useMentorDirectoryStore.ts` around lines 59 - 68, The current isGeneratedWelcomeReview relies on a fragile magic-number check (review.id === mentorId * 10); change it to detect generated welcome reviews using an explicit marker or, if no marker exists, fall back to matching authorName and GENERATED_WELCOME_REVIEW_CONTENT. Update the isGeneratedWelcomeReview function to first look for a dedicated flag (e.g., review.isGeneratedWelcome or review.metadata?.generatedWelcome) and return true if present and truthy; if that field is absent, return true only when review.authorName === 'ZERO-ONE' && review.content === GENERATED_WELCOME_REVIEW_CONTENT. Ensure you reference and preserve the existing constant GENERATED_WELCOME_REVIEW_CONTENT and the function name isGeneratedWelcomeReview when making the change.
400-405:partialize와migrate에서normalizePersistedMentor중복 호출 제거 권장
partialize에서 저장 전 멘토를 정규화하고(402줄),migrate에서 로드 후 다시 정규화(normalizePersistedState→ 306줄)하고 있습니다.migrate는 모든 데이터 로드 시 항상 실행되므로partialize에서의 정규화는 중복입니다. 정규화를 한 곳으로 통합하세요. (일반적으로migrate에 유지하는 것을 권장)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/stores/useMentorDirectoryStore.ts` around lines 400 - 405, The partialize function currently calls normalizePersistedMentor before persisting, causing duplicate normalization because migrate/normalizePersistedState also normalizes on load; remove the normalizePersistedMentor mapping from partialize and let migrate/normalizePersistedState be the single normalization point so PersistedMentorDirectoryState is only normalized during migrate; update partialize to return memberId, createdMentors (unchanged array), mentorIdByMember, and nextMentorId without calling normalizePersistedMentor.src/features/mentoring/ui/registration/mentor-markdown-content.tsx (1)
246-248: 금지된 Tailwind 임의값 사용
max-h-[400px],max-w-[min(100%,400px)],py-[2px]는 코딩 가이드라인에서 금지하는 임의값입니다. 프로젝트 커스텀 디자인 토큰을 사용해야 합니다.♻️ 디자인 토큰 대체 제안
- '[&_img]:rounded-100 [&_img]:border-border-subtle [&_img]:mb-150 [&_img]:block [&_img]:h-auto [&_img]:max-h-[400px] [&_img]:max-w-[min(100%,400px)] [&_img]:border [&_img]:object-contain', - '[&_code]:rounded-50 [&_code]:bg-background-alternative [&_code]:font-designer-13r [&_code]:px-75 [&_code]:py-[2px]', + '[&_img]:rounded-100 [&_img]:border-border-subtle [&_img]:mb-150 [&_img]:block [&_img]:h-auto [&_img]:max-h-400 [&_img]:max-w-full [&_img]:border [&_img]:object-contain', + '[&_code]:rounded-50 [&_code]:bg-background-alternative [&_code]:font-designer-13r [&_code]:px-75 [&_code]:py-25',As per coding guidelines: "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-markdown-content.tsx` around lines 246 - 248, The class string array in mentor-markdown-content.tsx contains forbidden Tailwind arbitrary values (specifically "max-h-[400px]", "max-w-[min(100%,400px)]" and "py-[2px]")—replace those with the project's custom design tokens (use the corresponding max-height, max-width and padding token classes from your design system) in the same class list so the styling remains equivalent; update any token names to match your Tailwind/token config and run the linter to ensure no arbitrary-value patterns remain.src/features/mentoring/ui/registration/mentor-career-entries-editor.tsx (2)
259-266:watchedCareerEntries[index]undefined 접근 가능성
useWatch의 반환값이 배열 동기화 타이밍에 따라undefined일 수 있습니다. 현재currentEntry?.옵셔널 체이닝으로 일부 보호되어 있지만, 더 명확한 가드가 좋습니다.♻️ 명시적 가드 추가
{typedFields.map((field, index) => { const entryErrors = careerEntryErrors[index]; const currentEntry = watchedCareerEntries[index]; + if (!currentEntry) { + return null; + } const startParts = splitCareerEntryMonth(currentEntry?.startMonth);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-career-entries-editor.tsx` around lines 259 - 266, The map over typedFields can access watchedCareerEntries[index] before useWatch has a value, so add an explicit guard for currentEntry to avoid passing undefined into splitCareerEntryMonth: in the mapping callback (where typedFields.map is used) compute const currentEntry = watchedCareerEntries?.[index]; if currentEntry is falsy, return a safe fallback UI (or continue) instead of calling splitCareerEntryMonth; otherwise call splitCareerEntryMonth(currentEntry.startMonth) and splitCareerEntryMonth(currentEntry.endMonth) and proceed to read entryErrors/descriptionError/startMonthError/endMonthError—this prevents undefined access and keeps the rest of the logic in the same block (referencing typedFields, careerEntryErrors, watchedCareerEntries, splitCareerEntryMonth, and entryErrors).
376-376: 금지된 Tailwind 임의값 사용
grid-cols-[minmax(0,1fr)_120px]는 임의값을 사용하고 있습니다. 동일한 패턴이 Lines 376, 427에서 반복됩니다.As per coding guidelines: "Forbidden: Tailwind arbitrary values. Use only project custom design tokens."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-career-entries-editor.tsx` at line 376, The Tailwind arbitrary class "grid grid-cols-[minmax(0,1fr)_120px] gap-75" (used in the MentorCareerEntriesEditor JSX) violates the forbidden arbitrary-values rule; replace it with a project-approved solution by either using existing design-token utility classes or adding a small CSS module/class (e.g., twoColumnGrid) that sets grid-template-columns: minmax(0, 1fr) 120px and gap using the project's gap token, then swap both occurrences (the two places in MentorCareerEntriesEditor where that class appears) to use the new token-based utility or CSS class instead of the bracketed arbitrary Tailwind value.src/features/mentoring/ui/detail/mentor-detail-header-section.tsx (1)
77-82: 헤더 레이아웃도 임의 Tailwind 값 대신 토큰 클래스로 맞춰주세요.
sm:text-[30px]와xl:grid-cols-[...]는 repo 금지 패턴입니다. 여기서 허용하면 상세 쪽의 예외가 계속 퍼집니다.As per coding guidelines "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/detail/mentor-detail-header-section.tsx` around lines 77 - 82, The header uses forbidden Tailwind arbitrary values: remove sm:text-[30px] from the h1 and xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] from the grid div and replace them with your project's design-token classes (the same kind as font-designer-24b and text-text-strong) that provide the required responsive font size and layout breakpoints; update the h1 (className on the element rendering mentoringTitleLabel) to use the approved small-screen text token and update the surrounding grid div (className on the element starting the grid) to use the approved xl grid/layout token(s) defined in the repo so no arbitrary bracketed Tailwind values remain.src/types/mentoring/settings.ts (1)
54-54: 레코드 shape는type보다interface로 맞춰주세요.
MentorScheduleTextDrafts는 객체 구조를 표현하므로 이 repo의 TypeScript 규칙과 어긋납니다.As per coding guidelines "Use `interface` for defining object shapes in TypeScript, not `type` aliases, when representing object structures."🧩 제안 수정
-export type MentorScheduleTextDrafts = Record<WeekdayKey, string[]>; +export interface MentorScheduleTextDrafts + extends Record<WeekdayKey, string[]> {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/types/mentoring/settings.ts` at line 54, Replace the type alias MentorScheduleTextDrafts with an interface that models the object shape; specifically, change the declaration of MentorScheduleTextDrafts to an interface that maps each WeekdayKey to a string[] (use a mapped property signature over WeekdayKey) so it follows the repo rule of using interface for object shapes and keeps the same runtime shape and typings.src/features/mentoring/ui/registration/mentor-registration-form.tsx (1)
159-165: step UI에 Tailwind arbitrary value가 다시 들어왔습니다.
max-w-[240px],min-w-[132px],mt-[2px],min-h-[420px]는 새 화면 구조를 디자인 토큰 밖으로 빼냅니다. 프로젝트 규칙상 이런 값은 커스텀 토큰/유틸로 승격하거나style로 옮겨서 JSX 클래스는 토큰 기반으로 유지하는 편이 맞습니다.
As per coding guidelines,**/*.{tsx,jsx,ts,js}: Forbidden: Tailwind arbitrary values ... Forbidden: Hard-coded colors and spacing values. Use only project custom design tokens.Also applies to: 482-482, 615-615, 1913-1915
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-registration-form.tsx` around lines 159 - 165, The Tooltip usage is using Tailwind arbitrary values (max-w-[240px], min-w-[132px], mt-[2px], min-h-[420px]) which violates project rules; update the Tooltip props (contentClassName and any wrapper className) to use existing design tokens or project utility classes (create/choose matching custom tokens like token-max-w-sm, token-min-w-xs, token-mt-xxs, token-min-h-lg) or move the specific sizes into a style prop (e.g., style={{ maxWidth: ..., minWidth: ..., marginTop: ..., minHeight: ... }}) while keeping JSX class names token-based; apply the same replacement for the other occurrences in this file where those exact arbitrary classes appear (search for max-w-[240px], min-w-[132px], mt-[2px], min-h-[420px]) and ensure any new token names follow the project’s design token conventions.src/features/mentoring/ui/registration/mentor-registration-page-view.tsx (1)
28-29: 프리뷰 레이아웃에 토큰 밖 임의 값이 다시 들어왔습니다.Line 28-29의
108/60오프셋과 여기서 추가된xl:grid-cols-[…],w-[16px],min-w-[360px],w-[520px]는 모두 디자인 토큰 스케일을 우회합니다. 이 레이아웃이 꼭 필요해도 JSX 클래스는 토큰 기반으로 두고, 가변 값은style이나 로컬 CSS 유틸로 분리하는 쪽이 레포 규칙과 유지보수성에 맞습니다.
As per coding guidelines,**/*.{tsx,jsx,ts,js}: Forbidden: Tailwind arbitrary values ... Forbidden: Hard-coded colors and spacing values. Use only project custom design tokens.Also applies to: 158-178, 243-246, 291-291, 363-368
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-registration-page-view.tsx` around lines 28 - 29, The diff introduces hard-coded spacing and Tailwind arbitrary values that bypass design tokens (e.g., PREVIEW_PANEL_TOP_OFFSET, PREVIEW_PANEL_BOTTOM_OFFSET and classes like xl:grid-cols-[…], w-[16px], min-w-[360px], w-[520px]); replace these by using existing design tokens or move variable values into local CSS utilities/CSS variables or inline style constants: convert PREVIEW_PANEL_TOP_OFFSET and PREVIEW_PANEL_BOTTOM_OFFSET into token-based values or CSS variables and swap any w-[…], min-w-[…], and xl:grid-cols-[…] usages in the JSX to corresponding token classes (or reference the new CSS variables/local utility classes) so the JSX class list only uses project tokens and the arbitrary/flexible measurements live in a scoped style or style prop.src/features/mentoring/model/use-mentor-registration-controller.ts (1)
139-152: 회사 분류 상수 하드코딩 - 유지보수 취약점
normalizeDraftCompanyCategory에서 회사 분류 값이 직접 하드코딩되어 있습니다.COMPANY_CATEGORY_OPTIONS가 이미@/types/mentoring/settings에서 export되고 있으므로, 해당 상수를 참조하여 단일 소스를 유지하는 것이 좋습니다.♻️ 상수 참조로 변경 제안
+import { COMPANY_CATEGORY_OPTIONS } from '@/types/mentoring/settings'; + +type CompanyCategory = (typeof COMPANY_CATEGORY_OPTIONS)[number]; + const normalizeDraftCompanyCategory = ( value: unknown, ): MentorRegistrationFormInputValues['companyCategory'] | undefined => { - if ( - value !== '네카라쿠배' && - value !== 'IT 유니콘' && - value !== '창업' && - value !== '기타' - ) { + if ( + typeof value !== 'string' || + !COMPANY_CATEGORY_OPTIONS.includes(value as CompanyCategory) + ) { return undefined; } - return value; + return value as MentorRegistrationFormInputValues['companyCategory']; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/model/use-mentor-registration-controller.ts` around lines 139 - 152, normalizeDraftCompanyCategory currently hardcodes company category strings; import COMPANY_CATEGORY_OPTIONS from '@/types/mentoring/settings' and use it instead: update the function (normalizeDraftCompanyCategory) to check the incoming value against the set/array derived from COMPANY_CATEGORY_OPTIONS (e.g., map to option.value or the correct key) and return undefined when not present, and add the import for COMPANY_CATEGORY_OPTIONS at the top of the module.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2c0559af-348e-4687-8290-949a98673048
📒 Files selected for processing (76)
src/api/client/api-error.tssrc/api/openapi/models/mentor-settings-upsert-request.tssrc/app/(admin)/admin/mentoring/layout.tsxsrc/app/(service)/(my)/note-consultation/page.tsxsrc/app/global.csssrc/components/common/modals/study-completion-modal.tsxsrc/components/common/ui/badge/index.tsxsrc/components/common/ui/dropdown/single.tsxsrc/components/common/ui/form/multi-item-selector.tsxsrc/components/common/ui/text-action-button.tsxsrc/components/my-page/profile.tsxsrc/features/admin/mentoring/model/mock-seed.tssrc/features/admin/mentoring/model/use-admin-mentoring-overview-query.tssrc/features/admin/mentoring/ui/mentor-applications-page-client.tsxsrc/features/admin/mentoring/ui/mentor-operations-page-client.tsxsrc/features/admin/mentoring/ui/mentor-registration-detail.tsxsrc/features/admin/mentoring/ui/mentoring-dashboard-page-client.tsxsrc/features/admin/mentoring/ui/mentoring-flow-guide.tsxsrc/features/mentoring/api/mentor-api.tssrc/features/mentoring/api/mentor-api.types.tssrc/features/mentoring/api/mentor-core-keyword-contract.tssrc/features/mentoring/api/mentor-profile.mapper.tssrc/features/mentoring/api/mentor-settings.mapper.tssrc/features/mentoring/const/mentor-registration-labels.tssrc/features/mentoring/const/mentoring-list-labels.tssrc/features/mentoring/model/mentor-profile-utils.tssrc/features/mentoring/model/mentor-public-readiness.tssrc/features/mentoring/model/mentor-registration-draft-storage.tssrc/features/mentoring/model/mentor-registration-entry-onboarding.tssrc/features/mentoring/model/mentor-registration-keywords.tssrc/features/mentoring/model/mentor-registration-preview.tssrc/features/mentoring/model/mentor-registration-server-error.tssrc/features/mentoring/model/mentor-settings.tssrc/features/mentoring/model/mentoring-feature-flag.tssrc/features/mentoring/model/use-floating-panel-scroll-nudge.tssrc/features/mentoring/model/use-mentor-registration-controller.tssrc/features/mentoring/model/use-mentor-registration-preview-model.tssrc/features/mentoring/model/use-mentor-registration-preview-panel.tssrc/features/mentoring/model/use-upsert-my-mentor-settings-mutation.tssrc/features/mentoring/ui/apply/mentoring-apply-route-client.tsxsrc/features/mentoring/ui/common/mentor-profile-meta-list.tsxsrc/features/mentoring/ui/common/mentoring-guide-modal.tsxsrc/features/mentoring/ui/detail/mentor-career-history-panel.tsxsrc/features/mentoring/ui/detail/mentor-detail-header-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-method-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-page.tsxsrc/features/mentoring/ui/detail/mentor-detail-review-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsxsrc/features/mentoring/ui/detail/mentor-route-fallback.tsxsrc/features/mentoring/ui/detail/review-stars.tsxsrc/features/mentoring/ui/mentor-directory/mentor-card.tsxsrc/features/mentoring/ui/mentor-directory/mentor-directory-filters.tsxsrc/features/mentoring/ui/pages/my-mentoring-detail-page.tsxsrc/features/mentoring/ui/pages/my-mentoring-page.tsxsrc/features/mentoring/ui/registration/mentor-career-entries-editor.tsxsrc/features/mentoring/ui/registration/mentor-markdown-content.tsxsrc/features/mentoring/ui/registration/mentor-markdown-editor.tsxsrc/features/mentoring/ui/registration/mentor-registration-form.tsxsrc/features/mentoring/ui/registration/mentor-registration-header.tsxsrc/features/mentoring/ui/registration/mentor-registration-page-view.tsxsrc/features/mentoring/ui/settings/settlement-register-modal.tsxsrc/features/mentoring/ui/settings/weekly-schedule-grid.tsxsrc/features/mentoring/ui/temp/contact-seed-actions.tsxsrc/hooks/queries/settlement-account-api.tssrc/hooks/queries/use-auth-mutation.tssrc/hooks/queries/use-phone-verification-status.tssrc/hooks/queries/use-update-user-profile-mutation.tssrc/stores/useMentorDirectoryStore.tssrc/types/mentoring/domain.tssrc/types/mentoring/markdown.tssrc/types/mentoring/registration-options.tssrc/types/mentoring/registration-view.tssrc/types/mentoring/settings.tssrc/types/schemas/mentor-registration-schema.tssrc/types/schemas/mentoring-admin-schema.tssrc/utils/error-handler.ts
| try { | ||
| await onSubmit({ | ||
| bankName: draft.bankName.trim(), | ||
| accountNumber: draft.accountNumber.trim(), | ||
| accountHolder: draft.accountHolder.trim(), | ||
| }); | ||
| closeModal(); | ||
| } catch { | ||
| // Errors are surfaced by the caller toast. Keep the modal open so the | ||
| // user can retry without re-entering the values. | ||
| } |
There was a problem hiding this comment.
저장 실패를 토스트에만 맡기면 모달 안 재시도 맥락이 사라집니다.
catch에서 아무 상태도 갱신하지 않아 실패 후에도 모달 본문에는 이유가 남지 않습니다. 이미 inline error 슬롯이 있으니 analyzeError() 기반 메시지를 errorMessage에 먼저 넣고, Toast는 보조 피드백으로만 남겨 주세요.
As per coding guidelines "Error handling uses analyzeError() from src/utils/error-handler.ts for classification." and "For recoverable failures in UI: use inline error display first, Toast as secondary."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/mentoring/ui/settings/settlement-register-modal.tsx` around
lines 100 - 110, The catch block currently swallows errors so the modal loses
inline context; update the try/catch around the onSubmit call (the block that
calls onSubmit({ bankName: draft.bankName.trim(), accountNumber:
draft.accountNumber.trim(), accountHolder: draft.accountHolder.trim() }) and
closeModal()) to catch the thrown error as e, compute an inline message via
analyzeError(e) and set the component's errorMessage state so the inline error
slot shows the failure, keep the modal open (do not call closeModal on failure),
and still trigger the toast as secondary feedback if desired.
There was a problem hiding this comment.
Rate Limit Exceeded
@Hyeonjun0527 have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 9 seconds before sending another message.
d25a41a to
a4312d2
Compare
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/features/admin/mentoring/model/use-admin-mentoring-overview-query.ts (1)
3-3:⚠️ Potential issue | 🟠 Major실패를 빈 오버뷰로 치환하면 관리자 지표가 실제
0건과 구분되지 않습니다.현재
catch가 모든 예외에서EMPTY_ADMIN_MENTORING_OVERVIEW를 반환해서, 계약 파싱 실패나 런타임 오류가 나도 화면에는 정상적인 빈 상태처럼mentors/metrics가 내려갑니다.isError를 별도로 처리하지 않는 소비처에서는 지표를 0건으로 오인할 수 있으니, 마지막 정상 스냅샷을 유지하거나data자체를 분리하는 편이 안전합니다.hasHydrated전에는 계산을 건너뛰는 것도 같이 묶는 편이 좋습니다.예시 수정안
-import { useMemo } from 'react'; +import { useMemo, useRef } from 'react'; ... + const lastSuccessfulOverviewRef = useRef<AdminMentoringOverviewQueryResult>( + EMPTY_ADMIN_MENTORING_OVERVIEW, + ); + const overviewState = useMemo(() => { + if (!hasHydrated) { + return { + data: lastSuccessfulOverviewRef.current, + error: undefined, + } as const; + } + try { + const data = getAdminMentoringOverview({ + createdMentors, + mentorIdByMember, + requestsByMentor, + sessionsByMentor, + reviewsByMentor, + screeningByMentor, + operationByMentor, + }); + lastSuccessfulOverviewRef.current = data; + return { - data: getAdminMentoringOverview({ - createdMentors, - mentorIdByMember, - requestsByMentor, - sessionsByMentor, - reviewsByMentor, - screeningByMentor, - operationByMentor, - }), + data, error: undefined, } as const; } catch (error) { return { - data: EMPTY_ADMIN_MENTORING_OVERVIEW, + data: lastSuccessfulOverviewRef.current, error: normalizeAdminMentoringOverviewQueryError(error), } as const; } }, [ + hasHydrated, createdMentors, mentorIdByMember, operationByMentor, requestsByMentor, reviewsByMentor, screeningByMentor, sessionsByMentor, ]);Also applies to: 276-313
src/utils/error-handler.ts (1)
500-531:⚠️ Potential issue | 🟠 Major
detail전체를 그대로 Sentry에 실으면 개인 입력값까지 적재될 수 있습니다.
ApiError.detail은 검증 실패 payload처럼 임의의 응답 본문을 담을 수 있는데, 지금은 redaction 없이 개발 로그와 Sentry extra에 모두 실립니다. 멘토링 등록 폼의 이메일/전화번호/상세소개 같은 사용자 입력이 섞일 수 있으니, 안전한 키만 화이트리스트하거나 요약된 값으로 축약해서 보내는 편이 안전합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/error-handler.ts` around lines 500 - 531, The current code sends the raw detail (from extractApiErrorDetail and stored in variable detail) to both console.error and Sentry (inside Sentry.withScope via scope.setExtra), which may include sensitive user input; replace usage of the raw detail with a sanitized or whitelisted summary before logging and reporting: implement or call a sanitizer (e.g., sanitizeApiErrorDetail(detail) or adjust extractApiErrorDetail to return only safe keys like error type, validation keys, and truncated/hashed values) and use that sanitizedDetail in the development console output and in Sentry.withScope (replace scope.setExtra('detail', detail) with scope.setExtra('detail', sanitizedDetail)); ensure errorInfo, stack, and other non-sensitive fields remain unchanged.src/features/admin/mentoring/model/mock-seed.ts (1)
60-65:⚠️ Potential issue | 🔴 Critical
scheduleDrafts를schedule으로부터 생성하도록 수정하세요.
src/features/mentoring/api/mentor-settings.mapper.ts(460줄)에서 서버는createMentorScheduleTextDrafts(schedule)로 draft를 생성합니다.createServerLikeMentorRegistrationValues라는 함수명에 맞게 서버 동작을 정확히 모방하려면, 현재createEmptyMentorScheduleDrafts()를 다음과 같이 변경하세요:
src/features/mentoring/model/mentor-settings.ts에서createMentorScheduleTextDrafts를 import한 후:scheduleDrafts: createMentorScheduleTextDrafts(schedule),이렇게 하면 mock 데이터가 실제 서버 응답과 일치하게 됩니다. 현재 form의 fallback(773-774줄)이 빈 draft를 허용하지만, mock이 "server-like"라면 이 일관성을 유지하는 것이 중요합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/admin/mentoring/model/mock-seed.ts` around lines 60 - 65, The mock seed currently uses createEmptyMentorScheduleDrafts() for scheduleDrafts; update it to mirror server behavior by importing createMentorScheduleTextDrafts from src/features/mentoring/model/mentor-settings.ts and replace the value with createMentorScheduleTextDrafts(schedule) inside createServerLikeMentorRegistrationValues so scheduleDrafts is derived from the schedule object (the variable named schedule) instead of being empty.
♻️ Duplicate comments (5)
src/features/mentoring/ui/settings/settlement-register-modal.tsx (2)
108-118:⚠️ Potential issue | 🟡 Minor실패 이유를 모달 안에도 남겨 주세요.
지금
catch가 비어 있어서 재시도 맥락이 토스트에만 남습니다. 이미 inline error 슬롯이 있으니analyzeError(error)결과를errorMessage에 먼저 넣어 두는 편이, 실패 후 바로 원인을 수정하기 쉽습니다.As per coding guidelines "Error handling uses
analyzeError()fromsrc/utils/error-handler.tsfor classification." and "For recoverable failures in UI: use inline error display first, Toast as secondary."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/settings/settlement-register-modal.tsx` around lines 108 - 118, The catch block for the submission flow currently swallows errors; update the catch to accept the error, run analyzeError(error) and set the component's inline error state (e.g., errorMessage) with that result so the modal shows the failure reason while keeping the modal open for retry; keep the existing caller toast behavior but ensure you call analyzeError inside the catch and assign its output to the error property used by the inline error slot (referencing onSubmit, draft, closeModal, analyzeError and the inline errorMessage state).
125-125: 🛠️ Refactor suggestion | 🟠 Major
max-w-[720px]는 저장소 규칙상 금지된 Tailwind arbitrary value입니다.여기서는 프로젝트 width 토큰/유틸리티 클래스로 치환해 주세요.
As per coding guidelines "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/settings/settlement-register-modal.tsx` at line 125, The Modal.Content currently uses a forbidden Tailwind arbitrary value class "max-w-[720px]"; replace that class with the project's approved width token/utility (i.e., remove "max-w-[720px]" from the className on Modal.Content in SettlementRegisterModal and swap in the corresponding design-system token class such as the project's container/width utility like "max-w-container" or the appropriate "max-w-<design-token>" used elsewhere in the codebase) so that no arbitrary Tailwind values remain.src/features/mentoring/ui/detail/mentor-detail-header-section.tsx (1)
77-81: 🛠️ Refactor suggestion | 🟠 Major헤더 레이아웃에 금지된 arbitrary value가 남아 있습니다.
sm:text-[30px]와xl:grid-cols-[...]는 둘 다 저장소 규칙 위반입니다. 상세 본문에서처럼 프로젝트 토큰/유틸리티 클래스로 바꿔 주세요.As per coding guidelines "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/detail/mentor-detail-header-section.tsx` around lines 77 - 81, The header component in mentor-detail-header-section.tsx contains forbidden Tailwind arbitrary values: replace the sm:text-[30px] on the <h1> and the xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] on the surrounding <div> with the project's design-token/utility classes (use the same token names/pattern used elsewhere in the detail UI), e.g., swap sm:text-[30px] for the corresponding responsive text token utility and swap the xl:grid-cols[...] for the predefined grid-column token or utility that expresses the 0.9/1.1 split; update className strings in the MentorDetailHeaderSection (or the component that renders the shown <h1> and <div>) to use those token utilities instead of arbitrary values.src/features/mentoring/model/mentor-registration-server-error.ts (1)
167-191:⚠️ Potential issue | 🟠 Major
methods[0].enabled류의 bracket 경로는 아직 step 매핑에서 빠집니다.정규식은
price/durationMinutes만 처리하고, 아래 fallback은methods.만 검사합니다. 그래서methods[0].enabled같은 서버 오류는{}로 떨어져 저장 실패 후에도 가격/시간 step으로 이동하지 못합니다.🔧 제안 수정
- if (paramName === 'methods' || paramName.startsWith('methods.')) { + if ( + paramName === 'methods' || + paramName.startsWith('methods.') || + paramName.startsWith('methods[') + ) { return { stepId: MENTOR_REGISTRATION_STEP_IDS.pricingAndTime, }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/model/mentor-registration-server-error.ts` around lines 167 - 191, The logic that maps server paramName to the pricing/time step misses bracketed properties like "methods[0].enabled": update the methodMatch detection to capture property names beyond "price" and "durationMinutes" (e.g., include "enabled") or make the regex/property check generic for any property after methods[...] (change /^methods(?:\[(\d+)\]|\.(\d+))(?:\.(price|durationMinutes))?$/ to allow other property names), update the methodField union/handling in the methodMatch branch (methodField can be 'price'|'durationMinutes'|'enabled'|undefined) and ensure getMethodFieldPath is only called for fields that need a fieldPath (leave fieldPath undefined for "enabled"), and also expand the fallback condition to treat "methods[" prefix the same as "methods." so paramName.startsWith('methods[') routes to MENTOR_REGISTRATION_STEP_IDS.pricingAndTime.src/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsx (1)
221-239: 🛠️ Refactor suggestion | 🟠 Major
mt-[2px]두 군데 모두 금지된 arbitrary value입니다.아이콘 수직 정렬은 토큰 class나 기존 정렬 유틸리티로 맞춰 주세요. 임의 값을 남기면 같은 예외가 계속 늘어납니다.
As per coding guidelines "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsx` around lines 221 - 239, Replace the forbidden arbitrary Tailwind value mt-[2px] on the two Info components with a sanctioned spacing token or utility and/or adjust layout alignment; locate the two Info usages (the one inside the !isApplicationReady conditional and the one above acceptancePolicy.title/description) and remove mt-[2px], then use an approved class such as a project spacing token or a standard Tailwind utility (e.g., mt-2 or items-center on the parent container) to achieve the same vertical alignment without arbitrary values.
🧹 Nitpick comments (7)
src/features/mentoring/ui/common/mentoring-guide-modal.tsx (1)
21-23:GUIDE_CONTENT.description과 실제 렌더링 내용 사이에 불일치가 있습니다.Line 23의
description값이 정의되어 있지만, Lines 82-84에서 해당 항목을 렌더링할 때item.description을 무시하고 새로운 문자열을 직접 구성하고 있습니다. 이로 인해:
- Line 23의 description이 사실상 dead code가 됩니다.
- 매직 스트링
'관리 페이지는 어떻게 구분되나요?'비교는 title 변경 시 조용히 실패할 수 있습니다.♻️ 제안: GUIDE_CONTENT에 `id` 필드 추가 또는 description을 함수로 변경
+const getManagementPageDescription = (noteEnabled: boolean) => { + const noteConsultationLine = noteEnabled + ? '- /note-consultation' + : '쪽지상담함은 현재 제공하지 않습니다.'; + return `유저(멘티) 관점 관리: 내가 신청한 멘토링 내역과 후기 작성을 관리합니다.\n- /my-study-review\n\n${noteConsultationLine}\n멘토 운영 관리 화면도 현재 제공하지 않습니다.`; +}; + const GUIDE_CONTENT = [ { + id: 'what-is-mentoring', title: '멘토링이 뭔가요?', description: '멘토와 멘티를 1:1로 연결해 약속을 잡아주는 기능입니다...', }, { + id: 'management-page', title: '관리 페이지는 어떻게 구분되나요?', - description: - '유저(멘티) 관점 관리: 내가 신청한 멘토링 내역과 후기 작성을 관리합니다.\n- /my-study-review\n\n쪽지상담함과 멘토 운영 관리 화면은 현재 제공하지 않습니다.', + description: null, // dynamic content }, // ... ];렌더링 시:
-{item.title === '관리 페이지는 어떻게 구분되나요?' - ? `유저(멘티) 관점 관리:...` - : item.description} +{item.id === 'management-page' + ? getManagementPageDescription(isNoteConsultationEnabled) + : item.description}Also applies to: 82-84
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/common/mentoring-guide-modal.tsx` around lines 21 - 23, GUIDE_CONTENT entries currently have a static description that is ignored by the renderer: the render code builds a hardcoded string and compares title text instead of using item.description, making the declared descriptions dead code and fragile to title changes; update GUIDE_CONTENT by either adding a stable id field to each entry (e.g., id: 'management-pages') or change description to be a function (e.g., description: () => '...') and then modify the rendering logic in MentoringGuideModal to use item.description (or call item.description()) and to switch on item.id instead of comparing the title string, removing the magic-string comparison and ensuring the displayed text comes from GUIDE_CONTENT.src/types/mentoring/registration-options.ts (1)
34-35: 호환용 별칭도interface로 맞춰주세요.같은 객체 shape를 다시 노출하는 용도라면
type별칭보다interface extends ...가 저장소 규칙과 더 잘 맞습니다.♻️ 제안 코드
-export type MentorRegistrationCoreKeywordOption = - MentorRegistrationSelectableCoreKeywordOption; +export interface MentorRegistrationCoreKeywordOption + extends MentorRegistrationSelectableCoreKeywordOption {}As per coding guidelines: "Use
interfacefor defining object shapes in TypeScript, nottypealiases, when representing object structures."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/types/mentoring/registration-options.ts` around lines 34 - 35, Change the exported alias MentorRegistrationCoreKeywordOption from a type alias to an interface that extends MentorRegistrationSelectableCoreKeywordOption so the compatibility alias uses interface syntax; locate the declaration of MentorRegistrationCoreKeywordOption and replace the "export type MentorRegistrationCoreKeywordOption = MentorRegistrationSelectableCoreKeywordOption" with an "export interface MentorRegistrationCoreKeywordOption extends MentorRegistrationSelectableCoreKeywordOption" declaration to match the repository's object-shape convention.src/features/mentoring/model/use-mentor-registration-controller.ts (1)
920-928: 드래프트 persistence 디바운스 타이머가 매우 짧습니다.180ms 디바운스는 빠른 타이핑 시 sessionStorage에 과도한 쓰기를 유발할 수 있습니다. 일반적으로 300-500ms가 더 적절합니다. 현재 값이 의도적이라면 무시해도 됩니다.
♻️ 디바운스 시간 조정 제안
persistTimer = setTimeout(() => { persistDraft(); - }, 180); + }, 300); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/model/use-mentor-registration-controller.ts` around lines 920 - 928, The debounce timeout used to persist draft is too short (180ms) and can cause excessive sessionStorage writes; update the timer in the persist logic where persistTimer and persistDraft are used (the setTimeout(...) block) to a more reasonable value such as 300–500ms (e.g., replace 180 with 300 or 500) so quick typing batches into fewer writes, and ensure existing clearTimeout(persistTimer) logic remains unchanged.src/features/mentoring/api/mentor-api.types.ts (1)
367-371:MentorCoreKeywordRequestDto에서 code와 label이 모두 optional입니다.타입에 따라
code(PREDEFINED) 또는label(CUSTOM) 중 하나는 필수여야 하지만, 현재 타입 정의는 둘 다 optional로 되어 있어 런타임에 잘못된 요청이 생성될 수 있습니다. discriminated union을 사용하면 타입 안전성이 향상됩니다.♻️ Discriminated union 적용 제안
-export interface MentorCoreKeywordRequestDto { - type: 'PREDEFINED' | 'CUSTOM'; - code?: string; - label?: string; -} +export type MentorCoreKeywordRequestDto = + | { type: 'PREDEFINED'; code: string } + | { type: 'CUSTOM'; label: string };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/api/mentor-api.types.ts` around lines 367 - 371, MentorCoreKeywordRequestDto currently has both code and label optional which allows invalid combos; change it to a discriminated union by replacing MentorCoreKeywordRequestDto with two specific interfaces (e.g., MentorCoreKeywordPredefined and MentorCoreKeywordCustom) where type: 'PREDEFINED' requires code and type: 'CUSTOM' requires label, then export MentorCoreKeywordRequestDto = MentorCoreKeywordPredefined | MentorCoreKeywordCustom so TypeScript enforces the correct required field based on the type discriminator.src/features/mentoring/api/mentor-core-keyword-contract.ts (1)
202-222:flatMap사용이 불필요하게 복잡합니다.
flatMap을 사용하여 단일 요소 배열을 반환하는 것보다map을 직접 사용하는 것이 더 명확합니다. 현재 로직은 항상 정확히 하나의 요소를 반환하므로flatMap의 이점이 없습니다.♻️ map 사용으로 단순화
- return normalizedProfileKeywords.flatMap<MentorCoreKeywordRequestDto>( - (profileKeyword) => { + return normalizedProfileKeywords.map<MentorCoreKeywordRequestDto>( + (profileKeyword): MentorCoreKeywordRequestDto => { const predefinedCode = predefinedCodeMap.get( profileKeyword.trim().toLowerCase(), ); - return predefinedCode - ? [ - { - type: 'PREDEFINED', - code: predefinedCode, - }, - ] - : [ - { - type: 'CUSTOM', - label: profileKeyword, - }, - ]; + return predefinedCode + ? { type: 'PREDEFINED', code: predefinedCode } + : { type: 'CUSTOM', label: profileKeyword }; }, );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/api/mentor-core-keyword-contract.ts` around lines 202 - 222, The use of normalizedProfileKeywords.flatMap(...) is unnecessary because each input always maps to exactly one MentorCoreKeywordRequestDto; replace the flatMap with normalizedProfileKeywords.map(...) and return a single object (either { type: 'PREDEFINED', code: predefinedCode } or { type: 'CUSTOM', label: profileKeyword }) instead of an array, keeping the existing lookup of predefinedCodeMap and trimming/lowercasing logic intact.src/features/mentoring/ui/registration/mentor-registration-form.tsx (1)
1850-1857: 스케줄 필요 여부와 관계없이 draft 에러가 표시될 수 있습니다.Line 1850의 조건
(needsSchedule || !isScheduleDraftValid)는needsSchedule이 false이고 draft가 invalid할 때도 에러를 표시합니다. 그러나scheduleDraftMessages는 Line 1127에서needsSchedule이 false면 빈 배열이 됩니다. 따라서 실제로는 문제가 없지만, 조건이 약간 혼란스럽습니다.♻️ 조건 단순화 제안
- {(needsSchedule || !isScheduleDraftValid) && ( + {scheduleDraftMessages.length > 0 && ( <FieldErrorText🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-registration-form.tsx` around lines 1850 - 1857, The conditional rendering for the schedule error is confusing because scheduleDraftMessages is empty when needsSchedule is false; change the condition so the error is only shown when a schedule is required and the draft is invalid by replacing the existing condition `(needsSchedule || !isScheduleDraftValid)` with a check that both require a schedule and the draft is invalid (i.e., `needsSchedule && !isScheduleDraftValid`) where FieldErrorText is rendered using scheduleDraftMessages[0] ?? errors.schedule?.message; this keeps behavior correct and avoids showing draft errors when schedules are not required.src/features/mentoring/ui/registration/mentor-registration-page-view.tsx (1)
363-376: 모달 title/description이 undefined일 수 있습니다.
welcomeOnboarding이 truthy일 때만 모달이 열리지만, TypeScript 관점에서welcomeOnboarding?.title과welcomeOnboarding?.description이 undefined를 반환할 수 있습니다. 현재?? ''fallback으로 처리되어 있어 기능적으로는 문제없지만, 모달이 열린 상태에서 빈 제목이 표시될 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-registration-page-view.tsx` around lines 363 - 376, The modal can open when welcomeOnboarding is truthy but its title/description may still be undefined, causing an empty header; update the open condition to only open when required fields exist (e.g., change Modal.Root open to Boolean(welcomeOnboarding && welcomeOnboarding.title && welcomeOnboarding.description)) and/or provide explicit meaningful fallbacks when rendering Modal.Title and the description (replace welcomeOnboarding?.title ?? '' and welcomeOnboarding?.description ?? '' with either non-null asserted values from welcomeOnboarding (welcomeOnboarding!.title) after the stronger guard or with explicit strings like '환영합니다'/'설명이 없습니다' to ensure the modal never shows an empty header). Ensure references: welcomeOnboarding, Modal.Root, Modal.Title, and Modal.Content are updated accordingly.
🤖 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/features/admin/mentoring/ui/mentor-applications-page-client.tsx`:
- Line 292: The hardcoded text "서버 정산 readiness 계약 필요" replaced the dynamic
display for the "정산 정보 인증" field, removing the actual completed/incomplete
status; in MentorApplicationsPageClient
(src/features/admin/mentoring/ui/mentor-applications-page-client.tsx) restore
conditional rendering: read the settlement / payment verification status from
the application object (e.g., the property used previously like
settlementVerified, payAuthStatus, or similar) and render a completed/incomplete
label based on that value, falling back to the contractual message only when the
status is undefined or null; update the JSX for the "정산 정보 인증" label to use this
conditional logic instead of the static string.
In `@src/features/mentoring/model/mentor-profile-utils.ts`:
- Around line 145-147: Replace the raw pass-through of skillTags with the same
normalization used for interviewQuestions: call toStringArray(source?.skillTags)
(and ensure it falls back to an empty array if that returns falsy) so
MentorSettings receives only normalized string entries; update the object
property in mentor-profile-utils.ts where interviewQuestions, preNotice,
skillTags are set to use toStringArray for skillTags instead of
source?.skillTags.
In `@src/features/mentoring/model/mentor-registration-preview.ts`:
- Around line 286-294: The preview highlight logic is missing company visibility
changes: update the conditional that currently checks prev.mentoringTitle,
prev.appealLine, prev.jobGroup, prev.jobTitle, prev.careerYears,
isSameCareerEntries(...), and prev.listVisible to also compare prev.companyName
!== next.companyName and prev.hideCompanyName !== next.hideCompanyName so that
changes to company name or the hideCompanyName flag trigger the same "headline"
preview update; locate the condition using prev/...next and isSameCareerEntries
to add these two comparisons.
In `@src/features/mentoring/model/use-mentor-registration-preview-panel.ts`:
- Around line 222-277: The pointer event cleanup in onPreviewResizeStart is
incomplete: only handlePointerUp removes listeners and commits state, leaving
pointermove/pointerup handlers and isResizing set if a pointercancel occurs or
the component unmounts. Fix by adding a handlePointerCancel (and/or a shared
cleanup function) that mirrors handlePointerUp's teardown (release pointer
capture via event.currentTarget.releasePointerCapture(event.pointerId) or
document-level equivalent, setIsResizing(false), commit refs via
setCommittedFormOverflowWidth/setCommittedPanelWidth/setCommittedPanelOverflowWidth)
and removes all three listeners, register
document.addEventListener('pointercancel', handlePointerCancel) alongside
pointermove/pointerup, and ensure both handlePointerUp and handlePointerCancel
call the same cleanup function so listeners are always removed and state
committed.
In `@src/features/mentoring/ui/apply/mentoring-apply-route-client.tsx`:
- Around line 80-87: The component currently always returns
MentorRouteUnavailableState (with MENTOR_APPLY_UNSUPPORTED_MESSAGE and
ctaHref={`/mentoring/${mentor.id}`}), which blocks the real apply UI even when
isApplicationReady is true; change the control flow in the
mentoring-apply-route-client component so that when isApplicationReady is true
you render the actual application UI (the application form/submit flow) instead
of returning MentorRouteUnavailableState, and only return
MentorRouteUnavailableState when isApplicationReady is false (keep the existing
message/cta for the unavailable branch).
In `@src/features/mentoring/ui/detail/mentor-detail-page.tsx`:
- Around line 122-133: Compute a single source-of-truth "activeMethod" and use
it for both state update and rendering: derive activeMethod =
enabledMethods.includes(selectedMethod) ? selectedMethod : (enabledMethods[0] ??
'note'), then update the effect to call setSelectedMethod(activeMethod) only
when it differs, and replace selectedOption to use mentor.methods[activeMethod]
(instead of mentor.methods[selectedMethod]) so the UI immediately reflects the
enabled fallback.
In `@src/features/mentoring/ui/mentor-directory/mentor-card.tsx`:
- Line 158: Replace the Tailwind arbitrary values used in the mentor card UI:
change the div with className "mb-125 min-h-[56px]" to use a project design
token or custom utility instead of min-h-[56px], and likewise replace the other
occurrence of "max-w-[130px]" with the appropriate custom token/utility (e.g.,
an existing min-h- or max-w- token such as min-h-14/max-w-sm or your project's
equivalent). Update the className strings for the elements that currently
include "min-h-[56px]" and "max-w-[130px]" to reference the approved design
tokens/utilities so no arbitrary bracketed values remain.
In `@src/features/mentoring/ui/registration/mentor-career-entries-editor.tsx`:
- Line 376: The grid uses Tailwind arbitrary value
"grid-cols-[minmax(0,1fr)_120px]" (seen in the MentorCareerEntriesEditor
component's className) which violates design-token rules; fix by adding a
token-based utility CSS class (e.g., .grid-cols-main-fixed) in the global styles
that sets grid-template-columns: minmax(0,1fr) var(--size-120) (use the
project’s existing design token variable name instead of --size-120), then
replace both occurrences of "grid-cols-[minmax(0,1fr)_120px]" with
"grid-cols-main-fixed" (keeping the surrounding "grid" and gap classes) so the
layout uses a reusable token-based class rather than an arbitrary Tailwind
value.
In `@src/types/schemas/mentor-registration-schema.ts`:
- Around line 293-307: The skillTags array schema allows duplicate tags, causing
duplicate-key warnings and repeated UI tags; update the skillTags Zod schema
(symbol: skillTags) to enforce uniqueness by adding a .refine that compares the
array length to the Set of trimmed values (so "a" and " a " count as same) and
returns a clear error message (e.g., '핵심 키워드는 중복될 수 없습니다.'). Keep the existing
string trim/min/max checks (symbols: MENTOR_SKILL_TAG_MIN_LENGTH,
MENTOR_SKILL_TAG_MAX_LENGTH) and apply the uniqueness .refine after the array
validation so duplicates are rejected at schema-validation time.
- Around line 140-194: The current superRefine block allows entries with
periodEnabled=true where startMonth is provided but endMonth is empty and
isCurrent=false to pass validation; add a check inside the same superRefine that
when entry.periodEnabled is true and hasStartMonth (entry.startMonth.length>0)
and !hasEndMonth (entry.endMonth.length===0) and entry.isCurrent===false, call
ctx.addIssue with code z.ZodIssueCode.custom, path ['endMonth'], and a message
like '종료 기간을 입력해주세요.' so incomplete ranges are rejected before saving.
---
Outside diff comments:
In `@src/features/admin/mentoring/model/mock-seed.ts`:
- Around line 60-65: The mock seed currently uses
createEmptyMentorScheduleDrafts() for scheduleDrafts; update it to mirror server
behavior by importing createMentorScheduleTextDrafts from
src/features/mentoring/model/mentor-settings.ts and replace the value with
createMentorScheduleTextDrafts(schedule) inside
createServerLikeMentorRegistrationValues so scheduleDrafts is derived from the
schedule object (the variable named schedule) instead of being empty.
In `@src/utils/error-handler.ts`:
- Around line 500-531: The current code sends the raw detail (from
extractApiErrorDetail and stored in variable detail) to both console.error and
Sentry (inside Sentry.withScope via scope.setExtra), which may include sensitive
user input; replace usage of the raw detail with a sanitized or whitelisted
summary before logging and reporting: implement or call a sanitizer (e.g.,
sanitizeApiErrorDetail(detail) or adjust extractApiErrorDetail to return only
safe keys like error type, validation keys, and truncated/hashed values) and use
that sanitizedDetail in the development console output and in Sentry.withScope
(replace scope.setExtra('detail', detail) with scope.setExtra('detail',
sanitizedDetail)); ensure errorInfo, stack, and other non-sensitive fields
remain unchanged.
---
Duplicate comments:
In `@src/features/mentoring/model/mentor-registration-server-error.ts`:
- Around line 167-191: The logic that maps server paramName to the pricing/time
step misses bracketed properties like "methods[0].enabled": update the
methodMatch detection to capture property names beyond "price" and
"durationMinutes" (e.g., include "enabled") or make the regex/property check
generic for any property after methods[...] (change
/^methods(?:\[(\d+)\]|\.(\d+))(?:\.(price|durationMinutes))?$/ to allow other
property names), update the methodField union/handling in the methodMatch branch
(methodField can be 'price'|'durationMinutes'|'enabled'|undefined) and ensure
getMethodFieldPath is only called for fields that need a fieldPath (leave
fieldPath undefined for "enabled"), and also expand the fallback condition to
treat "methods[" prefix the same as "methods." so
paramName.startsWith('methods[') routes to
MENTOR_REGISTRATION_STEP_IDS.pricingAndTime.
In `@src/features/mentoring/ui/detail/mentor-detail-header-section.tsx`:
- Around line 77-81: The header component in mentor-detail-header-section.tsx
contains forbidden Tailwind arbitrary values: replace the sm:text-[30px] on the
<h1> and the xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] on the surrounding
<div> with the project's design-token/utility classes (use the same token
names/pattern used elsewhere in the detail UI), e.g., swap sm:text-[30px] for
the corresponding responsive text token utility and swap the xl:grid-cols[...]
for the predefined grid-column token or utility that expresses the 0.9/1.1
split; update className strings in the MentorDetailHeaderSection (or the
component that renders the shown <h1> and <div>) to use those token utilities
instead of arbitrary values.
In `@src/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsx`:
- Around line 221-239: Replace the forbidden arbitrary Tailwind value mt-[2px]
on the two Info components with a sanctioned spacing token or utility and/or
adjust layout alignment; locate the two Info usages (the one inside the
!isApplicationReady conditional and the one above
acceptancePolicy.title/description) and remove mt-[2px], then use an approved
class such as a project spacing token or a standard Tailwind utility (e.g., mt-2
or items-center on the parent container) to achieve the same vertical alignment
without arbitrary values.
In `@src/features/mentoring/ui/settings/settlement-register-modal.tsx`:
- Around line 108-118: The catch block for the submission flow currently
swallows errors; update the catch to accept the error, run analyzeError(error)
and set the component's inline error state (e.g., errorMessage) with that result
so the modal shows the failure reason while keeping the modal open for retry;
keep the existing caller toast behavior but ensure you call analyzeError inside
the catch and assign its output to the error property used by the inline error
slot (referencing onSubmit, draft, closeModal, analyzeError and the inline
errorMessage state).
- Line 125: The Modal.Content currently uses a forbidden Tailwind arbitrary
value class "max-w-[720px]"; replace that class with the project's approved
width token/utility (i.e., remove "max-w-[720px]" from the className on
Modal.Content in SettlementRegisterModal and swap in the corresponding
design-system token class such as the project's container/width utility like
"max-w-container" or the appropriate "max-w-<design-token>" used elsewhere in
the codebase) so that no arbitrary Tailwind values remain.
---
Nitpick comments:
In `@src/features/mentoring/api/mentor-api.types.ts`:
- Around line 367-371: MentorCoreKeywordRequestDto currently has both code and
label optional which allows invalid combos; change it to a discriminated union
by replacing MentorCoreKeywordRequestDto with two specific interfaces (e.g.,
MentorCoreKeywordPredefined and MentorCoreKeywordCustom) where type:
'PREDEFINED' requires code and type: 'CUSTOM' requires label, then export
MentorCoreKeywordRequestDto = MentorCoreKeywordPredefined |
MentorCoreKeywordCustom so TypeScript enforces the correct required field based
on the type discriminator.
In `@src/features/mentoring/api/mentor-core-keyword-contract.ts`:
- Around line 202-222: The use of normalizedProfileKeywords.flatMap(...) is
unnecessary because each input always maps to exactly one
MentorCoreKeywordRequestDto; replace the flatMap with
normalizedProfileKeywords.map(...) and return a single object (either { type:
'PREDEFINED', code: predefinedCode } or { type: 'CUSTOM', label: profileKeyword
}) instead of an array, keeping the existing lookup of predefinedCodeMap and
trimming/lowercasing logic intact.
In `@src/features/mentoring/model/use-mentor-registration-controller.ts`:
- Around line 920-928: The debounce timeout used to persist draft is too short
(180ms) and can cause excessive sessionStorage writes; update the timer in the
persist logic where persistTimer and persistDraft are used (the setTimeout(...)
block) to a more reasonable value such as 300–500ms (e.g., replace 180 with 300
or 500) so quick typing batches into fewer writes, and ensure existing
clearTimeout(persistTimer) logic remains unchanged.
In `@src/features/mentoring/ui/common/mentoring-guide-modal.tsx`:
- Around line 21-23: GUIDE_CONTENT entries currently have a static description
that is ignored by the renderer: the render code builds a hardcoded string and
compares title text instead of using item.description, making the declared
descriptions dead code and fragile to title changes; update GUIDE_CONTENT by
either adding a stable id field to each entry (e.g., id: 'management-pages') or
change description to be a function (e.g., description: () => '...') and then
modify the rendering logic in MentoringGuideModal to use item.description (or
call item.description()) and to switch on item.id instead of comparing the title
string, removing the magic-string comparison and ensuring the displayed text
comes from GUIDE_CONTENT.
In `@src/features/mentoring/ui/registration/mentor-registration-form.tsx`:
- Around line 1850-1857: The conditional rendering for the schedule error is
confusing because scheduleDraftMessages is empty when needsSchedule is false;
change the condition so the error is only shown when a schedule is required and
the draft is invalid by replacing the existing condition `(needsSchedule ||
!isScheduleDraftValid)` with a check that both require a schedule and the draft
is invalid (i.e., `needsSchedule && !isScheduleDraftValid`) where FieldErrorText
is rendered using scheduleDraftMessages[0] ?? errors.schedule?.message; this
keeps behavior correct and avoids showing draft errors when schedules are not
required.
In `@src/features/mentoring/ui/registration/mentor-registration-page-view.tsx`:
- Around line 363-376: The modal can open when welcomeOnboarding is truthy but
its title/description may still be undefined, causing an empty header; update
the open condition to only open when required fields exist (e.g., change
Modal.Root open to Boolean(welcomeOnboarding && welcomeOnboarding.title &&
welcomeOnboarding.description)) and/or provide explicit meaningful fallbacks
when rendering Modal.Title and the description (replace welcomeOnboarding?.title
?? '' and welcomeOnboarding?.description ?? '' with either non-null asserted
values from welcomeOnboarding (welcomeOnboarding!.title) after the stronger
guard or with explicit strings like '환영합니다'/'설명이 없습니다' to ensure the modal never
shows an empty header). Ensure references: welcomeOnboarding, Modal.Root,
Modal.Title, and Modal.Content are updated accordingly.
In `@src/types/mentoring/registration-options.ts`:
- Around line 34-35: Change the exported alias
MentorRegistrationCoreKeywordOption from a type alias to an interface that
extends MentorRegistrationSelectableCoreKeywordOption so the compatibility alias
uses interface syntax; locate the declaration of
MentorRegistrationCoreKeywordOption and replace the "export type
MentorRegistrationCoreKeywordOption =
MentorRegistrationSelectableCoreKeywordOption" with an "export interface
MentorRegistrationCoreKeywordOption extends
MentorRegistrationSelectableCoreKeywordOption" declaration to match the
repository's object-shape convention.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2e27ceda-54db-4ebb-b13c-0dd7f304fb9d
📒 Files selected for processing (76)
src/api/client/api-error.tssrc/api/openapi/models/mentor-settings-upsert-request.tssrc/app/(admin)/admin/mentoring/layout.tsxsrc/app/(service)/(my)/note-consultation/page.tsxsrc/app/global.csssrc/components/common/modals/study-completion-modal.tsxsrc/components/common/ui/badge/index.tsxsrc/components/common/ui/dropdown/single.tsxsrc/components/common/ui/form/multi-item-selector.tsxsrc/components/common/ui/text-action-button.tsxsrc/components/my-page/profile.tsxsrc/features/admin/mentoring/model/mock-seed.tssrc/features/admin/mentoring/model/use-admin-mentoring-overview-query.tssrc/features/admin/mentoring/ui/mentor-applications-page-client.tsxsrc/features/admin/mentoring/ui/mentor-operations-page-client.tsxsrc/features/admin/mentoring/ui/mentor-registration-detail.tsxsrc/features/admin/mentoring/ui/mentoring-dashboard-page-client.tsxsrc/features/admin/mentoring/ui/mentoring-flow-guide.tsxsrc/features/mentoring/api/mentor-api.tssrc/features/mentoring/api/mentor-api.types.tssrc/features/mentoring/api/mentor-core-keyword-contract.tssrc/features/mentoring/api/mentor-profile.mapper.tssrc/features/mentoring/api/mentor-settings.mapper.tssrc/features/mentoring/const/mentor-registration-labels.tssrc/features/mentoring/const/mentoring-list-labels.tssrc/features/mentoring/model/mentor-profile-utils.tssrc/features/mentoring/model/mentor-public-readiness.tssrc/features/mentoring/model/mentor-registration-draft-storage.tssrc/features/mentoring/model/mentor-registration-entry-onboarding.tssrc/features/mentoring/model/mentor-registration-keywords.tssrc/features/mentoring/model/mentor-registration-preview.tssrc/features/mentoring/model/mentor-registration-server-error.tssrc/features/mentoring/model/mentor-settings.tssrc/features/mentoring/model/mentoring-feature-flag.tssrc/features/mentoring/model/use-floating-panel-scroll-nudge.tssrc/features/mentoring/model/use-mentor-registration-controller.tssrc/features/mentoring/model/use-mentor-registration-preview-model.tssrc/features/mentoring/model/use-mentor-registration-preview-panel.tssrc/features/mentoring/model/use-upsert-my-mentor-settings-mutation.tssrc/features/mentoring/ui/apply/mentoring-apply-route-client.tsxsrc/features/mentoring/ui/common/mentor-profile-meta-list.tsxsrc/features/mentoring/ui/common/mentoring-guide-modal.tsxsrc/features/mentoring/ui/detail/mentor-career-history-panel.tsxsrc/features/mentoring/ui/detail/mentor-detail-header-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-method-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-page.tsxsrc/features/mentoring/ui/detail/mentor-detail-review-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsxsrc/features/mentoring/ui/detail/mentor-route-fallback.tsxsrc/features/mentoring/ui/detail/review-stars.tsxsrc/features/mentoring/ui/mentor-directory/mentor-card.tsxsrc/features/mentoring/ui/mentor-directory/mentor-directory-filters.tsxsrc/features/mentoring/ui/pages/my-mentoring-detail-page.tsxsrc/features/mentoring/ui/pages/my-mentoring-page.tsxsrc/features/mentoring/ui/registration/mentor-career-entries-editor.tsxsrc/features/mentoring/ui/registration/mentor-markdown-content.tsxsrc/features/mentoring/ui/registration/mentor-markdown-editor.tsxsrc/features/mentoring/ui/registration/mentor-registration-form.tsxsrc/features/mentoring/ui/registration/mentor-registration-header.tsxsrc/features/mentoring/ui/registration/mentor-registration-page-view.tsxsrc/features/mentoring/ui/settings/settlement-register-modal.tsxsrc/features/mentoring/ui/settings/weekly-schedule-grid.tsxsrc/features/mentoring/ui/temp/contact-seed-actions.tsxsrc/hooks/queries/settlement-account-api.tssrc/hooks/queries/use-auth-mutation.tssrc/hooks/queries/use-phone-verification-status.tssrc/hooks/queries/use-update-user-profile-mutation.tssrc/stores/useMentorDirectoryStore.tssrc/types/mentoring/domain.tssrc/types/mentoring/markdown.tssrc/types/mentoring/registration-options.tssrc/types/mentoring/registration-view.tssrc/types/mentoring/settings.tssrc/types/schemas/mentor-registration-schema.tssrc/types/schemas/mentoring-admin-schema.tssrc/utils/error-handler.ts
✅ Files skipped from review due to trivial changes (18)
- src/features/mentoring/ui/detail/mentor-detail-method-section.tsx
- src/features/admin/mentoring/ui/mentor-operations-page-client.tsx
- src/app/(admin)/admin/mentoring/layout.tsx
- src/components/common/modals/study-completion-modal.tsx
- src/features/mentoring/ui/registration/mentor-registration-header.tsx
- src/features/mentoring/ui/pages/my-mentoring-page.tsx
- src/components/common/ui/badge/index.tsx
- src/features/mentoring/model/mentoring-feature-flag.ts
- src/features/admin/mentoring/ui/mentoring-flow-guide.tsx
- src/types/mentoring/domain.ts
- src/components/common/ui/dropdown/single.tsx
- src/features/mentoring/const/mentoring-list-labels.ts
- src/features/mentoring/model/mentor-registration-draft-storage.ts
- src/features/mentoring/ui/mentor-directory/mentor-directory-filters.tsx
- src/features/admin/mentoring/ui/mentoring-dashboard-page-client.tsx
- src/stores/useMentorDirectoryStore.ts
- src/features/mentoring/model/mentor-settings.ts
- src/features/mentoring/model/use-mentor-registration-preview-model.ts
🚧 Files skipped from review as they are similar to previous changes (22)
- src/hooks/queries/use-auth-mutation.ts
- src/features/mentoring/ui/detail/review-stars.tsx
- src/features/mentoring/model/mentor-registration-entry-onboarding.ts
- src/app/(service)/(my)/note-consultation/page.tsx
- src/api/client/api-error.ts
- src/hooks/queries/settlement-account-api.ts
- src/types/schemas/mentoring-admin-schema.ts
- src/features/mentoring/model/use-upsert-my-mentor-settings-mutation.ts
- src/api/openapi/models/mentor-settings-upsert-request.ts
- src/features/mentoring/const/mentor-registration-labels.ts
- src/features/mentoring/ui/detail/mentor-route-fallback.tsx
- src/features/mentoring/ui/common/mentor-profile-meta-list.tsx
- src/features/mentoring/model/mentor-registration-keywords.ts
- src/features/mentoring/ui/temp/contact-seed-actions.tsx
- src/components/my-page/profile.tsx
- src/features/mentoring/ui/detail/mentor-career-history-panel.tsx
- src/features/admin/mentoring/ui/mentor-registration-detail.tsx
- src/features/mentoring/api/mentor-settings.mapper.ts
- src/features/mentoring/model/mentor-public-readiness.ts
- src/hooks/queries/use-update-user-profile-mutation.ts
- src/features/mentoring/model/use-floating-panel-scroll-nudge.ts
- src/types/mentoring/settings.ts
| ?.verified | ||
| ? '완료' | ||
| : '미완료'} | ||
| 서버 정산 readiness 계약 필요 |
There was a problem hiding this comment.
정산 인증 상태가 고정 문구로 대체되어 심사 판단 정보가 사라집니다.
Line 292에서 상태값을 정적 문자열로 고정하면, 정산 정보 인증 필드가 실제 완료/미완료를 보여주지 못해 기능 회귀가 됩니다. 상태 기반 표시 + 계약 미연동 fallback으로 유지하는 편이 안전합니다.
제안 수정안
- 서버 정산 readiness 계약 필요
+ {selectedMentor.mentor.mentorSettings?.settlementDraft
+ ?.verified === true
+ ? '완료'
+ : selectedMentor.mentor.mentorSettings?.settlementDraft
+ ?.verified === false
+ ? '미완료'
+ : '정산 readiness 계약 미연동'}📝 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.
| 서버 정산 readiness 계약 필요 | |
| {selectedMentor.mentor.mentorSettings?.settlementDraft | |
| ?.verified === true | |
| ? '완료' | |
| : selectedMentor.mentor.mentorSettings?.settlementDraft | |
| ?.verified === false | |
| ? '미완료' | |
| : '정산 readiness 계약 미연동'} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/admin/mentoring/ui/mentor-applications-page-client.tsx` at line
292, The hardcoded text "서버 정산 readiness 계약 필요" replaced the dynamic display for
the "정산 정보 인증" field, removing the actual completed/incomplete status; in
MentorApplicationsPageClient
(src/features/admin/mentoring/ui/mentor-applications-page-client.tsx) restore
conditional rendering: read the settlement / payment verification status from
the application object (e.g., the property used previously like
settlementVerified, payAuthStatus, or similar) and render a completed/incomplete
label based on that value, falling back to the contractual message only when the
status is undefined or null; update the JSX for the "정산 정보 인증" label to use this
conditional logic instead of the static string.
There was a problem hiding this comment.
• 아래는 지금 보이는 CodeRabbit actionable 리뷰 10개를 순서대로 번호 붙여서, 리뷰 내용 / 내 평
가 페어로 정리한 겁니다.
전제는 방금 말한 운영 의도대로 반영했습니다.
- 멘토링 신청은 아직 의도적으로 막힘
- 정산정보는 아직 개발중
- 정산 인증 상태가 고정 문구로 대체되어 회귀라는 리뷰
리뷰: src/features/admin/mentoring/ui/mentor-applications-page-client.tsx:287
내 평가: 틀림
이건 지금 정산 계약이 아직 없다는 제품 전제를 무시한 지적입니다. 문구가 거칠 수는 있어도,
“완료/미완료를 보여줘야 한다”는 리뷰는 현재 단계 기준으로 성립하지 않습니다. - skillTags만 raw로 통과시켜 정규화 경계를 비껴간다는 리뷰
리뷰: src/features/mentoring/model/mentor-profile-utils.ts:147
내 평가: 부분적으로 맞음
이 줄만 보면 toStringArray를 안 써서 일관성은 떨어집니다. 다만 실제 API 응답은 이미 src/
features/mentoring/api/mentor-profile.mapper.ts:350, src/features/mentoring/api/mentor-
settings.mapper.ts:404 에서 먼저 정리하고 들어옵니다. 그래서 “잘못된 응답이 그대로 들어온
다”는 표현은 과장입니다. - 회사명/비공개 여부 변경이 headline 하이라이트에서 빠졌다는 리뷰
리뷰: src/features/mentoring/model/mentor-registration-preview.ts:286
내 평가: 맞음
실제 헤드라인은 src/features/mentoring/ui/detail/mentor-detail-page.tsx:79 의
companyLabel에 영향을 받는데, 변경 감지에서는 companyName/hideCompanyName를 빠뜨렸습니다.
다만 코멘트에 섞인 companyCategory까지는 지금 헤드라인 영향이 없습니다. - preview panel resize의 pointer cleanup이 불완전하다는 리뷰
리뷰: src/features/mentoring/model/use-mentor-registration-preview-panel.ts:222
내 평가: 맞음
현재는 pointerup에서만 리스너를 제거합니다. pointercancel이나 unmount cleanup이 없어 리스
너/isResizing 상태가 남을 수 있습니다. - /apply 경로가 어떤 상태에서도 실제 신청 화면에 도달하지 않는다는 리뷰
리뷰: src/features/mentoring/ui/apply/mentoring-apply-route-client.tsx:80
내 평가: 틀림
지금 신청 기능은 의도적으로 막혀 있습니다. src/features/mentoring/ui/detail/mentor-
detail-sidebar-cta.tsx:211 도 같은 전제를 따르고 있어서, 이건 버그 지적이 아니라 제품 정
책 미반영 리뷰입니다. - selectedOption fallback이 실제로는 동작하지 않는다는 리뷰
리뷰: src/features/mentoring/ui/detail/mentor-detail-page.tsx:130
내 평가: 맞음
mentor.methods는 src/types/mentoring/domain.ts:44 상 모든 key를 갖는 Record라서 ??
fallback이 사실상 죽어 있습니다. 비활성화 직후 한 렌더 동안 stale 값이 보일 수 있습니다. - mentor-card에 arbitrary value가 새로 들어왔다는 리뷰
리뷰: src/features/mentoring/ui/mentor-directory/mentor-card.tsx:158, src/features/
mentoring/ui/mentor-directory/mentor-card.tsx:214
내 평가: 맞음
저장소 스타일 규칙 기준으로는 맞습니다. 기능 버그는 아니고 디자인 토큰 규칙 위반입니다. - mentor-career-entries-editor의 grid-cols-[...]가 arbitrary value라는 리뷰
리뷰: src/features/mentoring/ui/registration/mentor-career-entries-editor.tsx:376, src/
features/mentoring/ui/registration/mentor-career-entries-editor.tsx:427
내 평가: 맞음
이것도 기능 버그보다는 스타일 규칙 위반입니다. - 시작 월만 입력한 이력이 그대로 통과한다는 리뷰
리뷰: src/types/schemas/mentor-registration-schema.ts:140
내 평가: 맞음
핵심은 isCurrent=false인데 startMonth만 있고 endMonth=''인 상태입니다. 재직중이면
isCurrent=true로 표현해야 하는데, 지금은 그 미완성 상태가 schema를 통과하고 요청 빌더에서
도 src/features/mentoring/api/mentor-settings.mapper.ts:810 startMonth만 실려 나갈 수 있
습니다. - skillTags 중복을 schema에서 막아야 한다는 리뷰
리뷰: src/types/schemas/mentor-registration-schema.ts:293
내 평가: 부분적으로 맞음
방어선으로는 좋은 제안입니다. 하지만 현재 폼은 이미 src/features/mentoring/model/mentor-
registration-keywords.ts:77, src/features/mentoring/ui/registration/mentor-registration-
form.tsx:1221 에서 중복 제거를 하고 있어 must-fix 급이라고 보긴 어렵습니다.
한 줄로 압축하면:
- 맞음: 3, 4, 6, 7, 8, 9
- 부분적으로 맞음: 2, 10
- 틀림: 1, 5
작업완료
There was a problem hiding this comment.
@Hyeonjun0527, 상세한 평가 공유해 주셔서 감사합니다. 이 코멘트(#1)에 대해서는 제가 제품 단계 전제를 고려하지 않은 지적이었음을 인정합니다 — 정산 계약이 아직 미연동 상태라면 settlementDraft?.verified 기반의 완료/미완료 표시는 현 시점에서 성립하지 않습니다. "서버 정산 readiness 계약 필요" 문구는 내부 관리자 화면에서 현재 상태를 명시적으로 알리는 의도된 표현으로 이해했습니다.
나머지 평가 요약도 잘 확인했습니다:
- 맞음 (3, 4, 6, 7, 8, 9): 해당 코멘트들이 타당한 지적이라고 평가해 주셨으므로 각 스레드에서 별도로 확인하겠습니다.
- 부분적으로 맞음 (2, 10): upstream mapper에서 이미 정규화가 이루어지고 있다는 점, 그리고 폼 레이어에서 이미 중복 제거가 된다는 점을 감안한 평가로 이해했습니다.
- 틀림 (5):
/apply경로 차단은 의도된 제품 정책임을 확인했습니다.
✏️ Learnings added
Learnt from: Hyeonjun0527
Repo: code-zero-to-one/study-platform-client PR: 439
File: src/features/admin/mentoring/ui/mentor-applications-page-client.tsx:292-292
Timestamp: 2026-03-23T14:42:50.316Z
Learning: In `code-zero-to-one/study-platform-client`, the settlement (정산) contract/API integration is intentionally not yet implemented. The `settlementDraft` field and its `verified` status on `MentorSettings` should not be treated as a source of truth for settlement readiness. The admin UI in `src/features/admin/mentoring/ui/mentor-applications-page-client.tsx` intentionally shows a placeholder message ("서버 정산 readiness 계약 필요") for the settlement verification field until the server-side settlement contract is established. Do not flag this as a regression.
Learnt from: Hyeonjun0527
Repo: code-zero-to-one/study-platform-client PR: 439
File: src/features/admin/mentoring/ui/mentor-applications-page-client.tsx:292-292
Timestamp: 2026-03-23T14:42:50.316Z
Learning: In `code-zero-to-one/study-platform-client`, the mentoring apply (`/apply`) flow is intentionally blocked at the product level in the current development stage. `src/features/mentoring/ui/apply/mentoring-apply-route-client.tsx` always renders `MentorRouteUnavailableState` regardless of readiness state, and `src/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsx` follows the same policy. Do not flag this as a functional regression or bug — it is an intentional product policy.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: HA-SEUNG-JEONG
Repo: code-zero-to-one/study-platform-client PR: 379
File: src/features/study/group/ui/step/step1-group.tsx:61-65
Timestamp: 2026-02-05T02:17:57.573Z
Learning: In the repository `code-zero-to-one/study-platform-client`, there are existing database records where `classification='GROUP_STUDY'` and `type='MENTORING'`. When editing such records in `src/features/study/group/ui/step/step1-group.tsx`, the form must handle the case where the loaded type value is filtered out from the available options.
Learnt from: HA-SEUNG-JEONG
Repo: code-zero-to-one/study-platform-client PR: 415
File: src/components/common/layout/page-container.tsx:14-14
Timestamp: 2026-03-08T13:57:25.733Z
Learning: In this project, treat 'w-7xl' as a valid Tailwind CSS v4 width utility (from the container scale) equivalent to 80rem (1280px). Do not flag 'w-7xl' as non-existent; it is a semantic, preferred replacement for 'w-[1280px]'. Apply this guidance to TSX files across the codebase.
Learnt from: HA-SEUNG-JEONG
Repo: code-zero-to-one/study-platform-client PR: 427
File: src/components/common/ui/avatar-stack.tsx:75-75
Timestamp: 2026-03-16T03:02:43.294Z
Learning: In TSX files, do not flag Tailwind class usages that map to project CSS tokens defined in the global CSS (e.g., --color-gray-200: `#e9eaeb` in src/app/global.css) such as border-gray-200, bg-gray-200, or text-gray-200, since they are intentional custom tokens. Treat these as valid project token usages and avoid false positives. If enforcing, document this as a repository-wide exception and consider an automated check that recognizes token-based class names when a corresponding CSS variable exists in global.css.
commit 7b8ddddae021998b7b54ccf5048d4c9c2a52e077
Author: Hyeonjun0527 <osumaniaddict527@gmail.com>
Date: Mon Mar 23 21:00:02 2026 +0900
수정: 멘토링 등록 저장 흐름과 공개 노출 분기를 안정화
멘토링 등록 화면에서 핵심키워드 정책을 백엔드 계약에 맞게 정리하고 selectable 키워드와 공개 태그를 분리했다. 기존 predefined 키워드 스냅샷 보존, 잘못된 키워드 응답 처리, 커스텀 키워드 길이 13자 제한까지 한 경계에서 맞췄다.
저장 후에는 항상 안내 모달을 거치도록 통일하고 목록 노출 여부에 따라 상세 또는 멘토링 목록으로 이동하도록 정리했다. 저장 중복 피드백, 목록 비노출 저장 후 404 이동, 미완성 상세 가리기, 프로필 이미지 반영 누락, 에디터와 모달의 세부 UX 회귀도 함께 바로잡았다.
추가로 AGENTS 규칙에 커밋 메시지는 반드시 한국어로 작성하도록 명시해 이후 커밋 규칙을 문서와 실제 작업이 일치하도록 맞췄다.
commit 0d9657b28e49aed2fce6108b43f264f6ae7ac76d
Author: Hyeonjun0527 <osumaniaddict527@gmail.com>
Date: Sat Mar 21 19:29:45 2026 +0900
feat: 멘토링등록
commit 0f42eca344f58b0450450a8782e5d5ac3d6b8b7c
Author: Hyeonjun0527 <osumaniaddict527@gmail.com>
Date: Fri Mar 20 18:58:15 2026 +0900
refactor: 멘토링기능리팩토링
fix: 멘토링 등록 저장 경계와 공개 준비 판정을 note-only·legacy 데이터 기준에 맞게 보정
fix: 멘토링 등록 경력 검증과 상세 미리보기 상태 동기화를 보강
a4312d2 to
dd1c6a6
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/features/mentoring/ui/registration/mentor-markdown-editor.tsx (1)
694-694:⚠️ Potential issue | 🟡 Minor임의의 Tailwind 값
w-[180px]이 사용되었습니다.코딩 가이드라인에 따르면
w-[180px]와 같은 임의의 Tailwind 값은 금지됩니다. 프로젝트 디자인 토큰을 사용하세요.♻️ 디자인 토큰 사용 제안
<input type="range" min={MENTOR_MARKDOWN_IMAGE_MIN_WIDTH} max={MENTOR_MARKDOWN_IMAGE_MAX_WIDTH} step={MENTOR_MARKDOWN_IMAGE_WIDTH_STEP} value={selectedImageWidth} onChange={(event) => { handleImageWidthChange(Number(event.target.value)); }} - className="accent-background-brand-default w-[180px]" + className="accent-background-brand-default w-1800" />Note: 프로젝트의
global.css에 정의된 적절한 width 토큰을 확인하여 사용하세요.As per coding guidelines: "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-markdown-editor.tsx` at line 694, The class uses a forbidden Tailwind arbitrary width value "w-[180px]" in the MentorMarkdownEditor JSX; replace this arbitrary value with the appropriate project width design token (e.g., a token from global.css like w-tokenName or a utility such as w-{token}) wherever "className=\"accent-background-brand-default w-[180px]\"" appears in the MentorMarkdownEditor component so the component uses the project's custom design token instead of an arbitrary value.
🧹 Nitpick comments (9)
src/hooks/queries/use-auth-mutation.ts (1)
74-75: cleanup 성격 로직은onSettled로 분리하는 쪽이 더 일관됩니다.추가된 reset 호출 자체는 맞지만, 현재처럼 성공 콜백에 cleanup을 두면 실패 시 정리 경로가 분기됩니다. 이 블록(reset/clear/removeItem)은
onSettled로 이동하고,onSuccess는 성공 전용 동작(이벤트/라우팅)만 남기는 구성을 권장합니다.♻️ 제안 diff
return useMutation({ mutationFn: logout, onSuccess: () => { const memberId = getCookie('memberId'); if (memberId) sendGTMEvent({ event: 'custom_member_logout', dl_timestamp: new Date().toISOString(), dl_member_id: hashValue(memberId), }); - - clearClientSession(); - localStorage.removeItem(VIEWED_ARCHIVES_KEY); - - resetUserStore(); - resetPhoneVerification(); - resetMentorDirectory(); - resetMentoringManagement(); - resetMentorScreening(); - resetMentorOperation(); - queryClient.clear(); - router.push('/home'); router.refresh(); }, + onSettled: () => { + clearClientSession(); + localStorage.removeItem(VIEWED_ARCHIVES_KEY); + + resetUserStore(); + resetPhoneVerification(); + resetMentorDirectory(); + resetMentoringManagement(); + resetMentorScreening(); + resetMentorOperation(); + queryClient.clear(); + }, });As per coding guidelines "
useMutationhooks는onSuccess(성공 전용),onError(에러 처리),onSettled(정리 작업)로 역할을 분리"해야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/queries/use-auth-mutation.ts` around lines 74 - 75, Move cleanup calls currently placed in the mutation's onSuccess handler into onSettled so cleanup always runs regardless of success or failure: locate the useMutation call in use-auth-mutation.ts and remove resetMentorScreening() and resetMentorOperation() (and any clear/removeItem logic nearby) from onSuccess, then add those cleanup calls to the onSettled handler; keep onSuccess limited to success-only actions (events, routing, analytics) and leave error handling in onError.src/types/schemas/mentor-registration-schema.ts (1)
67-123: weeklyScheduleSchema의 반복적인 max 검증을 리팩토링할 수 있습니다.각 요일별로 동일한
max(SCHEDULE_DAY_MAX_SLOT_COUNT, ...)검증이 반복됩니다. 헬퍼 함수나 루프를 통해 코드 중복을 줄일 수 있습니다.♻️ 선택적 리팩토링 제안
const daySlotArraySchema = z .array(timeSlotSchema) .max( SCHEDULE_DAY_MAX_SLOT_COUNT, '하루 스케줄은 최대 48개 슬롯까지 입력할 수 있습니다.', ); const weeklyScheduleSchema = z .object( Object.fromEntries( WEEKDAY_KEYS.map((day) => [day, daySlotArraySchema]), ) as Record<(typeof WEEKDAY_KEYS)[number], typeof daySlotArraySchema>, ) .superRefine(/* ... */);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/types/schemas/mentor-registration-schema.ts` around lines 67 - 123, The weeklyScheduleSchema repeats the same .array(timeSlotSchema).max(SCHEDULE_DAY_MAX_SLOT_COUNT, ...) for each weekday; refactor by extracting that repeated schema into a single reusable variable (e.g., daySlotArraySchema) and build the object shape dynamically from WEEKDAY_KEYS (e.g., Object.fromEntries(WEEKDAY_KEYS.map(day => [day, daySlotArraySchema]))) before applying .superRefine that still checks duplicates; update references to timeSlotSchema, SCHEDULE_DAY_MAX_SLOT_COUNT, WEEKDAY_KEYS and keep the existing superRefine logic intact.src/features/admin/mentoring/ui/mentor-registration-detail.tsx (1)
199-201: 동일 항목에 대해 formatMentorCareerEntryPeriodLabel이 두 번 호출됩니다.조건부 렌더링과 실제 렌더링에서 같은 entry에 대해 함수가 두 번 호출됩니다. 변수에 저장하여 재사용하면 효율적입니다.
♻️ 변수 캐싱 제안
{majorHistoryEntries.map((entry, index) => { + const periodLabel = formatMentorCareerEntryPeriodLabel(entry); + return ( <div key={`${entry.description}-${index}`} className="flex items-start gap-75" > <span className="font-designer-14r text-text-default">-</span> <div className="min-w-0 flex-1"> - {formatMentorCareerEntryPeriodLabel(entry) && ( + {periodLabel && ( <p className="font-designer-12r text-text-subtle mb-25"> - {formatMentorCareerEntryPeriodLabel(entry)} + {periodLabel} </p> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/admin/mentoring/ui/mentor-registration-detail.tsx` around lines 199 - 201, The code calls formatMentorCareerEntryPeriodLabel(entry) twice for the same entry (once in the conditional and once in the JSX); compute and cache the result in a local variable (e.g., const periodLabel = formatMentorCareerEntryPeriodLabel(entry)) inside the scope where entry is used (such as within the map or component render) and then use periodLabel for both the conditional and the paragraph content to avoid redundant calls.src/features/mentoring/ui/settings/weekly-schedule-grid.tsx (1)
128-131: 항등 조건문이 항상 같은 값을 반환합니다.
timezone === 'Asia/Seoul' ? 'Asia/Seoul' : 'Asia/Seoul'와slotUnitMinutes === 30 ? 30 : 30는 항상 동일한 값을 반환하므로 조건 분기가 의미 없습니다. 타입 좁히기가 목적이라면 type assertion이나 상수로 대체할 수 있습니다.♻️ 간소화 제안
const normalizeWeeklySchedule = ( value: MentorWeeklySchedule | undefined, ): MentorWeeklySchedule => { if (!value) { return createFallbackSchedule(); } return { - timezone: value.timezone === 'Asia/Seoul' ? 'Asia/Seoul' : 'Asia/Seoul', - slotUnitMinutes: value.slotUnitMinutes === 30 ? 30 : 30, + timezone: 'Asia/Seoul', + slotUnitMinutes: 30, weekly: createWeekdayRecord((day) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/settings/weekly-schedule-grid.tsx` around lines 128 - 131, The ternary expressions in the object returned by the function are redundant—replace timezone: value.timezone === 'Asia/Seoul' ? 'Asia/Seoul' : 'Asia/Seoul' and slotUnitMinutes: value.slotUnitMinutes === 30 ? 30 : 30 with direct values or appropriate type assertions; e.g., set timezone to the desired constant (or assert value.timezone as the narrowed type) and set slotUnitMinutes to 30 (or assert value.slotUnitMinutes as 30) so the returned object (the timezone and slotUnitMinutes properties used alongside createWeekdayRecord) is simplified and type-safe.src/features/mentoring/ui/apply/mentoring-apply-route-client.tsx (1)
10-21: 사용되지 않는 import와 파라미터가 있습니다.
MentoringMethodTypeimport와selectedType파라미터가 선언되어 있지만 컴포넌트 본문에서 사용되지 않습니다. 멘토링 신청 기능이 의도적으로 비활성화되어 있으므로 이 코드는 향후 기능 활성화를 위해 남겨둔 것으로 보이지만, 현재는 데드 코드입니다.🧹 선택적 정리 제안
-import type { MentoringMethodType } from '@/types/mentoring/domain'; import { MentorNotFoundState, MentorRouteErrorState, MentorRouteLoading, MentorRouteUnavailableState, } from '../detail/mentor-route-fallback'; interface MentoringApplyRouteClientProps { mentorId: number; - selectedType?: MentoringMethodType; } export default function MentoringApplyRouteClient({ mentorId, }: MentoringApplyRouteClientProps) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/apply/mentoring-apply-route-client.tsx` around lines 10 - 21, Remove the dead import and unused prop: delete the MentoringMethodType import and remove the selectedType property from the MentoringApplyRouteClientProps interface (and from the MentoringApplyRouteClient component signature if present), leaving only mentorId; ensure there are no remaining references to selectedType or MentoringMethodType in this file and run the linter/TS check to confirm no unused-symbol errors remain.src/app/global.css (1)
429-447:h-650과w-650유틸리티 네이밍이 혼동될 수 있습니다.
h-650과w-650이 모두 52px로 정의되어 있는데, 숫자 650이 52와 연관성이 없어 네이밍 컨벤션이 일관되지 않습니다. 다른 spacing 토큰들은--spacing-650이 52px라면 이해가 되지만, spacing 스케일에 650이 정의되어 있지 않습니다. 향후 유지보수를 위해h-52또는 의미 있는 이름(예:h-panel-header)을 고려해 볼 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/global.css` around lines 429 - 447, The h-650 and w-650 utilities are misleading because they set height/width to 52px while the name implies 650; rename these utilities to reflect the actual value (e.g., h-52 and w-52 or a semantic name like h-panel-header/w-panel-header) and update all usages accordingly, or alternatively align the value with the naming by mapping to an existing spacing token (e.g., --spacing-650) if that token truly represents 52px; update the utility declarations (h-650, w-650) and every reference to them (search for "h-650" and "w-650") to keep naming consistent with the measured value or token.src/features/mentoring/ui/registration/mentor-registration-page-view.tsx (2)
24-26: 상수에 Tailwind 임의 값이 포함되어 있습니다.
PAGE_CONTAINER_CLASS에max-w-[1280px]가 포함되어 있습니다. 프로젝트에서max-w-page또는max-w-7xl같은 커스텀 토큰을 사용하는 것이 가이드라인에 부합합니다.♻️ 제안 수정
const PAGE_CONTAINER_CLASS = - 'mx-auto w-full max-w-[1280px] px-150 py-400 sm:px-300 xl:px-400 xl:py-500'; + 'mx-auto w-full max-w-page px-150 py-400 sm:px-300 xl:px-400 xl:py-500';As per coding guidelines: "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-registration-page-view.tsx` around lines 24 - 26, PAGE_CONTAINER_CLASS contains a Tailwind arbitrary value max-w-[1280px] which violates the project's design-token rule; update PAGE_CONTAINER_CLASS to use the project's custom max-width token (e.g., replace max-w-[1280px] with the approved token such as max-w-page or max-w-7xl) and keep PREVIEW_PANEL_EXTRA_WIDTH unchanged, ensuring the chosen token exists in the design system and maintaining the rest of the class string.
289-292:min-w-[360px]임의 값이 사용되었습니다.Line 291의
min-w-[360px]는 코딩 가이드라인을 위반합니다. 디자인 토큰으로 교체하거나 커스텀 유틸리티로 정의해 주세요.♻️ 제안 수정
- <div className="min-w-[360px] [&_a]:pointer-events-none"> + <div className="min-w-360 [&_a]:pointer-events-none">또는
global.css에--spacing-360: 360px와 같은 토큰을 정의하여 사용하세요.As per coding guidelines: "Forbidden: Tailwind arbitrary values."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/registration/mentor-registration-page-view.tsx` around lines 289 - 292, The className uses a Tailwind arbitrary value "min-w-[360px]" which violates the guideline; replace it by using a design token or a custom utility instead: define a spacing token (e.g., --spacing-360: 360px) in global CSS or add a custom Tailwind utility/class (e.g., .min-w-360) in your Tailwind config, then update the element wrapping MentorDetailPage (the element rendered when state.isPreviewOpen is true) to use the token/utility instead of "min-w-[360px]".src/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsx (1)
221-231: 금지된 Tailwind 임의 값mt-[2px]이 사용되었습니다.Lines 223, 231에서
mt-[2px]가 사용되고 있습니다. 코딩 가이드라인에 따르면 임의 값 대신 프로젝트 디자인 토큰만 사용해야 합니다.mt-25또는 가장 가까운 토큰으로 교체해 주세요.♻️ 제안 수정
- <Info className="text-text-subtlest mt-[2px] h-14 w-14 shrink-0" /> + <Info className="text-text-subtlest mt-25 h-14 w-14 shrink-0" />- <Info className="text-text-subtlest mt-[2px] h-14 w-14 shrink-0" /> + <Info className="text-text-subtlest mt-25 h-14 w-14 shrink-0" />As per coding guidelines: "Forbidden: Tailwind arbitrary values (e.g.,
p-[4px],w-[320px]). Use only project custom design tokens."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsx` around lines 221 - 231, The review flags usage of forbidden Tailwind arbitrary value `mt-[2px]` in the MentorDetailSidebar CTA rendering; locate the two className strings on the Info component instances in the JSX where `isApplicationReady` and the subsequent block are rendered and replace `mt-[2px]` with the nearest approved design token (e.g., `mt-25`) so the classes become compliant with project design tokens, ensuring both occurrences (the Info above the applyUnavailableMessage and the second Info) are updated consistently.
🤖 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/features/mentoring/ui/detail/mentor-detail-header-section.tsx`:
- Around line 76-79: The h1 in mentor-detail-header-section.tsx uses a forbidden
Tailwind arbitrary value "sm:text-[30px]"; replace that class with the project's
design token utility (or add a new utility in global.css) and apply it to the h1
(look for the <h1> element rendering mentoringTitleLabel in
MentorDetailHeaderSection). Either swap "sm:text-[30px]" for the existing token
(e.g., a font-designer-30b-style utility) or define a new utility in global.css
as described and use that class name instead of the arbitrary value.
In `@src/features/mentoring/ui/mentor-directory/mentor-card.tsx`:
- Line 158: The component mentor-card.tsx uses the CSS utility class "min-h-700"
but that utility is not defined in global.css; add an explicit `@utility` entry
for min-h-700 in global.css following the existing pattern (e.g., mirror
min-h-280/min-h-260/min-h-200) so the class sets min-height: 700px, or
alternatively set min-height to var(--spacing-700) if you prefer to reuse the
spacing token; ensure the utility name exactly matches "min-h-700" so the div
className="mb-125 min-h-700" gets the intended height.
---
Outside diff comments:
In `@src/features/mentoring/ui/registration/mentor-markdown-editor.tsx`:
- Line 694: The class uses a forbidden Tailwind arbitrary width value
"w-[180px]" in the MentorMarkdownEditor JSX; replace this arbitrary value with
the appropriate project width design token (e.g., a token from global.css like
w-tokenName or a utility such as w-{token}) wherever
"className=\"accent-background-brand-default w-[180px]\"" appears in the
MentorMarkdownEditor component so the component uses the project's custom design
token instead of an arbitrary value.
---
Nitpick comments:
In `@src/app/global.css`:
- Around line 429-447: The h-650 and w-650 utilities are misleading because they
set height/width to 52px while the name implies 650; rename these utilities to
reflect the actual value (e.g., h-52 and w-52 or a semantic name like
h-panel-header/w-panel-header) and update all usages accordingly, or
alternatively align the value with the naming by mapping to an existing spacing
token (e.g., --spacing-650) if that token truly represents 52px; update the
utility declarations (h-650, w-650) and every reference to them (search for
"h-650" and "w-650") to keep naming consistent with the measured value or token.
In `@src/features/admin/mentoring/ui/mentor-registration-detail.tsx`:
- Around line 199-201: The code calls formatMentorCareerEntryPeriodLabel(entry)
twice for the same entry (once in the conditional and once in the JSX); compute
and cache the result in a local variable (e.g., const periodLabel =
formatMentorCareerEntryPeriodLabel(entry)) inside the scope where entry is used
(such as within the map or component render) and then use periodLabel for both
the conditional and the paragraph content to avoid redundant calls.
In `@src/features/mentoring/ui/apply/mentoring-apply-route-client.tsx`:
- Around line 10-21: Remove the dead import and unused prop: delete the
MentoringMethodType import and remove the selectedType property from the
MentoringApplyRouteClientProps interface (and from the MentoringApplyRouteClient
component signature if present), leaving only mentorId; ensure there are no
remaining references to selectedType or MentoringMethodType in this file and run
the linter/TS check to confirm no unused-symbol errors remain.
In `@src/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsx`:
- Around line 221-231: The review flags usage of forbidden Tailwind arbitrary
value `mt-[2px]` in the MentorDetailSidebar CTA rendering; locate the two
className strings on the Info component instances in the JSX where
`isApplicationReady` and the subsequent block are rendered and replace
`mt-[2px]` with the nearest approved design token (e.g., `mt-25`) so the classes
become compliant with project design tokens, ensuring both occurrences (the Info
above the applyUnavailableMessage and the second Info) are updated consistently.
In `@src/features/mentoring/ui/registration/mentor-registration-page-view.tsx`:
- Around line 24-26: PAGE_CONTAINER_CLASS contains a Tailwind arbitrary value
max-w-[1280px] which violates the project's design-token rule; update
PAGE_CONTAINER_CLASS to use the project's custom max-width token (e.g., replace
max-w-[1280px] with the approved token such as max-w-page or max-w-7xl) and keep
PREVIEW_PANEL_EXTRA_WIDTH unchanged, ensuring the chosen token exists in the
design system and maintaining the rest of the class string.
- Around line 289-292: The className uses a Tailwind arbitrary value
"min-w-[360px]" which violates the guideline; replace it by using a design token
or a custom utility instead: define a spacing token (e.g., --spacing-360: 360px)
in global CSS or add a custom Tailwind utility/class (e.g., .min-w-360) in your
Tailwind config, then update the element wrapping MentorDetailPage (the element
rendered when state.isPreviewOpen is true) to use the token/utility instead of
"min-w-[360px]".
In `@src/features/mentoring/ui/settings/weekly-schedule-grid.tsx`:
- Around line 128-131: The ternary expressions in the object returned by the
function are redundant—replace timezone: value.timezone === 'Asia/Seoul' ?
'Asia/Seoul' : 'Asia/Seoul' and slotUnitMinutes: value.slotUnitMinutes === 30 ?
30 : 30 with direct values or appropriate type assertions; e.g., set timezone to
the desired constant (or assert value.timezone as the narrowed type) and set
slotUnitMinutes to 30 (or assert value.slotUnitMinutes as 30) so the returned
object (the timezone and slotUnitMinutes properties used alongside
createWeekdayRecord) is simplified and type-safe.
In `@src/hooks/queries/use-auth-mutation.ts`:
- Around line 74-75: Move cleanup calls currently placed in the mutation's
onSuccess handler into onSettled so cleanup always runs regardless of success or
failure: locate the useMutation call in use-auth-mutation.ts and remove
resetMentorScreening() and resetMentorOperation() (and any clear/removeItem
logic nearby) from onSuccess, then add those cleanup calls to the onSettled
handler; keep onSuccess limited to success-only actions (events, routing,
analytics) and leave error handling in onError.
In `@src/types/schemas/mentor-registration-schema.ts`:
- Around line 67-123: The weeklyScheduleSchema repeats the same
.array(timeSlotSchema).max(SCHEDULE_DAY_MAX_SLOT_COUNT, ...) for each weekday;
refactor by extracting that repeated schema into a single reusable variable
(e.g., daySlotArraySchema) and build the object shape dynamically from
WEEKDAY_KEYS (e.g., Object.fromEntries(WEEKDAY_KEYS.map(day => [day,
daySlotArraySchema]))) before applying .superRefine that still checks
duplicates; update references to timeSlotSchema, SCHEDULE_DAY_MAX_SLOT_COUNT,
WEEKDAY_KEYS and keep the existing superRefine logic intact.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0c03ad72-ef91-45f1-b0f1-a54d23c6ea00
📒 Files selected for processing (76)
src/api/client/api-error.tssrc/api/openapi/models/mentor-settings-upsert-request.tssrc/app/(admin)/admin/mentoring/layout.tsxsrc/app/(service)/(my)/note-consultation/page.tsxsrc/app/global.csssrc/components/common/modals/study-completion-modal.tsxsrc/components/common/ui/badge/index.tsxsrc/components/common/ui/dropdown/single.tsxsrc/components/common/ui/form/multi-item-selector.tsxsrc/components/common/ui/text-action-button.tsxsrc/components/my-page/profile.tsxsrc/features/admin/mentoring/model/mock-seed.tssrc/features/admin/mentoring/model/use-admin-mentoring-overview-query.tssrc/features/admin/mentoring/ui/mentor-applications-page-client.tsxsrc/features/admin/mentoring/ui/mentor-operations-page-client.tsxsrc/features/admin/mentoring/ui/mentor-registration-detail.tsxsrc/features/admin/mentoring/ui/mentoring-dashboard-page-client.tsxsrc/features/admin/mentoring/ui/mentoring-flow-guide.tsxsrc/features/mentoring/api/mentor-api.tssrc/features/mentoring/api/mentor-api.types.tssrc/features/mentoring/api/mentor-core-keyword-contract.tssrc/features/mentoring/api/mentor-profile.mapper.tssrc/features/mentoring/api/mentor-settings.mapper.tssrc/features/mentoring/const/mentor-registration-labels.tssrc/features/mentoring/const/mentoring-list-labels.tssrc/features/mentoring/model/mentor-profile-utils.tssrc/features/mentoring/model/mentor-public-readiness.tssrc/features/mentoring/model/mentor-registration-draft-storage.tssrc/features/mentoring/model/mentor-registration-entry-onboarding.tssrc/features/mentoring/model/mentor-registration-keywords.tssrc/features/mentoring/model/mentor-registration-preview.tssrc/features/mentoring/model/mentor-registration-server-error.tssrc/features/mentoring/model/mentor-settings.tssrc/features/mentoring/model/mentoring-feature-flag.tssrc/features/mentoring/model/use-floating-panel-scroll-nudge.tssrc/features/mentoring/model/use-mentor-registration-controller.tssrc/features/mentoring/model/use-mentor-registration-preview-model.tssrc/features/mentoring/model/use-mentor-registration-preview-panel.tssrc/features/mentoring/model/use-upsert-my-mentor-settings-mutation.tssrc/features/mentoring/ui/apply/mentoring-apply-route-client.tsxsrc/features/mentoring/ui/common/mentor-profile-meta-list.tsxsrc/features/mentoring/ui/common/mentoring-guide-modal.tsxsrc/features/mentoring/ui/detail/mentor-career-history-panel.tsxsrc/features/mentoring/ui/detail/mentor-detail-header-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-method-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-page.tsxsrc/features/mentoring/ui/detail/mentor-detail-review-section.tsxsrc/features/mentoring/ui/detail/mentor-detail-sidebar-cta.tsxsrc/features/mentoring/ui/detail/mentor-route-fallback.tsxsrc/features/mentoring/ui/detail/review-stars.tsxsrc/features/mentoring/ui/mentor-directory/mentor-card.tsxsrc/features/mentoring/ui/mentor-directory/mentor-directory-filters.tsxsrc/features/mentoring/ui/pages/my-mentoring-detail-page.tsxsrc/features/mentoring/ui/pages/my-mentoring-page.tsxsrc/features/mentoring/ui/registration/mentor-career-entries-editor.tsxsrc/features/mentoring/ui/registration/mentor-markdown-content.tsxsrc/features/mentoring/ui/registration/mentor-markdown-editor.tsxsrc/features/mentoring/ui/registration/mentor-registration-form.tsxsrc/features/mentoring/ui/registration/mentor-registration-header.tsxsrc/features/mentoring/ui/registration/mentor-registration-page-view.tsxsrc/features/mentoring/ui/settings/settlement-register-modal.tsxsrc/features/mentoring/ui/settings/weekly-schedule-grid.tsxsrc/features/mentoring/ui/temp/contact-seed-actions.tsxsrc/hooks/queries/settlement-account-api.tssrc/hooks/queries/use-auth-mutation.tssrc/hooks/queries/use-phone-verification-status.tssrc/hooks/queries/use-update-user-profile-mutation.tssrc/stores/useMentorDirectoryStore.tssrc/types/mentoring/domain.tssrc/types/mentoring/markdown.tssrc/types/mentoring/registration-options.tssrc/types/mentoring/registration-view.tssrc/types/mentoring/settings.tssrc/types/schemas/mentor-registration-schema.tssrc/types/schemas/mentoring-admin-schema.tssrc/utils/error-handler.ts
✅ Files skipped from review due to trivial changes (18)
- src/features/admin/mentoring/ui/mentor-operations-page-client.tsx
- src/components/common/modals/study-completion-modal.tsx
- src/features/admin/mentoring/ui/mentoring-dashboard-page-client.tsx
- src/api/client/api-error.ts
- src/components/common/ui/dropdown/single.tsx
- src/features/admin/mentoring/ui/mentoring-flow-guide.tsx
- src/features/mentoring/ui/pages/my-mentoring-page.tsx
- src/features/mentoring/model/mentor-registration-draft-storage.ts
- src/features/mentoring/ui/registration/mentor-registration-header.tsx
- src/types/mentoring/domain.ts
- src/api/openapi/models/mentor-settings-upsert-request.ts
- src/hooks/queries/use-phone-verification-status.ts
- src/features/mentoring/model/mentoring-feature-flag.ts
- src/components/common/ui/badge/index.tsx
- src/features/mentoring/ui/common/mentor-profile-meta-list.tsx
- src/features/mentoring/model/use-floating-panel-scroll-nudge.ts
- src/features/mentoring/api/mentor-core-keyword-contract.ts
- src/types/mentoring/settings.ts
🚧 Files skipped from review as they are similar to previous changes (29)
- src/features/mentoring/ui/detail/mentor-detail-method-section.tsx
- src/features/mentoring/const/mentoring-list-labels.ts
- src/app/(service)/(my)/note-consultation/page.tsx
- src/features/mentoring/ui/detail/review-stars.tsx
- src/app/(admin)/admin/mentoring/layout.tsx
- src/features/admin/mentoring/ui/mentor-applications-page-client.tsx
- src/hooks/queries/use-update-user-profile-mutation.ts
- src/features/mentoring/ui/mentor-directory/mentor-directory-filters.tsx
- src/types/schemas/mentoring-admin-schema.ts
- src/utils/error-handler.ts
- src/features/mentoring/ui/common/mentoring-guide-modal.tsx
- src/features/mentoring/model/use-upsert-my-mentor-settings-mutation.ts
- src/features/mentoring/ui/pages/my-mentoring-detail-page.tsx
- src/hooks/queries/settlement-account-api.ts
- src/features/mentoring/ui/detail/mentor-route-fallback.tsx
- src/features/mentoring/ui/detail/mentor-detail-review-section.tsx
- src/features/mentoring/api/mentor-api.ts
- src/types/mentoring/registration-options.ts
- src/types/mentoring/markdown.ts
- src/features/mentoring/model/mentor-registration-keywords.ts
- src/components/common/ui/text-action-button.tsx
- src/features/mentoring/ui/detail/mentor-career-history-panel.tsx
- src/features/mentoring/model/mentor-registration-entry-onboarding.ts
- src/components/my-page/profile.tsx
- src/features/mentoring/ui/registration/mentor-career-entries-editor.tsx
- src/features/mentoring/ui/temp/contact-seed-actions.tsx
- src/stores/useMentorDirectoryStore.ts
- src/features/mentoring/ui/settings/settlement-register-modal.tsx
- src/features/mentoring/ui/registration/mentor-registration-form.tsx
| <> | ||
| <h1 className="font-designer-24b text-text-strong mb-300 leading-snug sm:text-[30px]"> | ||
| {mentoringTitleLabel} | ||
| </h1> |
There was a problem hiding this comment.
sm:text-[30px] arbitrary value가 사용되었습니다.
코딩 가이드라인에 따르면 Tailwind arbitrary values (예: p-[4px], w-[320px])는 금지되어 있습니다. sm:text-[30px]를 프로젝트 디자인 토큰으로 교체해 주세요.
🔧 수정 제안
- <h1 className="font-designer-24b text-text-strong mb-300 leading-snug sm:text-[30px]">
+ <h1 className="font-designer-24b text-text-strong mb-300 leading-snug sm:font-designer-28b">또는 30px가 필요하다면 global.css에 새 유틸리티를 정의하세요:
`@utility` font-designer-30b {
font-size: 30px;
font-weight: var(--font-weight-bold);
line-height: 45px;
letter-spacing: 0;
}As per coding guidelines: "Forbidden: Tailwind arbitrary values (e.g., p-[4px], w-[320px]). Use only project custom design tokens."
📝 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.
| <> | |
| <h1 className="font-designer-24b text-text-strong mb-300 leading-snug sm:text-[30px]"> | |
| {mentoringTitleLabel} | |
| </h1> | |
| <> | |
| <h1 className="font-designer-24b text-text-strong mb-300 leading-snug sm:font-designer-28b"> | |
| {mentoringTitleLabel} | |
| </h1> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/mentoring/ui/detail/mentor-detail-header-section.tsx` around
lines 76 - 79, The h1 in mentor-detail-header-section.tsx uses a forbidden
Tailwind arbitrary value "sm:text-[30px]"; replace that class with the project's
design token utility (or add a new utility in global.css) and apply it to the h1
(look for the <h1> element rendering mentoringTitleLabel in
MentorDetailHeaderSection). Either swap "sm:text-[30px]" for the existing token
(e.g., a font-designer-30b-style utility) or define a new utility in global.css
as described and use that class name instead of the arbitrary value.
| </div> | ||
| )} | ||
| <div className="mb-125"> | ||
| <div className="mb-125 min-h-700"> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# min-h-700 사용처와 정의 여부 확인
echo "=== Checking min-h-700 definition in global.css ==="
rg -n 'min-h-700' src/app/global.css
echo "=== Checking spacing-700 definition ==="
rg -n 'spacing-700' src/app/global.css
echo "=== Checking other min-h utilities defined ==="
rg -n '@utility min-h-' src/app/global.cssRepository: code-zero-to-one/study-platform-client
Length of output: 322
🏁 Script executed:
#!/bin/bash
# Check all usages of min-h-700 in the codebase
echo "=== All usages of min-h-700 ==="
rg -n 'min-h-700' --type tsx --type ts
echo "=== Check if min-h-700 should be in global.css but missing ==="
echo "Current min-h definitions in global.css:"
rg -n '@utility min-h-' src/app/global.css -A 2Repository: code-zero-to-one/study-platform-client
Length of output: 436
🏁 Script executed:
#!/bin/bash
# Search for min-h-700 more broadly without file type restrictions
echo "=== Searching for min-h-700 in entire codebase ==="
rg 'min-h-700' --no-heading
echo "=== Verify the specific file and line ==="
sed -n '155,165p' src/features/mentoring/ui/mentor-directory/mentor-card.tsx | cat -n
echo "=== Check all min-h- custom utilities defined in global.css ==="
rg '@utility min-h-' src/app/global.cssRepository: code-zero-to-one/study-platform-client
Length of output: 817
global.css에 min-h-700 유틸리티를 명시적으로 정의해야 합니다.
min-h-700은 파일에서 사용 중이지만 global.css에 명시적으로 정의되어 있지 않습니다. 프로젝트의 기존 패턴(min-h-280, min-h-260, min-h-200 등)을 따라 명시적으로 정의하세요:
`@utility` min-h-700 {
min-height: 700px;
}
또는 --spacing-700: 56px를 활용하려면:
`@utility` min-h-700 {
min-height: var(--spacing-700);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/mentoring/ui/mentor-directory/mentor-card.tsx` at line 158, The
component mentor-card.tsx uses the CSS utility class "min-h-700" but that
utility is not defined in global.css; add an explicit `@utility` entry for
min-h-700 in global.css following the existing pattern (e.g., mirror
min-h-280/min-h-260/min-h-200) so the class sets min-height: 700px, or
alternatively set min-height to var(--spacing-700) if you prefer to reuse the
spacing token; ensure the utility name exactly matches "min-h-700" so the div
className="mb-125 min-h-700" gets the intended height.
배경
멘토링 등록 화면, 저장 후 안내/이동, 공개 상세/목록 노출 기준에서 상태 불일치와 UX 회귀가 누적되어 있었습니다.
이번 PR은 멘토링 등록을 백엔드 계약에 맞게 다시 정렬하고, 저장 후 공개 노출 분기와 화면 표시 기준을 일관되게 맞추는 데 초점을 맞췄습니다.
주요 변경 사항
1. 핵심키워드 저장 계약과 등록 옵션 역할 정리
PREDEFINED/CUSTOM저장 계약을 프론트 전반에 맞췄고, 잘못된 핵심키워드 응답과 옵션 drift를 더 안전하게 처리하도록 정리했습니다.2. 저장 흐름과 공개 노출 분기 안정화
3. 멘토 상세/목록/프로필 동기화 개선
4. 등록 폼과 에디터 UX 보강
검증
yarn lint --fixyarn prettier:fixyarn typecheck리뷰 포인트
listVisible=false및 상세 소개 미완성 상태에서의 공개 상세/목록 노출 기준이 맞는지Summary by CodeRabbit
릴리스 노트
새로운 기능
UI/UX 개선
기능 제어
접근성·소소한 개선