[JDDEV-51] Feat: 실제 공고 지원 플로우 연결 (~자소서 입력) #58
Conversation
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThis PR implements the question selection and answer-input flow for the interview application by exporting the Question type, wiring sessionStorage persistence, rewriting InputSection to manage answers with normalized question models, refactoring page navigation, updating header/footer event handling, and refining modal and input component behaviors. ChangesQuestion Selection and Answer Input Interview Flow
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 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: 6
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/input/InputMultiLine1000.tsx (1)
35-42:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEnforce
maxLengthinstead of only flagging overflow.The component currently marks over-limit input as error but still accepts it. That breaks the max-length contract.
💡 Suggested patch
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const next = e.target.value; - // 여기서 return을 안 하니까 복붙해도 그대로 다 들어갑니다. + if (next.length > maxLength) return; setInternalValue(next); onChange?.(next); }; @@ <textarea @@ rows={rows} + maxLength={maxLength} />Also applies to: 55-70, 78-78
🤖 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/input/InputMultiLine1000.tsx` around lines 35 - 42, handleChange currently flags overflow via isError but still accepts input beyond maxLength; change handleChange to enforce maxLength by truncating e.target.value to maxLength before calling setInternalValue and onChange (i.e., compute const next = e.target.value.slice(0, maxLength) or similar), and apply the same enforcement wherever value updates occur in this component (other handlers around lines 55-70 and the place at 78) so the component never sets or emits a value longer than maxLength while preserving existing error logic using isError (value.length > maxLength || !!error).
🤖 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:
- Around line 17-19: The saveSelectedQuestions function uses a global
sessionStorage key "selectedQuestions" which causes collisions across virtual
IDs; update saveSelectedQuestions to derive a flow- and route-scoped storage key
(e.g., include the virtual [id] and a flow identifier like
"selectedQuestions:{id}:{flowName}") and store selectedQuestions under that
scoped key, then ensure the write page/InputSection receives the exact same
scoped key (via prop or route state) so reads/writes use the same namespaced key
(refer to saveSelectedQuestions, selectedQuestions, and the InputSection
prop/state passing in this file).
In `@jobdri/src/app/mock-application/questions/page.tsx`:
- Around line 12-14: The code currently writes selectedQuestions to
sessionStorage under the global key "selectedQuestions" in
saveSelectedQuestions(), which collides with other flows; change the flow to use
a namespaced key (or a prop-provided key) so each page uses its own storage
key—update saveSelectedQuestions() to set sessionStorage with a unique key like
"mockApplication:selectedQuestions" (or accept a key parameter) and update the
write page to pass that same key into InputSection so both pages read/write the
same namespaced key instead of the shared "selectedQuestions".
In `@jobdri/src/components/apply/InputSection.tsx`:
- Around line 47-50: Validate item.maxLength before using it: replace the
current numeric-only check with a guard that accepts only a finite positive
integer (e.g., Number.isFinite + Number.isInteger + > 0) and otherwise falls
back to DEFAULT_MAX_LENGTH; update the normalization around maxLength in
InputSection (where item.maxLength and DEFAULT_MAX_LENGTH are referenced) so
values like 0, negatives, NaN, Infinity, or non-integers are rejected and
DEFAULT_MAX_LENGTH is used.
In `@jobdri/src/components/common/header/Header.tsx`:
- Around line 69-76: The handler handleLeftActionClick currently always
navigates to MOCK_APPLICATION_HOME_PATH after invoking leftActionOnClick which
forces navigation even when a custom handler exists; change the logic so
navigation only happens when there is no custom leftActionOnClick provided
(i.e., call router.push(MOCK_APPLICATION_HOME_PATH) only if leftActionOnClick is
undefined), updating the handleLeftActionClick implementation to guard the
router.push call accordingly (references: handleLeftActionClick,
leftActionOnClick, router.push, MOCK_APPLICATION_HOME_PATH).
In `@jobdri/src/components/common/icons/IconBox.tsx`:
- Around line 93-96: The lookup into iconColorStyles in IconBox uses state as
"primary" | "secondary" but state can be "danger", causing undefined in the
non-TRASH path; update the className construction (the clsx(...) expression in
IconBox) to guard/normalize state before indexing iconColorStyles (e.g., map
"danger" to a valid bucket or fall back to "primary"/"secondary" based on
background) so iconColorStyles[state][background] is never accessed with
"danger" and iconClassName still applies.
In `@jobdri/src/components/common/modal/ModalInput.tsx`:
- Around line 189-194: In ModalInput, the alert-mode cancel Button is always
rendered with the optional prop onCancel, so ensure it always invokes a working
handler by wiring a fallback: change the Button onClick to call onCancel if
provided, otherwise call the component's close handler (e.g., handleClose /
onClose / onDismiss) or another internal close function; update the Button usage
in the ModalInput component so the cancel button never becomes a no-op by
falling back to that close handler.
---
Outside diff comments:
In `@jobdri/src/components/common/input/InputMultiLine1000.tsx`:
- Around line 35-42: handleChange currently flags overflow via isError but still
accepts input beyond maxLength; change handleChange to enforce maxLength by
truncating e.target.value to maxLength before calling setInternalValue and
onChange (i.e., compute const next = e.target.value.slice(0, maxLength) or
similar), and apply the same enforcement wherever value updates occur in this
component (other handlers around lines 55-70 and the place at 78) so the
component never sets or emits a value longer than maxLength while preserving
existing error logic using isError (value.length > maxLength || !!error).
🪄 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: a36fc09d-a04f-425d-b3d8-b710477992d7
📒 Files selected for processing (19)
jobdri/src/app/apply-type/ApplyTypePageClient.tsxjobdri/src/app/apply/virtual/[id]/questions/page.tsxjobdri/src/app/apply/virtual/[id]/write/page.tsxjobdri/src/app/mock-application/jd-input/JdInputPageClient.tsxjobdri/src/app/mock-application/jd-review/JdReviewPageClient.tsxjobdri/src/app/mock-application/questions/page.tsxjobdri/src/app/mock-application/write/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/icons/IconBox.tsxjobdri/src/components/common/input/InputMultiLine1000.tsxjobdri/src/components/common/input/InputSingleLine.tsxjobdri/src/components/common/modal/ModalAdd.tsxjobdri/src/components/common/modal/ModalInput.tsxjobdri/src/components/common/modal/ModalNotice.tsxjobdri/src/styles/color.css
| const saveSelectedQuestions = () => { | ||
| sessionStorage.setItem("selectedQuestions", JSON.stringify(selectedQuestions)); | ||
| }; |
There was a problem hiding this comment.
Use a flow- and route-scoped storage key to prevent data collision.
Line 18 writes to a global key ("selectedQuestions"). This collides with mock-application and other virtual IDs in the same browser session, so users can land on the write page with another flow’s questions.
Suggested direction
+const STORAGE_KEY = `selectedQuestions:virtual:${id}`;
+
const saveSelectedQuestions = () => {
- sessionStorage.setItem("selectedQuestions", JSON.stringify(selectedQuestions));
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(selectedQuestions));
};Then pass the same key to the write page InputSection via prop/route state so read/write stay aligned.
🤖 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 around lines 17 - 19,
The saveSelectedQuestions function uses a global sessionStorage key
"selectedQuestions" which causes collisions across virtual IDs; update
saveSelectedQuestions to derive a flow- and route-scoped storage key (e.g.,
include the virtual [id] and a flow identifier like
"selectedQuestions:{id}:{flowName}") and store selectedQuestions under that
scoped key, then ensure the write page/InputSection receives the exact same
scoped key (via prop or route state) so reads/writes use the same namespaced key
(refer to saveSelectedQuestions, selectedQuestions, and the InputSection
prop/state passing in this file).
| maxLength: | ||
| typeof item.maxLength === "number" | ||
| ? item.maxLength | ||
| : DEFAULT_MAX_LENGTH, |
There was a problem hiding this comment.
Validate maxLength as a finite positive integer before accepting persisted data.
Current normalization accepts any numeric maxLength. Values like 0, negative, or non-finite numbers can produce invalid input constraints and incorrect completion status.
Proposed patch
maxLength:
- typeof item.maxLength === "number"
- ? item.maxLength
+ Number.isFinite(item.maxLength) && item.maxLength > 0
+ ? Math.floor(item.maxLength)
: DEFAULT_MAX_LENGTH,📝 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.
| maxLength: | |
| typeof item.maxLength === "number" | |
| ? item.maxLength | |
| : DEFAULT_MAX_LENGTH, | |
| maxLength: | |
| Number.isFinite(item.maxLength) && item.maxLength > 0 | |
| ? Math.floor(item.maxLength) | |
| : DEFAULT_MAX_LENGTH, |
🤖 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 47 - 50, Validate
item.maxLength before using it: replace the current numeric-only check with a
guard that accepts only a finite positive integer (e.g., Number.isFinite +
Number.isInteger + > 0) and otherwise falls back to DEFAULT_MAX_LENGTH; update
the normalization around maxLength in InputSection (where item.maxLength and
DEFAULT_MAX_LENGTH are referenced) so values like 0, negatives, NaN, Infinity,
or non-integers are rejected and DEFAULT_MAX_LENGTH is used.
| const handleLeftActionClick: ButtonHTMLAttributes< | ||
| HTMLButtonElement | ||
| >["onClick"] = (event) => { | ||
| leftActionOnClick?.(event); | ||
|
|
||
| if (!event.defaultPrevented) { | ||
| router.push(MOCK_APPLICATION_HOME_PATH); | ||
| } |
There was a problem hiding this comment.
Avoid forcing home navigation when a custom left action exists.
On Line 74, router.push("/mock-application") still runs after leftAction.onClick unless the handler explicitly calls preventDefault(). That can break existing consumers expecting full control of navigation.
Suggested fix
const handleLeftActionClick: ButtonHTMLAttributes<
HTMLButtonElement
>["onClick"] = (event) => {
- leftActionOnClick?.(event);
-
- if (!event.defaultPrevented) {
+ if (leftActionOnClick) {
+ leftActionOnClick(event);
+ return;
+ }
+ if (!event.defaultPrevented) {
router.push(MOCK_APPLICATION_HOME_PATH);
}
};🤖 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 69 - 76, The
handler handleLeftActionClick currently always navigates to
MOCK_APPLICATION_HOME_PATH after invoking leftActionOnClick which forces
navigation even when a custom handler exists; change the logic so navigation
only happens when there is no custom leftActionOnClick provided (i.e., call
router.push(MOCK_APPLICATION_HOME_PATH) only if leftActionOnClick is undefined),
updating the handleLeftActionClick implementation to guard the router.push call
accordingly (references: handleLeftActionClick, leftActionOnClick, router.push,
MOCK_APPLICATION_HOME_PATH).
| className={clsx( | ||
| iconColorStyles[state as "primary" | "secondary"][background], | ||
| iconClassName, | ||
| )} |
There was a problem hiding this comment.
Guard "danger" state in non-TRASH path to prevent runtime crashes.
state allows "danger", but these lookups are indexed as "primary" | "secondary". In non-TRASH cases, "danger" can resolve to undefined and break rendering.
💡 Suggested patch
export default function IconBox({
@@
}: IconBoxProps) {
+ const paletteState = state === "danger" ? "secondary" : state;
+
if (type === "TRASH") {
@@
className={clsx(
"flex shrink-0 items-center justify-center gap-2.5 rounded-icon-default",
sizeStyles[size],
- bgStyles[state as "primary" | "secondary"][background],
+ bgStyles[paletteState][background],
className,
)}
>
<Icon
type={type}
className={clsx(
- iconColorStyles[state as "primary" | "secondary"][background],
+ iconColorStyles[paletteState][background],
iconClassName,
)}
/>📝 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.
| className={clsx( | |
| iconColorStyles[state as "primary" | "secondary"][background], | |
| iconClassName, | |
| )} | |
| export default function IconBox({ | |
| state, | |
| type, | |
| size, | |
| background, | |
| icon: Icon, | |
| iconClassName, | |
| className, | |
| }: IconBoxProps) { | |
| const paletteState = state === "danger" ? "secondary" : state; | |
| if (type === "TRASH") { | |
| return ( | |
| <div | |
| className={clsx( | |
| "flex shrink-0 items-center justify-center gap-2.5 rounded-icon-default", | |
| sizeStyles[size], | |
| bgStyles[paletteState][background], | |
| className, | |
| )} | |
| > | |
| <Icon | |
| type={type} | |
| className={clsx( | |
| iconColorStyles[paletteState][background], | |
| iconClassName, | |
| )} | |
| /> | |
| </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 `@jobdri/src/components/common/icons/IconBox.tsx` around lines 93 - 96, The
lookup into iconColorStyles in IconBox uses state as "primary" | "secondary" but
state can be "danger", causing undefined in the non-TRASH path; update the
className construction (the clsx(...) expression in IconBox) to guard/normalize
state before indexing iconColorStyles (e.g., map "danger" to a valid bucket or
fall back to "primary"/"secondary" based on background) so
iconColorStyles[state][background] is never accessed with "danger" and
iconClassName still applies.
| <Button | ||
| label="취소하기" | ||
| size="large" | ||
| styleType="quaternary" | ||
| onClick={onCancel} | ||
| className="h-[46px] flex-1 !text-text-system-fail" |
There was a problem hiding this comment.
Ensure alert-mode cancel always has a working handler.
onCancel is optional, but the cancel button is always rendered in this branch. When onCancel is omitted, the button is a no-op.
💡 Suggested patch
<Button
label="취소하기"
size="large"
styleType="quaternary"
- onClick={onCancel}
+ onClick={onCancel ?? onClose}
className="h-[46px] flex-1 !text-text-system-fail"
/>📝 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.
| <Button | |
| label="취소하기" | |
| size="large" | |
| styleType="quaternary" | |
| onClick={onCancel} | |
| className="h-[46px] flex-1 !text-text-system-fail" | |
| <Button | |
| label="취소하기" | |
| size="large" | |
| styleType="quaternary" | |
| onClick={onCancel ?? onClose} | |
| className="h-[46px] flex-1 !text-text-system-fail" | |
| /> |
🤖 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/modal/ModalInput.tsx` around lines 189 - 194, In
ModalInput, the alert-mode cancel Button is always rendered with the optional
prop onCancel, so ensure it always invokes a working handler by wiring a
fallback: change the Button onClick to call onCancel if provided, otherwise call
the component's close handler (e.g., handleClose / onClose / onDismiss) or
another internal close function; update the Button usage in the ModalInput
component so the cancel button never becomes a no-op by falling back to that
close handler.
🔗 관련 이슈
📝 개요
⌨️ 작업 상세 내용
Summary by CodeRabbit
New Features
Bug Fixes
UI/UX Improvements