Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughCSP Report-Only 헤더 추가, 이미지 업로드 클라이언트 사이즈 검증 도입, 리다이렉트 안전성 검사 추가, 그룹 스터디 폼 필드 최대 길이 제약 도입, 사용자 상태를 sessionStorage로 전환하고 localStorage 정리하는 변경입니다. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/api/auth/clear-session/route.ts (1)
27-40:⚠️ Potential issue | 🟠 Major
Origin헤더 우선 사용은 오픈 리다이렉트 우회 지점이 됩니다.경로 검증을 추가했더라도, Line 27처럼
Origin헤더를 base URL로 신뢰하면 응답Location의 도메인을 조작할 수 있습니다. 기준 origin은 서버가 파싱한 요청 URL(request.nextUrl.origin)을 사용하세요.제안 패치
- const origin = request.headers.get('origin'); - const baseUrl = - origin || - (() => { - const host = request.headers.get('host'); - const protocol = request.headers.get('x-forwarded-proto') || 'https'; - - return host ? `${protocol}://${host}` : null; - })() || + request.nextUrl.origin || process.env.NEXT_PUBLIC_SITE_URL || process.env.NEXT_PUBLIC_API_PROD_BASE_URL?.replace('/api/v1', '') || process.env.NEXT_PUBLIC_API_BASE_URL?.replace('/api/v1', '') || request.url.split('/api')[0];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/auth/clear-session/route.ts` around lines 27 - 40, The code trusts the incoming Origin header when building baseUrl (variable origin and baseUrl), which enables open-redirect manipulation; replace use of request.headers.get('origin') with the server-parsed origin request.nextUrl.origin (or otherwise derive baseUrl from request.nextUrl.origin) and remove or ignore the untrusted Origin header, ensuring all fallback chain (process.env... and request.url split) remains but using request.nextUrl.origin as the primary trusted origin; update any tests or callers that assumed header-origin behavior and keep baseUrl construction logic (baseUrl variable) intact otherwise.
🧹 Nitpick comments (1)
src/components/ui/image-upload-input.tsx (1)
90-91: 클래스 조합에clsx유틸 사용 권장템플릿 문자열 대신
clsx유틸을 사용하면 Tailwind 클래스 충돌 관리와 가독성이 더 좋습니다.제안 패치
+import clsx from 'clsx'; import Image from 'next/image'; import { useState, DragEvent, ChangeEvent, useRef } from 'react'; import Button from '@/components/ui/button'; @@ - className={`${inputStyles.base} ${isDragging ? inputStyles.dragging : inputStyles.notDragging}`} + className={clsx( + inputStyles.base, + isDragging ? inputStyles.dragging : inputStyles.notDragging, + )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/image-upload-input.tsx` around lines 90 - 91, Replace the template-string className composition in the ImageUploadInput component with the clsx utility: import clsx (or classnames) at the top and use clsx(inputStyles.base, isDragging ? inputStyles.dragging : inputStyles.notDragging) instead of the current `${inputStyles.base} ${isDragging ? inputStyles.dragging : inputStyles.notDragging}` expression; this touches the JSX prop where className is set and uses the existing inputStyles and isDragging identifiers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ui/image-upload-input.tsx`:
- Around line 130-136: The remove button (inside the ImageUploadInput component)
only contains the "✕" glyph so screen readers can't determine its purpose;
update the button element that calls handleRemove to include an accessible label
by adding either an aria-label (e.g., aria-label="Remove image") or a
visually-hidden text node (e.g., <span className="sr-only">Remove image</span>)
inside the button while keeping the existing styling and onClick handler; ensure
the label text clearly describes the action (Remove image) and that no visual
layout changes occur.
- Around line 73-80: The file-input does not clear its value after selecting or
removing a file, so re-selecting the same file may not fire change events;
update handleFileChange and handleRemove to reset the input value after
processing by either using the existing input event (e.g., set
e.currentTarget.value = '' after calling validateAndChange) or add a ref (e.g.,
fileInputRef) to the input element and set fileInputRef.current.value = '' after
calling validateAndChange in handleFileChange and after
setSizeError/onChangeImage in handleRemove so the same file can be reselected
reliably.
In `@src/providers/index.tsx`:
- Around line 19-23: Wrap the localStorage cleanup inside the useEffect in a
safe guard: detect environment (window and window.localStorage) and execute
localStorage.removeItem('user-info-storage') inside a try/catch (inside the same
useEffect) so any SecurityError/DOMException is caught and ignored or logged via
your logger; ensure the catch does not rethrow so the component init cannot
fail. Reference: the useEffect block containing
localStorage.removeItem('user-info-storage').
---
Outside diff comments:
In `@src/app/api/auth/clear-session/route.ts`:
- Around line 27-40: The code trusts the incoming Origin header when building
baseUrl (variable origin and baseUrl), which enables open-redirect manipulation;
replace use of request.headers.get('origin') with the server-parsed origin
request.nextUrl.origin (or otherwise derive baseUrl from request.nextUrl.origin)
and remove or ignore the untrusted Origin header, ensuring all fallback chain
(process.env... and request.url split) remains but using request.nextUrl.origin
as the primary trusted origin; update any tests or callers that assumed
header-origin behavior and keep baseUrl construction logic (baseUrl variable)
intact otherwise.
---
Nitpick comments:
In `@src/components/ui/image-upload-input.tsx`:
- Around line 90-91: Replace the template-string className composition in the
ImageUploadInput component with the clsx utility: import clsx (or classnames) at
the top and use clsx(inputStyles.base, isDragging ? inputStyles.dragging :
inputStyles.notDragging) instead of the current `${inputStyles.base}
${isDragging ? inputStyles.dragging : inputStyles.notDragging}` expression; this
touches the JSX prop where className is set and uses the existing inputStyles
and isDragging identifiers.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
next.config.tssrc/app/api/auth/clear-session/route.tssrc/components/ui/image-upload-input.tsxsrc/features/study/group/model/group-study-form.schema.tssrc/features/study/group/ui/step/step2-group.tsxsrc/providers/index.tsxsrc/stores/useUserStore.ts
| useEffect(() => { | ||
| // [보안 마이그레이션] sessionStorage 전환 이후 localStorage의 잔여 민감 데이터 정리. | ||
| // 기존 사용자 브라우저에 남아있는 user-info-storage를 삭제하여 XSS 노출 경로 제거. | ||
| localStorage.removeItem('user-info-storage'); | ||
| }, []); |
There was a problem hiding this comment.
스토리지 접근 예외를 방어 처리해 주세요.
브라우저 보안 설정에 따라 localStorage 접근이 예외를 던질 수 있어, 초기화 이펙트가 실패할 수 있습니다. 안전하게 감싸는 편이 좋습니다.
제안 패치
useEffect(() => {
- localStorage.removeItem('user-info-storage');
+ try {
+ localStorage.removeItem('user-info-storage');
+ } catch {
+ // storage 접근이 제한된 환경에서는 무시
+ }
}, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/providers/index.tsx` around lines 19 - 23, Wrap the localStorage cleanup
inside the useEffect in a safe guard: detect environment (window and
window.localStorage) and execute localStorage.removeItem('user-info-storage')
inside a try/catch (inside the same useEffect) so any SecurityError/DOMException
is caught and ignored or logged via your logger; ensure the catch does not
rethrow so the component init cannot fail. Reference: the useEffect block
containing localStorage.removeItem('user-info-storage').
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/app/api/auth/clear-session/route.ts (1)
4-11: 오픈 리다이렉트 방어 로직은 양호하지만, 백슬래시 처리를 추가하면 더 안전합니다.일부 브라우저에서 백슬래시(
\)를 슬래시로 해석할 수 있어,/\evil.com같은 경로가 예상치 못한 동작을 유발할 가능성이 있습니다. 방어적 코딩 관점에서 백슬래시 검증 추가를 권장합니다.🛡️ 백슬래시 검증 추가 제안
function isSafeRedirectPath(path: string) { if (!path) return false; if (!path.startsWith('/')) return false; // 절대 URL(https://evil.com) 차단 if (path.startsWith('//')) return false; // 프로토콜-상대 URL(//evil.com) 차단 + if (path.includes('\\')) return false; // 백슬래시 포함 경로 차단 return true; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/auth/clear-session/route.ts` around lines 4 - 11, The isSafeRedirectPath function should also reject paths containing backslashes to prevent browser interpretation of "\" as "/", so update isSafeRedirectPath to return false if path includes a backslash (e.g., check path.includes('\\') or a regex) in addition to the existing checks; ensure the new check references the isSafeRedirectPath function so the validation runs before allowing any redirect.src/features/study/group/model/group-study-form.schema.ts (1)
111-118:interviewPost항목도trim()을 선적용해 다른 필드와 일관성을 맞추는 것을 권장합니다.현재는 공백 포함 길이로
max가 평가되어, 앞뒤 공백이 많은 입력에서 UX가 다소 불일치할 수 있습니다.제안 diff
interviewPost: z .array( z .string() + .trim() .max( GROUP_STUDY_INTERVIEW_Q_MAX_LENGTH, `질문은 ${GROUP_STUDY_INTERVIEW_Q_MAX_LENGTH}자 이하로 입력해주세요.`, ), ) - .refine((arr) => arr.length > 0 && arr.every((v) => v.trim() !== ''), { + .refine((arr) => arr.length > 0 && arr.every((v) => v !== ''), { message: '모든 질문을 입력해야 합니다.', })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/study/group/model/group-study-form.schema.ts` around lines 111 - 118, The interviewPost array items are validated for max length without trimming, causing leading/trailing spaces to affect length checks; update the Zod schema for interviewPost to apply .trim() before .max (i.e., change the item validator from z.string().max(...) to z.string().trim().max(GROUP_STUDY_INTERVIEW_Q_MAX_LENGTH, ...)) so it matches other fields and enforces length on trimmed content; ensure the error message string remains the same and that this change is applied to the schema that constructs interviewPost.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/app/api/auth/clear-session/route.ts`:
- Around line 4-11: The isSafeRedirectPath function should also reject paths
containing backslashes to prevent browser interpretation of "\" as "/", so
update isSafeRedirectPath to return false if path includes a backslash (e.g.,
check path.includes('\\') or a regex) in addition to the existing checks; ensure
the new check references the isSafeRedirectPath function so the validation runs
before allowing any redirect.
In `@src/features/study/group/model/group-study-form.schema.ts`:
- Around line 111-118: The interviewPost array items are validated for max
length without trimming, causing leading/trailing spaces to affect length
checks; update the Zod schema for interviewPost to apply .trim() before .max
(i.e., change the item validator from z.string().max(...) to
z.string().trim().max(GROUP_STUDY_INTERVIEW_Q_MAX_LENGTH, ...)) so it matches
other fields and enforces length on trimmed content; ensure the error message
string remains the same and that this change is applied to the schema that
constructs interviewPost.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
next.config.tssrc/app/api/auth/clear-session/route.tssrc/features/study/group/model/group-study-form.schema.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- next.config.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/features/study/group/model/group-study-form.schema.ts (1)
111-125:superRefine를 사용하여 항목별 에러 경로를 지정하면 폼 피드백이 더 정확해집니다.현재
refine는 배열 단위 메시지만 제공하여 어떤 질문이 비었는지 바로 알기 어렵습니다.superRefine에서ctx.addIssue()로path: [...ctx.path, index]를 지정하면 각 항목별로 독립적인 에러를 표시할 수 있어 사용자 경험이 개선됩니다.Zod v4의 권장 패턴:
.superRefine((arr, ctx) => { arr.forEach((item, idx) => { if (!item || item.trim() === '') { ctx.addIssue({ code: 'custom', message: '질문을 입력해주세요.', path: [...ctx.path, idx], }); } }); if (arr.length > 10) { ctx.addIssue({ code: 'custom', message: '질문은 최대 10개까지만 입력할 수 있습니다.', path: ctx.path, }); } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/study/group/model/group-study-form.schema.ts` around lines 111 - 125, Replace the two array-level .refine(...) checks with a single .superRefine((arr, ctx) => { ... }) on the interview questions array schema: iterate arr with arr.forEach((item, idx) => { if (!item || item.trim() === '') ctx.addIssue({ code: 'custom', message: '질문을 입력해주세요.', path: [...ctx.path, idx] }) }) to add per-item errors, and check length with if (arr.length > 10) ctx.addIssue({ code: 'custom', message: '질문은 최대 10개까지만 입력할 수 있습니다.', path: ctx.path }); keep the existing GROUP_STUDY_INTERVIEW_Q_MAX_LENGTH validation on the string schema (the .max call) so item length still enforces that constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ui/image-upload-input.tsx`:
- Around line 29-39: The validateAndChange function leaves a previous sizeError
when a non-image file is dropped because it returns early on the type check;
update validateAndChange to clear any existing sizeError before returning for
non-image files (i.e., call setSizeError(null) when file.type does not start
with 'image/'), then keep the existing size check logic and onChangeImage(file)
call; reference validateAndChange, setSizeError, and onChangeImage to locate the
change.
---
Nitpick comments:
In `@src/features/study/group/model/group-study-form.schema.ts`:
- Around line 111-125: Replace the two array-level .refine(...) checks with a
single .superRefine((arr, ctx) => { ... }) on the interview questions array
schema: iterate arr with arr.forEach((item, idx) => { if (!item || item.trim()
=== '') ctx.addIssue({ code: 'custom', message: '질문을 입력해주세요.', path:
[...ctx.path, idx] }) }) to add per-item errors, and check length with if
(arr.length > 10) ctx.addIssue({ code: 'custom', message: '질문은 최대 10개까지만 입력할 수
있습니다.', path: ctx.path }); keep the existing GROUP_STUDY_INTERVIEW_Q_MAX_LENGTH
validation on the string schema (the .max call) so item length still enforces
that constant.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/app/api/auth/clear-session/route.tssrc/components/ui/image-upload-input.tsxsrc/features/study/group/model/group-study-form.schema.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/app/api/auth/clear-session/route.ts
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ui/image-upload-input.tsx`:
- Line 147: The validation message element that renders {sizeError} should
announce dynamically to assistive tech; update the JSX that renders sizeError
(the <p className="text-text-danger text-sm">{sizeError}</p>) to include an
accessibility live region such as role="alert" (or aria-live="assertive") so
screen readers are notified immediately when sizeError appears; keep the
conditional render as-is and only add the attribute to the <p> element.
- Line 96: Replace the template-string class composition with the project's cn()
util and CVA-based style tokens: stop using `${inputStyles.base} ${isDragging ?
inputStyles.dragging : inputStyles.notDragging}` and instead compose classes
with cn(inputStyles.base, isDragging ? inputStyles.dragging :
inputStyles.notDragging). If inputStyles is not already a cva(...) descriptor,
convert its definition to use cva(...) (exported as inputStyles) with variants
for dragging vs notDragging, then call cn(inputStyles(), { /* variant keys if
used */ }) or cn(inputStyles.base, ...) as appropriate so the component (using
isDragging) follows the project's CVA + cn pattern.
- Around line 101-106: Replace the raw Image tag using src="/icons/camera.svg"
with an SVG React component imported via the project's svgr setup: import the
SVG (e.g. camera.svg) as a React component and render that component in place of
the <Image ... src="/icons/camera.svg" .../> element (adjust props like
width/height/alt or className as needed); update the import at the top of the
file accordingly and remove the static Image usage so it matches other files
(e.g. home-dashboard.tsx) that use SVGs as React components.
In `@src/features/study/group/model/group-study-form.schema.ts`:
- Around line 110-136: The interviewPost array schema currently only validates
items so an empty array passes; update the interviewPost z.array(...) definition
to enforce at least one entry (e.g., call .min(1, { message: '질문을 최소 1개 입력해주세요.'
}) or use .nonempty({ message: '질문을 최소 1개 입력해주세요.' })) on the array schema while
keeping the inner string validation and the existing superRefine logic intact so
item-level checks and custom errors still run.
In `@src/features/study/group/ui/step/step3-group.tsx`:
- Around line 65-68: formState.errors.interviewPost array-level errors are not
rendered, only per-index errors are shown; update the component (step3-group.tsx
/ wherever interviewPost is rendered) to also render the array-level message
(formState.errors.interviewPost?.message or
formState.errors.interviewPost?.type) in addition to the existing per-item block
so users see messages like "최대 10개" — place the array-level error display above
or below the list of items where interviewPost is mapped.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/ui/image-upload-input.tsxsrc/features/study/group/model/group-study-form.schema.tssrc/features/study/group/ui/step/step3-group.tsx
| onDragEnter={handleDragEnter} | ||
| onDragLeave={handleDragLeave} | ||
| onDragOver={handleDragOver} | ||
| className={`${inputStyles.base} ${isDragging ? inputStyles.dragging : inputStyles.notDragging}`} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/components/ui/image-upload-input.tsx | head -150Repository: code-zero-to-one/study-platform-client
Length of output: 5532
🏁 Script executed:
grep -r "clsx\|tailwind-merge\|class-variance-authority" --include="package.json" --include="*.ts" --include="*.tsx" | head -20Repository: code-zero-to-one/study-platform-client
Length of output: 1650
🏁 Script executed:
cat -n src/components/ui/button/index.tsx | head -50Repository: code-zero-to-one/study-platform-client
Length of output: 2111
🏁 Script executed:
cat -n src/components/ui/button/index.tsx | sed -n '50,70p'Repository: code-zero-to-one/study-platform-client
Length of output: 739
🏁 Script executed:
cat -n src/components/ui/\(shadcn\)/lib/utils.tsRepository: code-zero-to-one/study-platform-client
Length of output: 292
동적 클래스 조합을 CVA와 cn() 유틸로 통일해 주세요.
현재 템플릿 문자열 조합은 프로젝트 스타일 가이드와 불일치합니다. 다른 UI 컴포넌트(Button, Badge, Chip 등)와 같이 CVA를 사용하고, 프로젝트의 cn() 유틸리티를 활용해주세요.
제안 패치
+import { cva } from 'class-variance-authority';
+import { cn } from '@/components/ui/(shadcn)/lib/utils';
import Image from 'next/image';
import { useState, DragEvent, ChangeEvent, useRef } from 'react';
import Button from '@/components/ui/button';
-const inputStyles = {
- base: 'rounded-100 flex w-full flex-col items-center justify-center border-2 p-500',
- dragging: 'border-border-brand bg-fill-brand-subtle-hover',
- notDragging: 'border-gray-300 border-dashed',
-};
+const inputStyles = cva(
+ 'rounded-100 flex w-full flex-col items-center justify-center border-2 p-500',
+ {
+ variants: {
+ dragging: {
+ true: 'border-border-brand bg-fill-brand-subtle-hover',
+ false: 'border-gray-300 border-dashed',
+ },
+ },
+ defaultVariants: {
+ dragging: false,
+ },
+ },
+);
...
- className={`${inputStyles.base} ${isDragging ? inputStyles.dragging : inputStyles.notDragging}`}
+ className={cn(inputStyles({ dragging: isDragging }))}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/image-upload-input.tsx` at line 96, Replace the
template-string class composition with the project's cn() util and CVA-based
style tokens: stop using `${inputStyles.base} ${isDragging ?
inputStyles.dragging : inputStyles.notDragging}` and instead compose classes
with cn(inputStyles.base, isDragging ? inputStyles.dragging :
inputStyles.notDragging). If inputStyles is not already a cva(...) descriptor,
convert its definition to use cva(...) (exported as inputStyles) with variants
for dragging vs notDragging, then call cn(inputStyles(), { /* variant keys if
used */ }) or cn(inputStyles.base, ...) as appropriate so the component (using
isDragging) follows the project's CVA + cn pattern.
| <Image | ||
| src="/icons/camera.svg" | ||
| width={32} | ||
| height={32} | ||
| alt="파일 업로드" | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if the file exists
fd "image-upload-input.tsx" src/Repository: code-zero-to-one/study-platform-client
Length of output: 122
🏁 Script executed:
# Check next.config configuration
cat -n next.config.tsRepository: code-zero-to-one/study-platform-client
Length of output: 5783
🏁 Script executed:
# Search for SVG imports to see current patterns
rg "\.svg['\"]" src/ -A 2 -B 2 | head -40Repository: code-zero-to-one/study-platform-client
Length of output: 2824
🏁 Script executed:
# Check if icons/camera.svg exists
fd "camera.svg"Repository: code-zero-to-one/study-platform-client
Length of output: 105
🏁 Script executed:
# Read the image-upload-input.tsx file to verify the current code
cat -n src/components/ui/image-upload-input.tsx | head -120 | tail -30Repository: code-zero-to-one/study-platform-client
Length of output: 1328
🏁 Script executed:
# Check the complete context around lines 101-106
sed -n '95,110p' src/components/ui/image-upload-input.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 709
🏁 Script executed:
# Check imports at the top of the file to understand current pattern
head -20 src/components/ui/image-upload-input.tsxRepository: code-zero-to-one/study-platform-client
Length of output: 683
SVG를 React 컴포넌트로 import해 주세요.
@svgr/webpack으로 설정된 SVG React 컴포넌트 import 방식을 사용하세요. 다른 파일들(예: home-dashboard.tsx)처럼 SVG를 컴포넌트로 가져와 사용하는 것이 프로젝트 가이드에 맞습니다.
제안 패치
+import CameraIcon from 'public/icons/camera.svg';
...
-<Image
- src="/icons/camera.svg"
- width={32}
- height={32}
- alt="파일 업로드"
-/>
+<CameraIcon
+ width={32}
+ height={32}
+ aria-label="파일 업로드"
+/>📝 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.
| <Image | |
| src="/icons/camera.svg" | |
| width={32} | |
| height={32} | |
| alt="파일 업로드" | |
| /> | |
| <CameraIcon | |
| width={32} | |
| height={32} | |
| aria-label="파일 업로드" | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/image-upload-input.tsx` around lines 101 - 106, Replace the
raw Image tag using src="/icons/camera.svg" with an SVG React component imported
via the project's svgr setup: import the SVG (e.g. camera.svg) as a React
component and render that component in place of the <Image ...
src="/icons/camera.svg" .../> element (adjust props like width/height/alt or
className as needed); update the import at the top of the file accordingly and
remove the static Image usage so it matches other files (e.g.
home-dashboard.tsx) that use SVGs as React components.
| )} | ||
| )} | ||
| </div> | ||
| {sizeError && <p className="text-text-danger text-sm">{sizeError}</p>} |
There was a problem hiding this comment.
오류 메시지에 라이브 리전을 추가해 주세요.
동적으로 바뀌는 검증 오류는 스크린리더에 즉시 전달되도록 role="alert"(또는 aria-live)를 붙이는 게 안전합니다.
제안 패치
- {sizeError && <p className="text-text-danger text-sm">{sizeError}</p>}
+ {sizeError && (
+ <p role="alert" aria-live="polite" className="text-text-danger text-sm">
+ {sizeError}
+ </p>
+ )}📝 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.
| {sizeError && <p className="text-text-danger text-sm">{sizeError}</p>} | |
| {sizeError && ( | |
| <p role="alert" aria-live="polite" className="text-text-danger text-sm"> | |
| {sizeError} | |
| </p> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/image-upload-input.tsx` at line 147, The validation message
element that renders {sizeError} should announce dynamically to assistive tech;
update the JSX that renders sizeError (the <p className="text-text-danger
text-sm">{sizeError}</p>) to include an accessibility live region such as
role="alert" (or aria-live="assertive") so screen readers are notified
immediately when sizeError appears; keep the conditional render as-is and only
add the attribute to the <p> element.
| {formState.errors.interviewPost?.[index]?.message && ( | ||
| <p role="alert" className="font-designer-14r text-text-error"> | ||
| {formState.errors.interviewPost[index]?.message as string} | ||
| </p> |
There was a problem hiding this comment.
배열 단위 검증 메시지가 화면에 표시되지 않습니다.
Line [65]~Line [68]은 항목 인덱스 에러만 렌더링해서, interviewPost 자체 에러(예: “최대 10개”)가 사용자에게 보이지 않을 수 있습니다. 배열 레벨 메시지도 별도로 렌더링해 주세요.
수정 예시
{questions.map((q, index) => (
<div key={index} className="flex flex-col gap-75">
@@
{formState.errors.interviewPost?.[index]?.message && (
<p role="alert" className="font-designer-14r text-text-error">
{formState.errors.interviewPost[index]?.message as string}
</p>
)}
</div>
))}
+ {typeof (formState.errors.interviewPost as { message?: string } | undefined)?.message === 'string' && (
+ <p role="alert" className="font-designer-14r text-text-error">
+ {(formState.errors.interviewPost as { message?: string }).message}
+ </p>
+ )}📝 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.
| {formState.errors.interviewPost?.[index]?.message && ( | |
| <p role="alert" className="font-designer-14r text-text-error"> | |
| {formState.errors.interviewPost[index]?.message as string} | |
| </p> | |
| {questions.map((q, index) => ( | |
| <div key={index} className="flex flex-col gap-75"> | |
| {formState.errors.interviewPost?.[index]?.message && ( | |
| <p role="alert" className="font-designer-14r text-text-error"> | |
| {formState.errors.interviewPost[index]?.message as string} | |
| </p> | |
| )} | |
| </div> | |
| ))} | |
| {typeof (formState.errors.interviewPost as { message?: string } | undefined)?.message === 'string' && ( | |
| <p role="alert" className="font-designer-14r text-text-error"> | |
| {(formState.errors.interviewPost as { message?: string }).message} | |
| </p> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/study/group/ui/step/step3-group.tsx` around lines 65 - 68,
formState.errors.interviewPost array-level errors are not rendered, only
per-index errors are shown; update the component (step3-group.tsx / wherever
interviewPost is rendered) to also render the array-level message
(formState.errors.interviewPost?.message or
formState.errors.interviewPost?.type) in addition to the existing per-item block
so users see messages like "최대 10개" — place the array-level error display above
or below the list of items where interviewPost is mapped.
🌱 연관된 이슈
☘️ 작업 내용
🍀 참고사항
스크린샷 (선택)
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정/안전성
기타