Skip to content

[REFACTOR] 강사 새 외주 작성 수정사항 반영#43

Merged
waldls merged 14 commits into
mainfrom
feature/#40-instructor_write-refactor
Jun 30, 2026
Merged

[REFACTOR] 강사 새 외주 작성 수정사항 반영#43
waldls merged 14 commits into
mainfrom
feature/#40-instructor_write-refactor

Conversation

@waldls

@waldls waldls commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

📢 PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호


✅ Key Changes

UT 이후 강사 새 외주 작성 부분의 UI 수정사항들을 반영했습니다.

  • 카테고리 섹션 UI 수정
  • 사이즈 선택 UI 개선 및 섹션 포커스 보더 색상 통일
  • 디자인 컨셉 키워드 카테고리 개편 및 Chip long 배리언트 추가
  • 디자인 컨셉 추가 입력창 및 키워드 선택 개수 제한 토스트 추가
  • 토글 컴포넌트 및 색상 선택 섹션 수정사항 반영
  • 자료 첨부 시 설명 필수 표시 및 필수 페이지 선택 섹션 텍스트 변경
  • 파일 업로드 에러처리 모달 추가
  • 날짜 드롭다운 항목 클릭 시 선택하기 없이 바로 날짜 확정되도록 인터랙션 추가
  • 텍스트 띄어쓰기 수정
  • 결제하기 버튼 위 에러 토스트 추가
  • FAQ 외부 링크 수정

📸 스크린샷 or 실행영상

2026-06-30.235804.mov

🎸 기타 사항 or 추가 코멘트

  • STEP3 플랜 선택의 설명문은 아직 서버 반영 전이라 이전 상태로 보입니다! (서버 release 하면 반영 O)

Summary by CodeRabbit

  • New Features
    • 디자인/인스트럭터 작성 화면에서 컨셉 키워드 선택이 최대 5개까지 가능해졌고, 선택한 항목을 바로 확인·삭제할 수 있게 되었습니다.
    • 파일 첨부 영역에 업로드 검증과 안내 모달이 추가되어, 허용되지 않는 파일이나 개수 초과 시 즉시 알림을 받을 수 있습니다.
  • Bug Fixes
    • 결제 오류 메시지가 토스트로 더 눈에 띄게 표시됩니다.
    • 날짜 선택, 사이드바 너비, 헤더/레이어 순서 등 일부 UI 동작과 표시가 더 안정적으로 개선되었습니다.

@waldls waldls self-assigned this Jun 30, 2026
@waldls waldls added ♻️ Refactor 코드 리팩토링 ❄️ 유민 labels Jun 30, 2026
@github-actions

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 549972a3-f01b-48a5-8530-9062cada685d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

워크스루

강사 외주 작성 페이지의 UT 수정사항을 반영하여, 카테고리/컨셉/색상/파일업로드/결제 모달 섹션의 UI 및 로직을 변경한다. 공유 UI 컴포넌트(Chip, Radio, Toast, Sidebar)를 확장하고, 파일 검증 유틸, 날짜 드롭다운 즉시 확정 인터랙션, CSS 토큰을 추가한다.

변경 사항

강사 외주 작성 페이지 UT 수정사항 반영

Layer / File(s) Summary
CSS 토큰 및 z-index 정의
src/app/globals.css, src/app/instructor/write/layout.tsx
--shadow-banner, --z-index-header, --z-index-toast 변수 추가, 레이아웃 헤더에 z-header 클래스 적용
Chip/Radio/Toast/Toggle 공유 컴포넌트 확장
src/shared/ui/Chip.tsx, src/shared/ui/Radio.tsx, src/shared/ui/Toast.tsx, src/shared/ui/Toggle.tsx, src/shared/ui/Header.tsx
Chip에 "long" variant·disabled·className 추가, Radio에 labelClassName 및 disabled opacity 제거, Toast 신규 컴포넌트 추가, Toggle 색상 클래스 변경, Header FAQ URL 교체
Sidebar ResizeObserver 기반 CSS 변수 동기화
src/shared/ui/Sidebar.tsx
클라이언트 컴포넌트로 전환, ResizeObserver--sidebar-w CSS 변수를 사이드바 너비에 맞게 실시간 동기화
파일 검증 유틸 및 날짜 계산 유틸 추가
src/shared/lib/utils/file.ts, src/shared/lib/utils/dropdown.ts
MAX_FILE_SIZE_BYTES·isAllowedFileType 추가, getDateForIndices(dayIndex 클램프 포함) 추가
DateDropdownMenu 아이템 클릭 즉시 확정
src/shared/ui/dropdown/WheelColumn.tsx, src/shared/ui/dropdown/DateDropdownMenu.tsx
WheelColumnonItemClick 콜백 추가, DateDropdownMenu에서 클릭 시 getDateForIndices로 날짜 계산 후 minDate 가드를 거쳐 onConfirm 즉시 호출
CATEGORIES/CONCEPT_CATEGORIES/KEYWORD_API_MAP 재구성
src/features/instructor/write/config/write.ts
홍보물·퍼스널 브랜딩 items 구체화, CONCEPT_CATEGORIES 새 키워드 그룹으로 교체, MAX_CONCEPT_SELECT 2→5, KEYWORD_API_MAP 전면 교체
CategorySection 기본 열림 및 Radio 비활성화 처리
src/widgets/instructor/write/ui/CategorySection.tsx
openIndex 초기값 null0, flex-wrap 레이아웃 도입, "교재 외지/내지" 외 항목 disabled 처리
디자인 컨셉 섹션 개편 및 ConceptResult 신규
src/features/instructor/write/ui/ConceptResult.tsx, src/features/instructor/write/ui/ConceptKeywordCard.tsx, src/widgets/instructor/write/ui/DesignConceptSection.tsx
ConceptResult 신규 컴포넌트(제거 가능 Chip 목록), ConceptKeywordCard"long" Chip 적용, 선택 제한 도달 시 Toast 표시 및 추가 요청 섹션 펼침 애니메이션 추가
색상 선택 섹션 및 colorMode 기본값 변경
src/features/instructor/write/model/writeFormStore.ts, src/features/instructor/write/ui/ColorChooseCard.tsx, src/widgets/instructor/write/ui/ColorChooseSection.tsx
colorMode 기본값 "custom""designer", ColorChooseCard에서 onRadioChange/Radio 제거, isMain을 위치(i===0) 기준으로 단순화
파일 업로드 섹션 검증 및 에러 모달
src/widgets/instructor/write/ui/AttachFileSection.tsx, src/widgets/instructor/write/ui/ReferenceSection.tsx
.png 확장자·30MB 용량 기준 필터링, 무효 파일·개수 초과 시 Modal 표시, 안내 문구 갱신
결제 모달 Step1 에러 Toast 전환
src/features/instructor/write/ui/PaymentModal/Step1.tsx
errorMessage 표시를 2.5초 자동 dismiss Toast로 교체, dismissedMessage 상태 및 타이머 useEffect 추가
기타 섹션 스타일·문구 일괄 변경
src/widgets/instructor/write/ui/NecessaryPageChooseSection.tsx, src/widgets/instructor/write/ui/SizeSection.tsx, src/widgets/instructor/write/ui/PlanChooseSection.tsx, src/widgets/instructor/write/ui/DeadlineChooseSection.tsx, src/widgets/instructor/write/ui/BasicInfoTypingSection.tsx
문구 변경, SizeSection 선택값 배지 추가, 포커스 border purple-40gray-40 통일

추정 코드 리뷰 노력

🎯 4 (Complex) | ⏱️ ~60 minutes

연관 가능성이 있는 PR

  • Ditda-Official/Ditda-Frontend#16: CATEGORIES, CONCEPT_CATEGORIES, colorMode 등 외주 작성 step-1 설정/상태를 최초 도입한 PR로, 이번 PR이 해당 설정값들을 전면 교체함
  • Ditda-Official/Ditda-Frontend#21: WriteLayout sticky 헤더를 처음 생성/리팩토링한 PR로, 이번 PR에서 동일 파일의 z-index 클래스를 변경함
  • Ditda-Official/Ditda-Frontend#30: globals.css에 Blur 토큰을 도입한 PR로, 이번 PR에서 해당 토큰 섹션 구조를 유지하며 shadow/z-index 토큰을 추가함

추천 레이블

✨ Feature UI

추천 리뷰어

  • KOJ50
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 새 외주 작성 수정사항 반영이라는 핵심 변경을 정확히 담고 있어 적절합니다.
Linked Issues check ✅ Passed 카테고리, 사이즈, 디자인 컨셉, 색상, 필수/레퍼런스, 플랜, 마감, 결제, 공통 테두리 요구사항이 모두 반영되었습니다.
Out of Scope Changes check ✅ Passed 명백한 범위 외 변경은 보이지 않으며, 추가 수정도 작성 플로우 UI 개편과 연관됩니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#40-instructor_write-refactor

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
src/widgets/instructor/write/ui/AttachFileSection.tsx (1)

25-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

중복 로직을 공유 훅으로 추출하는 것을 고려해주세요.

handleValidatedFilesAdded와 두 모달 상태/렌더링이 ReferenceSection과 거의 동일합니다(차이는 MAX_FILE_COUNT와 일부 문구뿐). 공유 훅(예: useValidatedFileUpload)으로 추출하면 중복을 줄이고 검증 규칙 변경 시 일관성을 유지할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/write/ui/AttachFileSection.tsx` around lines 25 - 42,
`handleValidatedFilesAdded` and its invalid-file/count modal state are
duplicating nearly the same upload-validation flow used in `ReferenceSection`;
extract the shared validation and modal handling into a reusable hook such as
`useValidatedFileUpload`, then have `AttachFileSection` consume it with its own
`MAX_FILE_COUNT` and UI text overrides. Keep the existing
`handleValidatedFilesAdded` entry point and wire its validation, modal
open/close state, and file-add callback through the shared hook so both sections
stay consistent when rules change.
src/widgets/instructor/write/ui/DesignConceptSection.tsx (2)

16-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

토스트/부제의 "5개"가 MAX_CONCEPT_SELECT와 중복되어 드리프트 위험이 있습니다.

LIMIT_TOAST_MESSAGE(라인 17)와 부제 "최대 5개까지"(라인 57)에 숫자가 하드코딩되어 있습니다. MAX_CONCEPT_SELECT는 이미 2→5로 한 번 변경된 이력이 있어, 다음 변경 시 문구가 어긋날 수 있습니다. 상수에서 파생하도록 변경을 권장합니다.

♻️ 제안
-const LIMIT_TOAST_MESSAGE =
-  "컨셉은 5개까지 선택할 수 있습니다. 추가적인 내용은 하단 토글을 열어 작성해주세요.";
+const LIMIT_TOAST_MESSAGE = `컨셉은 ${MAX_CONCEPT_SELECT}개까지 선택할 수 있습니다. 추가적인 내용은 하단 토글을 열어 작성해주세요.`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/write/ui/DesignConceptSection.tsx` around lines 16 -
17, The “5개” text is duplicated in both LIMIT_TOAST_MESSAGE and the subtitle,
creating drift risk from MAX_CONCEPT_SELECT. Update DesignConceptSection so the
toast message and the subtitle are derived from MAX_CONCEPT_SELECT instead of
hardcoding the number, using the existing MAX_CONCEPT_SELECT symbol to keep the
copy in sync when the limit changes.

77-85: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

조건부 클래스는 cn() 사용을 제안합니다.

라인 78, 83의 조건부 클래스 문자열을 템플릿 리터럴로 직접 이어붙이고 있습니다. cn() 유틸리티 사용을 권장합니다.

As per path instructions: "조건부 클래스 문자열을 직접 이어붙이는 경우 cn() 유틸리티 사용을 제안해주세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/write/ui/DesignConceptSection.tsx` around lines 77 -
85, The conditional class strings in DesignConceptSection should use the shared
cn() utility instead of inline template concatenation. Update the className
handling on the ArrowDownIcon and the collapsible container to build classes
through cn(), keeping the same isAdditionalOpen-dependent styling while making
the JSX consistent with the codebase’s class composition pattern.

Source: Path instructions

src/widgets/instructor/write/ui/CategorySection.tsx (2)

59-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

활성화 조건이 문자열 하드코딩에 묶여 있습니다.

disabled={item !== "교재 외지/내지"}로 인해 확장된 CATEGORIES의 나머지 항목들이 전부 비활성화됩니다. 의도된 동작(현재는 단일 항목만 활성)일 수 있으나, 매직 스트링이 UI 로직에 직접 박혀 있어 향후 라벨 변경 시 조용히 깨질 수 있습니다. 활성 항목을 config(예: CATEGORIES의 item 메타데이터)로 끌어올리는 것을 고려해주세요.

현재 단일 항목만 활성화하는 것이 의도된 사양인지 확인 부탁드립니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/write/ui/CategorySection.tsx` around lines 59 - 77,
The item enable/disable logic in CategorySection is hardcoded to a label string,
so the currently active option will break if the text changes or if CATEGORIES
expands. Move the enabled-state decision out of the JSX condition into
config-driven metadata on CATEGORIES (or a similar item property) and use that
in the Radio rendering, keeping the current single-active-item behavior only if
that is truly intended. Use the CategorySection component, the CATEGORIES list,
and the Radio props as the main points to update.

28-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

조건부 클래스 문자열은 cn() 사용을 제안합니다.

템플릿 리터럴로 조건부 클래스를 직접 이어붙이고 있습니다(라인 29, 44). 가독성과 일관성을 위해 cn() 유틸리티 사용을 권장합니다(같은 PR의 ColorChooseCard.tsx/ColorChooseSection.tsx는 이미 cn() 사용).

As per path instructions: "조건부 클래스 문자열을 직접 이어붙이는 경우 cn() 유틸리티 사용을 제안해주세요."

Also applies to: 43-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/write/ui/CategorySection.tsx` around lines 28 - 30,
조건부 클래스 문자열을 템플릿 리터럴로 직접 이어붙이고 있으므로 `cn()` 유틸리티로 통일하세요. `CategorySection`의 해당
`div`와 함께 `openIndex`에 따라 달라지는 클래스 조합을 `cn()` 호출로 옮겨 가독성과 일관성을 높이고, 같은 PR의
`ColorChooseCard`/`ColorChooseSection`처럼 패턴을 맞추세요. `CategorySection`의 JSX에서 조건부
`className`을 구성하는 부분을 찾아 직접 문자열 결합을 제거하면 됩니다.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/instructor/write/ui/ConceptResult.tsx`:
- Around line 10-22: In ConceptResult, the container currently uses a fixed
height that can clip wrapped chips when selectedKeywords spans multiple rows.
Update the wrapper in ConceptResult.tsx to avoid a hard height by switching to a
minimum height or, if you want to keep a single row, enforce no wrapping with
horizontal overflow; keep the existing Chip rendering and onRemove behavior
unchanged.

In `@src/features/instructor/write/ui/PaymentModal/Step1.tsx`:
- Around line 127-135: The auto-dismiss logic in Step1’s toast state conflicts
with the CTA disable logic because `showError` only controls visibility while
the button still depends on `!!errorMessage`, and `dismissedMessage` can
suppress re-showing the same error. Update the `Step1` component so the error
display and CTA disabled state use consistent conditions: either keep the toast
visible whenever `errorMessage` is present, or separate the transient toast
state from the actual blocking error state used by the button. Make sure the
`useEffect` timeout and `showError`/CTA logic are aligned so the user can still
understand why the action is blocked and the same error can reappear when
appropriate.

In `@src/shared/ui/Sidebar.tsx`:
- Around line 13-25: The Sidebar effect leaves a stale global CSS variable
behind because only ResizeObserver cleanup runs; update the cleanup in Sidebar’s
useEffect to also clear `--sidebar-w` from document.documentElement when the
component unmounts. Keep the existing updateWidth/ResizeObserver behavior, but
ensure the teardown removes the variable so consumers like DesignConceptSection
can fall back to 0 correctly when Sidebar is not mounted.

In `@src/shared/ui/Toast.tsx`:
- Around line 12-22: The Toast component is missing live region semantics, so
add an appropriate accessibility announcement to the main container in Toast so
screen readers are notified when the message appears. Update the div in
Toast.tsx to use role="alert" or role="status" with a matching aria-live value,
and make sure the hidden state used by show does not prevent the announcement
from being conveyed to assistive technology. Keep the change localized to the
Toast component and preserve the existing visual behavior while adding the
accessibility attributes.

In `@src/widgets/instructor/write/ui/AttachFileSection.tsx`:
- Around line 25-42: handleValidatedFilesAdded currently opens both
isInvalidFileModalOpen and isFileCountExceededModalOpen in the same call path,
so make these cases mutually exclusive. In AttachFileSection.tsx, update
handleValidatedFilesAdded to branch on invalid files first and return
immediately after opening the invalid-file modal, or otherwise ensure the
MAX_FILE_COUNT check only runs when there are no invalid files. Keep the
existing behavior in handleFilesAdded, isAllowedFileType, MAX_FILE_SIZE_BYTES,
and MAX_FILE_COUNT, but prevent both modals from being set true together.

In `@src/widgets/instructor/write/ui/NecessaryPageChooseSection.tsx`:
- Around line 31-33: The user-facing copy in NecessaryPageChooseSection needs a
spacing correction: change the phrase with “페이지 별” to “페이지별” in the heading text
rendered by that component. Update the string inside the h2 in
NecessaryPageChooseSection so the visible copy is polished and consistent with
the intended Korean spacing.

---

Nitpick comments:
In `@src/widgets/instructor/write/ui/AttachFileSection.tsx`:
- Around line 25-42: `handleValidatedFilesAdded` and its invalid-file/count
modal state are duplicating nearly the same upload-validation flow used in
`ReferenceSection`; extract the shared validation and modal handling into a
reusable hook such as `useValidatedFileUpload`, then have `AttachFileSection`
consume it with its own `MAX_FILE_COUNT` and UI text overrides. Keep the
existing `handleValidatedFilesAdded` entry point and wire its validation, modal
open/close state, and file-add callback through the shared hook so both sections
stay consistent when rules change.

In `@src/widgets/instructor/write/ui/CategorySection.tsx`:
- Around line 59-77: The item enable/disable logic in CategorySection is
hardcoded to a label string, so the currently active option will break if the
text changes or if CATEGORIES expands. Move the enabled-state decision out of
the JSX condition into config-driven metadata on CATEGORIES (or a similar item
property) and use that in the Radio rendering, keeping the current
single-active-item behavior only if that is truly intended. Use the
CategorySection component, the CATEGORIES list, and the Radio props as the main
points to update.
- Around line 28-30: 조건부 클래스 문자열을 템플릿 리터럴로 직접 이어붙이고 있으므로 `cn()` 유틸리티로 통일하세요.
`CategorySection`의 해당 `div`와 함께 `openIndex`에 따라 달라지는 클래스 조합을 `cn()` 호출로 옮겨 가독성과
일관성을 높이고, 같은 PR의 `ColorChooseCard`/`ColorChooseSection`처럼 패턴을 맞추세요.
`CategorySection`의 JSX에서 조건부 `className`을 구성하는 부분을 찾아 직접 문자열 결합을 제거하면 됩니다.

In `@src/widgets/instructor/write/ui/DesignConceptSection.tsx`:
- Around line 16-17: The “5개” text is duplicated in both LIMIT_TOAST_MESSAGE and
the subtitle, creating drift risk from MAX_CONCEPT_SELECT. Update
DesignConceptSection so the toast message and the subtitle are derived from
MAX_CONCEPT_SELECT instead of hardcoding the number, using the existing
MAX_CONCEPT_SELECT symbol to keep the copy in sync when the limit changes.
- Around line 77-85: The conditional class strings in DesignConceptSection
should use the shared cn() utility instead of inline template concatenation.
Update the className handling on the ArrowDownIcon and the collapsible container
to build classes through cn(), keeping the same isAdditionalOpen-dependent
styling while making the JSX consistent with the codebase’s class composition
pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d37763e9-d3b0-46d9-9cbd-4e73f3d7ddd4

📥 Commits

Reviewing files that changed from the base of the PR and between e639619 and bbcdfe4.

📒 Files selected for processing (28)
  • src/app/globals.css
  • src/app/instructor/write/layout.tsx
  • src/features/instructor/write/config/write.ts
  • src/features/instructor/write/model/writeFormStore.ts
  • src/features/instructor/write/ui/ColorChooseCard.tsx
  • src/features/instructor/write/ui/ConceptKeywordCard.tsx
  • src/features/instructor/write/ui/ConceptResult.tsx
  • src/features/instructor/write/ui/PaymentModal/Step1.tsx
  • src/shared/lib/utils/dropdown.ts
  • src/shared/lib/utils/file.ts
  • src/shared/ui/Chip.tsx
  • src/shared/ui/Header.tsx
  • src/shared/ui/Radio.tsx
  • src/shared/ui/Sidebar.tsx
  • src/shared/ui/Toast.tsx
  • src/shared/ui/Toggle.tsx
  • src/shared/ui/dropdown/DateDropdownMenu.tsx
  • src/shared/ui/dropdown/WheelColumn.tsx
  • src/widgets/instructor/write/ui/AttachFileSection.tsx
  • src/widgets/instructor/write/ui/BasicInfoTypingSection.tsx
  • src/widgets/instructor/write/ui/CategorySection.tsx
  • src/widgets/instructor/write/ui/ColorChooseSection.tsx
  • src/widgets/instructor/write/ui/DeadlineChooseSection.tsx
  • src/widgets/instructor/write/ui/DesignConceptSection.tsx
  • src/widgets/instructor/write/ui/NecessaryPageChooseSection.tsx
  • src/widgets/instructor/write/ui/PlanChooseSection.tsx
  • src/widgets/instructor/write/ui/ReferenceSection.tsx
  • src/widgets/instructor/write/ui/SizeSection.tsx

Comment on lines +10 to +22
<div className="rounded-12 bg-gray-5 flex h-12.5 flex-row gap-8 px-4 py-2">
<p className="text-gray-80 text-body1-m flex items-center">선택한 컨셉</p>
<div className="flex flex-row flex-wrap gap-2">
{selectedKeywords.map(keyword => (
<Chip
key={keyword}
label={keyword}
variant="removable"
onRemove={() => onRemove(keyword)}
/>
))}
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## ConceptResult.tsx\n'
sed -n '1,220p' src/features/instructor/write/ui/ConceptResult.tsx

printf '\n## Chip component references\n'
rg -n "export .*Chip|function Chip|const Chip|<Chip" src -g '*.{ts,tsx}' -A 3 -B 3

printf '\n## h-12.5 / rounded-12 utility definitions if present\n'
rg -n "h-12\\.5|rounded-12|text-body1-m|bg-gray-5|text-gray-80" src -g '*.{ts,tsx,css,scss,less}' -A 2 -B 2

Repository: Ditda-Official/Ditda-Frontend

Length of output: 50385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Chip.tsx (relevant slices)\n'
sed -n '1,220p' src/shared/ui/Chip.tsx

printf '\n## ConceptResult usage and surrounding layout\n'
rg -n "ConceptResult|selectedKeywords" src/features/instructor/write src/widgets/instructor/write -g '*.{ts,tsx}' -A 5 -B 5

printf '\n## Nearby container widths in write flow\n'
rg -n "w-[0-9]|max-w-|min-w-|flex-wrap|overflow" src/features/instructor/write src/widgets/instructor/write -g '*.{ts,tsx}' -A 2 -B 2

Repository: Ditda-Official/Ditda-Frontend

Length of output: 33087


src/features/instructor/write/ui/ConceptResult.tsx: 고정 높이는 제거하는 편이 안전합니다.
selectedKeywords가 최대 5개라 좁은 폭에서는 칩이 2줄로 래핑될 수 있는데, 현재 h-12.5로 고정돼 있어 두 번째 줄이 박스 밖으로 밀려나 아래 영역과 겹칠 수 있습니다. min-h-12.5로 바꾸거나, 한 줄만 유지하려면 flex-nowrap + overflow-x-auto로 맞춰주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/instructor/write/ui/ConceptResult.tsx` around lines 10 - 22, In
ConceptResult, the container currently uses a fixed height that can clip wrapped
chips when selectedKeywords spans multiple rows. Update the wrapper in
ConceptResult.tsx to avoid a hard height by switching to a minimum height or, if
you want to keep a single row, enforce no wrapping with horizontal overflow;
keep the existing Chip rendering and onRemove behavior unchanged.

Comment on lines +127 to +135
const [dismissedMessage, setDismissedMessage] = useState<string | null | undefined>(null);
const showError = !!errorMessage && errorMessage !== dismissedMessage;

useEffect(() => {
if (!errorMessage || errorMessage === dismissedMessage) return;
const timeout = setTimeout(() => setDismissedMessage(errorMessage), 2500);
return () => clearTimeout(timeout);
}, [errorMessage, dismissedMessage]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

토스트 자동 숨김과 CTA 비활성 조건이 충돌합니다.

지금 구현은 2.5초 뒤 토스트만 사라지고 버튼은 계속 !!errorMessage 때문에 비활성화됩니다. 그래서 사용자는 막힌 이유를 더 이상 볼 수 없고, 같은 문구가 다시 들어와도 dismissedMessage 때문에 토스트가 재노출되지 않습니다. errorMessage가 살아있는 동안은 토스트를 유지하거나, 반대로 토스트를 일시적으로만 보여줄 거라면 버튼 비활성 조건을 분리해야 합니다.

Also applies to: 193-194

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/instructor/write/ui/PaymentModal/Step1.tsx` around lines 127 -
135, The auto-dismiss logic in Step1’s toast state conflicts with the CTA
disable logic because `showError` only controls visibility while the button
still depends on `!!errorMessage`, and `dismissedMessage` can suppress
re-showing the same error. Update the `Step1` component so the error display and
CTA disabled state use consistent conditions: either keep the toast visible
whenever `errorMessage` is present, or separate the transient toast state from
the actual blocking error state used by the button. Make sure the `useEffect`
timeout and `showError`/CTA logic are aligned so the user can still understand
why the action is blocked and the same error can reappear when appropriate.

Comment thread src/shared/ui/Sidebar.tsx
Comment on lines +13 to +25
useEffect(() => {
const node = asideRef.current;
if (!node) return;

const updateWidth = () => {
document.documentElement.style.setProperty("--sidebar-w", `${node.offsetWidth}px`);
};

updateWidth();
const observer = new ResizeObserver(updateWidth);
observer.observe(node);
return () => observer.disconnect();
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

언마운트 시 --sidebar-w를 제거하지 않아 전역 변수가 남습니다.

--sidebar-wdocument.documentElement(전역)에 설정되는데, 클린업에서 observer.disconnect()만 하고 변수는 그대로 남습니다. Sidebar가 없는 페이지로 이동하면 이전 값이 유지되어, 이를 소비하는 DesignConceptSectionleft-[calc(50%+var(--sidebar-w,0)/2)]에서 0 폴백이 적용되지 않고 잘못된 오프셋이 발생합니다.

🧹 제안
     updateWidth();
     const observer = new ResizeObserver(updateWidth);
     observer.observe(node);
-    return () => observer.disconnect();
+    return () => {
+      observer.disconnect();
+      document.documentElement.style.removeProperty("--sidebar-w");
+    };
📝 Committable suggestion

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

Suggested change
useEffect(() => {
const node = asideRef.current;
if (!node) return;
const updateWidth = () => {
document.documentElement.style.setProperty("--sidebar-w", `${node.offsetWidth}px`);
};
updateWidth();
const observer = new ResizeObserver(updateWidth);
observer.observe(node);
return () => observer.disconnect();
}, []);
useEffect(() => {
const node = asideRef.current;
if (!node) return;
const updateWidth = () => {
document.documentElement.style.setProperty("--sidebar-w", `${node.offsetWidth}px`);
};
updateWidth();
const observer = new ResizeObserver(updateWidth);
observer.observe(node);
return () => {
observer.disconnect();
document.documentElement.style.removeProperty("--sidebar-w");
};
}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/Sidebar.tsx` around lines 13 - 25, The Sidebar effect leaves a
stale global CSS variable behind because only ResizeObserver cleanup runs;
update the cleanup in Sidebar’s useEffect to also clear `--sidebar-w` from
document.documentElement when the component unmounts. Keep the existing
updateWidth/ResizeObserver behavior, but ensure the teardown removes the
variable so consumers like DesignConceptSection can fall back to 0 correctly
when Sidebar is not mounted.

Comment thread src/shared/ui/Toast.tsx
Comment on lines +12 to +22
<div
className={cn(
"rounded-8 bg-gray-90 text-body1-sb shadow-banner z-toast flex items-center gap-2.5 p-4 text-white transition-all duration-300",
show ? "translate-y-0 opacity-100" : "pointer-events-none -translate-y-2 opacity-0",
className,
)}
>
<ExclamationMarkCircleIcon className="size-5 shrink-0 text-white" />
<span>{message}</span>
</div>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

스크린리더 안내를 위한 라이브 리전 속성 추가를 권장합니다.

토스트는 동적으로 노출되는 알림이므로 role="alert"(또는 role="status" + aria-live="assertive"/"polite")가 없으면 보조기술 사용자에게 메시지가 전달되지 않습니다. 또한 show=false일 때도 DOM에 남아 있어 항상 읽히지 않도록 라이브 리전 적용이 필요합니다.

♿ 제안
     <div
+      role="alert"
+      aria-live="assertive"
       className={cn(
📝 Committable suggestion

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

Suggested change
<div
className={cn(
"rounded-8 bg-gray-90 text-body1-sb shadow-banner z-toast flex items-center gap-2.5 p-4 text-white transition-all duration-300",
show ? "translate-y-0 opacity-100" : "pointer-events-none -translate-y-2 opacity-0",
className,
)}
>
<ExclamationMarkCircleIcon className="size-5 shrink-0 text-white" />
<span>{message}</span>
</div>
);
<div
role="alert"
aria-live="assertive"
className={cn(
"rounded-8 bg-gray-90 text-body1-sb shadow-banner z-toast flex items-center gap-2.5 p-4 text-white transition-all duration-300",
show ? "translate-y-0 opacity-100" : "pointer-events-none -translate-y-2 opacity-0",
className,
)}
>
<ExclamationMarkCircleIcon className="size-5 shrink-0 text-white" />
<span>{message}</span>
</div>
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/Toast.tsx` around lines 12 - 22, The Toast component is missing
live region semantics, so add an appropriate accessibility announcement to the
main container in Toast so screen readers are notified when the message appears.
Update the div in Toast.tsx to use role="alert" or role="status" with a matching
aria-live value, and make sure the hidden state used by show does not prevent
the announcement from being conveyed to assistive technology. Keep the change
localized to the Toast component and preserve the existing visual behavior while
adding the accessibility attributes.

Comment on lines +25 to +42
const handleValidatedFilesAdded = (files: File[]) => {
const validFiles = files.filter(
file => isAllowedFileType(file, [".png"]) && file.size <= MAX_FILE_SIZE_BYTES,
);

if (validFiles.length < files.length) {
setIsInvalidFileModalOpen(true);
}

if (validFiles.length === 0) return;

if (uploadedFiles.length + validFiles.length > MAX_FILE_COUNT) {
setIsFileCountExceededModalOpen(true);
return;
}

handleFilesAdded(validFiles);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

무효 파일과 개수 초과가 동시에 발생하면 두 모달이 함께 열립니다.

validFiles.length < files.lengthisInvalidFileModalOpen을 켠 뒤, 같은 호출에서 개수 초과 조건이 참이면 isFileCountExceededModalOpen도 켜져 두 모달이 동시에 렌더링됩니다. 두 상황을 상호 배타적으로 처리하는 것이 좋습니다.

🛠️ 제안 수정
   const handleValidatedFilesAdded = (files: File[]) => {
     const validFiles = files.filter(
       file => isAllowedFileType(file, [".png"]) && file.size <= MAX_FILE_SIZE_BYTES,
     );

-    if (validFiles.length < files.length) {
-      setIsInvalidFileModalOpen(true);
-    }
-
-    if (validFiles.length === 0) return;
-
-    if (uploadedFiles.length + validFiles.length > MAX_FILE_COUNT) {
+    if (validFiles.length < files.length) {
+      setIsInvalidFileModalOpen(true);
+      return;
+    }
+
+    if (validFiles.length === 0) return;
+
+    if (uploadedFiles.length + validFiles.length > MAX_FILE_COUNT) {
       setIsFileCountExceededModalOpen(true);
       return;
     }

     handleFilesAdded(validFiles);
   };
📝 Committable suggestion

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

Suggested change
const handleValidatedFilesAdded = (files: File[]) => {
const validFiles = files.filter(
file => isAllowedFileType(file, [".png"]) && file.size <= MAX_FILE_SIZE_BYTES,
);
if (validFiles.length < files.length) {
setIsInvalidFileModalOpen(true);
}
if (validFiles.length === 0) return;
if (uploadedFiles.length + validFiles.length > MAX_FILE_COUNT) {
setIsFileCountExceededModalOpen(true);
return;
}
handleFilesAdded(validFiles);
};
const handleValidatedFilesAdded = (files: File[]) => {
const validFiles = files.filter(
file => isAllowedFileType(file, [".png"]) && file.size <= MAX_FILE_SIZE_BYTES,
);
if (validFiles.length < files.length) {
setIsInvalidFileModalOpen(true);
return;
}
if (validFiles.length === 0) return;
if (uploadedFiles.length + validFiles.length > MAX_FILE_COUNT) {
setIsFileCountExceededModalOpen(true);
return;
}
handleFilesAdded(validFiles);
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/write/ui/AttachFileSection.tsx` around lines 25 - 42,
handleValidatedFilesAdded currently opens both isInvalidFileModalOpen and
isFileCountExceededModalOpen in the same call path, so make these cases mutually
exclusive. In AttachFileSection.tsx, update handleValidatedFilesAdded to branch
on invalid files first and return immediately after opening the invalid-file
modal, or otherwise ensure the MAX_FILE_COUNT check only runs when there are no
invalid files. Keep the existing behavior in handleFilesAdded,
isAllowedFileType, MAX_FILE_SIZE_BYTES, and MAX_FILE_COUNT, but prevent both
modals from being set true together.

Comment on lines 31 to +33
<h2 className="text-body2-m text-gray-70">
작업물에 필수적으로 들어갈 페이지를 선택해주세요
선택한 페이지에 대한 페이지 별 추가 디자인 요청사항과 원하시는 레이아웃이 있다면
작성해주세요.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

문구 띄어쓰기를 수정해 주세요.

Line 32의 페이지 별페이지별이 맞습니다. 사용자에게 바로 보이는 문구라 이번 텍스트 정리 범위에서 같이 바로잡는 편이 좋겠습니다.

수정 예시
-          선택한 페이지에 대한 페이지 별 추가 디자인 요청사항과 원하시는 레이아웃이 있다면
+          선택한 페이지에 대한 페이지별 추가 디자인 요청사항과 원하시는 레이아웃이 있다면
📝 Committable suggestion

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

Suggested change
<h2 className="text-body2-m text-gray-70">
작업물에 필수적으로 들어갈 페이지를 선택해주세요
선택한 페이지에 대한 페이지 추가 디자인 요청사항과 원하시는 레이아웃이 있다면
작성해주세요.
<h2 className="text-body2-m text-gray-70">
선택한 페이지에 대한 페이지별 추가 디자인 요청사항과 원하시는 레이아웃이 있다면
작성해주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/write/ui/NecessaryPageChooseSection.tsx` around lines
31 - 33, The user-facing copy in NecessaryPageChooseSection needs a spacing
correction: change the phrase with “페이지 별” to “페이지별” in the heading text
rendered by that component. Update the string inside the h2 in
NecessaryPageChooseSection so the visible copy is polished and consistent with
the intended Korean spacing.

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

@KOJ50 KOJ50 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

새 외주 작성하기 step2에서 선택으로 넣을 수 있는 파일을 넣으면 아래 설명이 필수로 바뀌는데 설명을 입력하지 않아도 다음 스텝으로 넘어갈 수 있습니다..! 확인부탁드립니당

@waldls

waldls commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

새 외주 작성하기 step2에서 선택으로 넣을 수 있는 파일을 넣으면 아래 설명이 필수로 바뀌는데 설명을 입력하지 않아도 다음 스텝으로 넘어갈 수 있습니다..! 확인부탁드립니당

step1->2 , step2->3으로 넘어가는 플로우들은 API 연동하면서 한꺼번에 잡는게 편할 것 같아서 아직 진행하지 않았습니다! 🥲
PR에 해당 내용 적어둔다는 것을 깜빡했네욥 죄송합니다

@github-actions

Copy link
Copy Markdown

@KOJ50 KOJ50 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고했어요~

@waldls waldls merged commit 52ed18e into main Jun 30, 2026
2 checks passed
@waldls waldls deleted the feature/#40-instructor_write-refactor branch June 30, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 강사 새 외주 작성 수정사항 반영

3 participants