[JDDEV-65] [JDDEV-66] Feat: 자소서 작성 페이지 UI 구현#57
Conversation
📝 WalkthroughWalkthroughThis PR restructures the virtual job application flow into apply/virtual/[id], lifts state to parent pages (controlled/forwardRef patterns), adds sessionStorage persistence for selected questions and answers, and applies minor UI/layout updates across common components. ChangesVirtual Job Application Flow Restructure
🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
jobdri/src/components/common/header/Header.tsx (1)
80-93:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPast steps missing
bg-fill-quaternary-defaultstyling.The PR objectives specify three distinct visual states for step badges:
- current step:
bg-white- past steps:
bg-fill-quaternary-default- incomplete:
bg-fill-disabledHowever, the current logic only distinguishes
isCurrentvs not-current, so past completed steps incorrectly receivebg-fill-disabledinstead ofbg-fill-quaternary-default.🎨 Proposed fix to add three-way step styling
<span className={clsx( "flex aspect-square h-5 w-5 items-center justify-center gap-2.5 rounded-icon-round text-cap12-med [font-feature-settings:'liga'_off,'clig'_off]", "tracking-normal", isCurrent ? "bg-white text-text-neutral-description " - : "bg-fill-disabled text-text-neutral-disabled", + : reached + ? "bg-fill-quaternary-default text-text-neutral-description" + : "bg-fill-disabled text-text-neutral-disabled", )} >🤖 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 `@jobdri/src/components/common/header/Header.tsx` around lines 80 - 93, The step badge styling only checks isCurrent (stepNumber === currentStep) so completed past steps fall into the non-current branch; add a third state by computing isPast (e.g., const isPast = stepNumber < currentStep) and update the className conditional in Header.tsx to apply three-way classes: when isCurrent use the "bg-white text-text-neutral-description" set, when isPast use "bg-fill-quaternary-default" with the appropriate text color for completed steps, otherwise use the existing "bg-fill-disabled text-text-neutral-disabled".
🤖 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 `@jobdri/src/app/apply/virtual/`[id]/questions/page.tsx:
- Line 40: The backAction href currently points to `/apply/virtual/${id}/jd`
which is an invalid route; update the backAction prop in the component (the JSX
line with backAction={{ href: `/apply/virtual/${id}/jd` }}) to point to the
correct previous step route, e.g. `/apply/virtual/${id}/jd-review` (or
`/apply/virtual/${id}/jd-input` if this page should go back to the input step)
so the back navigation doesn't 404.
In `@jobdri/src/app/apply/virtual/`[id]/write/page.tsx:
- Around line 19-25: The page allows navigation to the result even when
selectedQuestions is missing; update the client-side page component (the parent
of InputSection and Footer) to guard using the selectedQuestions state: if
selectedQuestions is undefined or empty, perform a client-side redirect with
next/navigation's useRouter (or useRouter().replace) to the questions step
(e.g., `/apply/virtual/${id}/questions`) or disable the Footer CTA by passing a
falsy/disabled ctaAction/ctaLabel; locate the selectedQuestions usage and add a
useEffect that checks its length and triggers the redirect (or conditionally
render Footer with disabled CTA) so users cannot reach the result without
prerequisites.
In `@jobdri/src/components/apply/InputSection.tsx`:
- Around line 34-35: isComplete currently computes completion using global
COMPLETE_THRESHOLD and MAX_LENGTH (e.g., references to isComplete,
COMPLETE_THRESHOLD, MAX_LENGTH), which yields wrong badges for questions with
different limits; change isComplete to accept the specific question max length
(e.g., isComplete(text: string, maxLength: number)) or compute using the
question's max (question.maxLength) at call sites, and evaluate completion using
that max (for example: text.length >= Math.ceil(maxLength *
COMPLETE_THRESHOLD_PERCENT) && text.length <= maxLength) so every badge/meter
uses the per-question limit instead of the fixed MAX_LENGTH.
- Around line 21-27: The useEffect that reads
sessionStorage.getItem("selectedQuestions") and calls JSON.parse(raw) can throw
on malformed data; wrap the parse in a try/catch, validate the parsed value is
an array of Question-like objects (check length, typeof entries and required
fields) before calling setQuestions and setTexts, and on failure either clear
the session key or skip state updates so the component doesn't crash; update the
block around sessionStorage.getItem("selectedQuestions") / JSON.parse(raw) and
the subsequent setQuestions/setTexts calls to use this guarded
parse-and-validate flow.
---
Outside diff comments:
In `@jobdri/src/components/common/header/Header.tsx`:
- Around line 80-93: The step badge styling only checks isCurrent (stepNumber
=== currentStep) so completed past steps fall into the non-current branch; add a
third state by computing isPast (e.g., const isPast = stepNumber < currentStep)
and update the className conditional in Header.tsx to apply three-way classes:
when isCurrent use the "bg-white text-text-neutral-description" set, when isPast
use "bg-fill-quaternary-default" with the appropriate text color for completed
steps, otherwise use the existing "bg-fill-disabled text-text-neutral-disabled".
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 485446f0-d028-4d52-ae2c-ad3427686328
📒 Files selected for processing (25)
jobdri/src/app/apply/apply-type/ApplyTypePageClient.tsxjobdri/src/app/apply/apply-type/page.tsxjobdri/src/app/apply/page.tsxjobdri/src/app/apply/virtual/[id]/(jd)/jd-input/JdInputPageClient.tsxjobdri/src/app/apply/virtual/[id]/(jd)/jd-input/page.tsxjobdri/src/app/apply/virtual/[id]/(jd)/jd-review/JdReviewPageClient.tsxjobdri/src/app/apply/virtual/[id]/(jd)/jd-review/page.tsxjobdri/src/app/apply/virtual/[id]/(jd)/page.tsxjobdri/src/app/apply/virtual/[id]/questions/page.tsxjobdri/src/app/apply/virtual/[id]/result/page.tsxjobdri/src/app/apply/virtual/[id]/write/page.tsxjobdri/src/app/apply/virtual/page.tsxjobdri/src/app/mock-application/jd-input/page.tsxjobdri/src/app/mock-application/jd-review/JdReviewPageClient.tsxjobdri/src/app/mock-application/jd-review/page.tsxjobdri/src/app/mock-application/page.tsxjobdri/src/components/apply/InputSection.tsxjobdri/src/components/apply/SelectQuestion.tsxjobdri/src/components/common/chips/ChipQnumber.tsxjobdri/src/components/common/footer/Footer.tsxjobdri/src/components/common/header/Header.tsxjobdri/src/components/common/input/InputMultiLine1000.tsxjobdri/src/components/common/modal/ModalInput.tsxjobdri/src/components/common/modal/ModalNotice.tsxjobdri/src/components/mock-application/MockApplicationHomePageClient.tsx
💤 Files with no reviewable changes (5)
- jobdri/src/app/apply/virtual/page.tsx
- jobdri/src/app/mock-application/page.tsx
- jobdri/src/app/mock-application/jd-review/JdReviewPageClient.tsx
- jobdri/src/app/mock-application/jd-review/page.tsx
- jobdri/src/app/mock-application/jd-input/page.tsx
| </main> | ||
| <Footer | ||
| ctaLabel="확정하기" | ||
| backAction={{ href: `/apply/virtual/${id}/jd` }} |
There was a problem hiding this comment.
Fix back navigation to a valid JD step route.
Line 40 uses /apply/virtual/${id}/jd, but this flow uses /apply/virtual/${id}/jd-review/jd-input. This back link is likely broken and can 404.
🔧 Proposed fix
- backAction={{ href: `/apply/virtual/${id}/jd` }}
+ backAction={{ href: `/apply/virtual/${id}/jd-review` }}📝 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.
| backAction={{ href: `/apply/virtual/${id}/jd` }} | |
| backAction={{ href: `/apply/virtual/${id}/jd-review` }} |
🤖 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 `@jobdri/src/app/apply/virtual/`[id]/questions/page.tsx at line 40, The
backAction href currently points to `/apply/virtual/${id}/jd` which is an
invalid route; update the backAction prop in the component (the JSX line with
backAction={{ href: `/apply/virtual/${id}/jd` }}) to point to the correct
previous step route, e.g. `/apply/virtual/${id}/jd-review` (or
`/apply/virtual/${id}/jd-input` if this page should go back to the input step)
so the back navigation doesn't 404.
| useEffect(() => { | ||
| const raw = sessionStorage.getItem("selectedQuestions"); | ||
| if (raw) { | ||
| const parsed: Question[] = JSON.parse(raw); | ||
| setQuestions(parsed); | ||
| setTexts(new Array(parsed.length).fill("")); | ||
| } |
There was a problem hiding this comment.
Harden sessionStorage parsing to prevent client-side crashes.
Malformed or stale selectedQuestions data will throw during JSON.parse and break the page. Add guarded parsing + shape checks before setting state.
Proposed fix
useEffect(() => {
const raw = sessionStorage.getItem("selectedQuestions");
- if (raw) {
- const parsed: Question[] = JSON.parse(raw);
- setQuestions(parsed);
- setTexts(new Array(parsed.length).fill(""));
- }
+ if (!raw) return;
+ try {
+ const parsed = JSON.parse(raw);
+ if (!Array.isArray(parsed)) return;
+
+ const normalized = parsed.filter(
+ (q): q is Question =>
+ typeof q?.id === "string" && typeof q?.question === "string",
+ );
+
+ setQuestions(normalized);
+ setTexts(new Array(normalized.length).fill(""));
+ } catch {
+ setQuestions([]);
+ setTexts([]);
+ }
}, []);🤖 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 `@jobdri/src/components/apply/InputSection.tsx` around lines 21 - 27, The
useEffect that reads sessionStorage.getItem("selectedQuestions") and calls
JSON.parse(raw) can throw on malformed data; wrap the parse in a try/catch,
validate the parsed value is an array of Question-like objects (check length,
typeof entries and required fields) before calling setQuestions and setTexts,
and on failure either clear the session key or skip state updates so the
component doesn't crash; update the block around
sessionStorage.getItem("selectedQuestions") / JSON.parse(raw) and the subsequent
setQuestions/setTexts calls to use this guarded parse-and-validate flow.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@jobdri/src/components/apply/InputSection.tsx`:
- Around line 37-44: isComplete currently uses a fixed ">= 1" threshold which is
too lenient; change the logic to use a percentage-based required length per
question (based on each question's maxLength) instead of a fixed 1 char.
Introduce or use a COMPLETION_PERCENT constant (e.g. 0.6) and compute
requiredLength = Math.ceil((q.maxLength ?? 1000) * COMPLETION_PERCENT), then
update isComplete/checkAllComplete so they compare currentTexts[i].length >=
requiredLength (or change isComplete signature to accept maxLength and compute
the threshold there) and ensure questions.every uses that threshold calculation
for each question.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b5be9f1b-c931-4e62-a920-c8417ce57ec3
📒 Files selected for processing (2)
jobdri/src/app/apply/virtual/[id]/write/page.tsxjobdri/src/components/apply/InputSection.tsx
| const isComplete = (text: string, maxLength: number) => | ||
| text.length >= 1 && text.length <= maxLength; | ||
|
|
||
| const checkAllComplete = (currentTexts: string[]) => | ||
| questions.length > 0 && | ||
| questions.every((q, i) => | ||
| isComplete(currentTexts[i] ?? "", q.maxLength ?? 1000), | ||
| ); |
There was a problem hiding this comment.
isComplete threshold is too lenient and breaks completion semantics.
isComplete now marks answers complete at >= 1 char, but this flow’s completion rule is percentage-based per question max length. This causes chips and allComplete to flip to complete too early.
Proposed fix
- const isComplete = (text: string, maxLength: number) =>
- text.length >= 1 && text.length <= maxLength;
+ const COMPLETE_RATIO = 0.5;
+ const isComplete = (text: string, maxLength: number) => {
+ const minLength = Math.ceil(maxLength * COMPLETE_RATIO);
+ return text.length >= minLength && text.length <= maxLength;
+ };Also applies to: 75-75
🤖 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 `@jobdri/src/components/apply/InputSection.tsx` around lines 37 - 44,
isComplete currently uses a fixed ">= 1" threshold which is too lenient; change
the logic to use a percentage-based required length per question (based on each
question's maxLength) instead of a fixed 1 char. Introduce or use a
COMPLETION_PERCENT constant (e.g. 0.6) and compute requiredLength =
Math.ceil((q.maxLength ?? 1000) * COMPLETION_PERCENT), then update
isComplete/checkAllComplete so they compare currentTexts[i].length >=
requiredLength (or change isComplete signature to accept maxLength and compute
the threshold there) and ensure questions.every uses that threshold calculation
for each question.
🔗 관련 이슈
📝 개요
⌨️ 작업 상세 내용
1. 자소서 입력 화면 (
/apply/virtual/[id]/write)InputSection컴포넌트 신규 구현sessionStorage에서 읽어 동적으로 표시ChipQnumber에CompleteBadge활성화min-h-screen flex flex-col래퍼로 변경하여 Footer를 페이지 내부에 위치2. 문항 선택 → 자소서 입력 연동 (
/apply/virtual/[id]/questions)SelectQuestion컴포넌트에onQuestionsChange콜백 추가sessionStorage("selectedQuestions")에 저장 후 write 페이지로 이동3. 공고 입력 흐름 리팩토링 (
/apply/virtual/[id]/(jd)/)mock-application라우트 삭제 →apply/virtual/[id]/(jd)/하위로 이동JdInputPageClient→page.tsx로 분리selectedMethod상태를page.tsx로 lift upuseImperativeHandle로handleCtaClick노출id는 prop 대신useParams()직접 사용JdReviewPageClient→page.tsx로 분리JdReviewPageClient는 콘텐츠(Header + JdReviewMain)만 담당page.tsx에서useParams(),useSearchParams()로 id/mode 처리4. 헤더 현재 스텝 표시 개선
bg-whitebg-fill-quaternary-defaultbg-fill-disabled5. 모의 지원 메인 페이지 연결
MockApplicationHomePageClient를/apply메인 페이지에 적용/apply-type→/apply/virtual수정6. 폴더 구조 정리
apply-type페이지를apply/하위로 이동mock-application라우트 전체 삭제변경 파일 목록
apply/page.tsxapply/virtual/[id]/(jd)/jd-input/page.tsxapply/virtual/[id]/(jd)/jd-input/JdInputPageClient.tsxapply/virtual/[id]/(jd)/jd-review/page.tsxapply/virtual/[id]/(jd)/jd-review/JdReviewPageClient.tsxapply/virtual/[id]/questions/page.tsxapply/virtual/[id]/write/page.tsxapply/virtual/[id]/result/page.tsxcomponents/apply/InputSection.tsxcomponents/apply/SelectQuestion.tsxcomponents/common/chips/ChipQnumber.tsxcomponents/common/header/Header.tsxcomponents/mock-application/MockApplicationHomePageClient.tsx💡 코드 설명 및 참고사항
📸 스크린샷 (UI 변경 시)
jobdri-chrome-2026-05-21-15-29-13_eUGlNRHS.mp4
🔍 리뷰 요구사항 (Reviewers)
Summary by CodeRabbit
New Features
Bug Fixes
Style