Skip to content

[JDDESIGN-3] Feat: 모의지원 기본화면 HIFI UI 구현 및 API 연동 #72

Merged
minnngo merged 10 commits into
developfrom
feature/JDDESIGN-3-main-ui-design
May 28, 2026
Merged

[JDDESIGN-3] Feat: 모의지원 기본화면 HIFI UI 구현 및 API 연동 #72
minnngo merged 10 commits into
developfrom
feature/JDDESIGN-3-main-ui-design

Conversation

@minnngo
Copy link
Copy Markdown
Collaborator

@minnngo minnngo commented May 28, 2026

🔗 관련 이슈

  • JDDESIGN-3

📝 개요

  • 모의지원 기본 화면 및 주요 플로우의 하이파이 UI를 반영했습니다.
  • 모의지원 홈, 결과 카드, 빈 상태, 로딩 화면, 텍스트 붙여넣기 플로우를 구현/정리했습니다.
  • 모의지원 삭제 API, 재도전하기, 결과 확인 이동 로직을 연결했습니다.
  • 머지 충돌을 해결하고 홈 화면 로직을 통합했습니다.

⌨️ 작업 상세 내용

  • 모의지원 기본 화면 하이파이 UI 구현 및 컴포넌트 분리
  • 기본 화면 빈 케이스 및 결과 카드 컴포넌트 추가
  • 재도전하기 플로우 수정 및 useReApply 연동
  • fullscreen modal 컴포넌트 추가
  • 모의지원 삭제 API 연결
  • 질문 생성/자소서 분석 로딩 화면 하이파이 구현
  • 텍스트 붙여넣기 플로우 및 텍스트 입력 모달/로딩 플로우 구현
  • 결과 확인 진입 시 jobPostingId 누락으로 404가 발생하던 경로 보정
  • 머지 충돌 해결 및 홈 화면의 sequence 조회/재지원/삭제/임시저장 모달 흐름 통합

💡 코드 설명 및 참고사항

결과 화면은 jobPostingId 기준으로 분석 API를 호출하므로, write/result 이동 경로에서 jobPostingId가 유지되도록 보정했습니다.
텍스트 붙여넣기 관련 기존 링크 입력 플로우 코드는 삭제하지 않고, 새 텍스트 입력 플로우만 분기되도록 유지했습니다.

📸 스크린샷 (UI 변경 시)

2026-05-29.002413.mp4
2026-05-29.002640.mp4
2026-05-29.002527.mp4

🔍 리뷰 요구사항 (Reviewers)

  • 모의지원 홈에서 임시저장/결과 카드/재도전하기 이동 경로가 의도대로 동작하는지 확인 부탁드립니다.
  • 모의지원 삭제 API 연결 후 지원 기록이 정상적으로 삭제되는지 확인 부탁드립니다.

Summary by CodeRabbit

  • New Features

    • Added text paste method for job posting intake
    • Enhanced question loading with animated graphics
    • Job posting deletion functionality
    • Improved application card displays with progress tracking and quick actions
    • Saved applications modal for managing submissions
  • Style

    • Updated button spacing and icon styling
    • Improved layout structure and visual hierarchy
  • Chores

    • Added animation library dependency

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

This PR introduces a text-paste input method for job postings, comprehensively refactors the mock application home UI with reusable component library and utilities, integrates Lottie animations for loading states, updates mock apply identity resolution throughout the resume flow, and refines grid layout and styling across the application.

Changes

Text Input Method & Mock Application Home Refactor

Layer / File(s) Summary
Text input method for job posting intake
jobdri/src/app/apply/virtual/[id]/(jd)/jd-input/JdInputPageClient.tsx, jobdri/src/app/apply/virtual/[id]/(jd)/jd-input/page.tsx, jobdri/src/components/common/buttons/ButtonCtaModal.tsx, jobdri/src/components/common/cards/Method1Card.tsx, jobdri/src/components/common/icons/Icon.tsx
Adds a new "text" method to the JD input modal with TextJobPostingInputModal for raw text entry, updates page component for client-side method selection, and refactors UI to present text-paste as the default intake option.
Mock application home component library
jobdri/src/components/mock-application/home/types.ts, jobdri/src/components/mock-application/home/applicationHomeUtils.ts, jobdri/src/components/mock-application/home/homeSteps.ts, jobdri/src/components/mock-application/home/index.ts, jobdri/src/components/mock-application/home/ApplicationCardShared.tsx, jobdri/src/components/mock-application/home/ApplicationKebabButton.tsx, jobdri/src/components/mock-application/home/ApplicationProgressSteps.tsx, jobdri/src/components/mock-application/home/EmptyApplicationState.tsx, jobdri/src/components/mock-application/home/MockApplicationHomeIntro.tsx, jobdri/src/components/mock-application/home/PausedApplicationCard.tsx, jobdri/src/components/mock-application/home/ResultApplicationCard.tsx, jobdri/src/components/mock-application/home/SavedApplicationsModal.tsx
Introduces shared ApplicationCardData type, exports utility functions for caching/routing/mapping, step constants, and reusable card/modal components for displaying and managing paused, result, and saved applications.
Mock application home page client refactoring
jobdri/src/components/mock-application/MockApplicationHomePageClient.tsx
Refactors to use the new home component library, introduces cache-sync timeout for offline support, rewrites delete flow with deleteJobPosting API integration, updates result/paused rendering with new card components, and wires retry/delete/resume callbacks.
Mock apply ID resolution in questions and write flows
jobdri/src/app/apply/virtual/[id]/questions/QuestionsPageClient.tsx, jobdri/src/app/apply/virtual/[id]/write/WritePageClient.tsx
Derives jobPostingId from mock apply records using getMockApplyResumeRecords, passes mockApplyId to analysis/status updates, and chains jobPostingId through resume navigation via query parameters.
Lottie animation system integration
jobdri/package.json, jobdri/src/components/mock-application/QuestionGenerationLoading.tsx, jobdri/src/assets/lottie/question-loading-sparkle.json
Adds lottie-react dependency, refactors QuestionGenerationLoading to use dynamically imported Lottie players for question book and sparkle animations, replacing static loaders with animated graphics.
Layout refinements and styling updates
jobdri/src/styles/grid.css, jobdri/src/styles/shadow.css, jobdri/src/components/common/lnb/Lnb.tsx, jobdri/src/components/common/buttons/TextButton.tsx
Introduces responsive grid layout with page-width tokens, replaces .main-content-frame with .content-frame-lnb flex container, updates .container-lnb with clamp-based responsive width and CSS container queries, updates hover shadow token, and adds className support to Lnb with clsx-based styling.
Supporting components and API functions
jobdri/components/common/LoadMotion.tsx, jobdri/src/lib/api/jobPostings.ts
Adds LoadMotion component for cycling active-dot loading indicators and introduces deleteJobPosting API function with parseApiResponseAllowNull helper for handling empty response bodies on successful deletion.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

  • JobDri-Developer/FrontEnd#62: Both PRs modify jobdri/src/app/apply/virtual/[id]/(jd)/jd-input/JdInputPageClient.tsx — main PR adds rawText ingestion path while retrieved PR adds URL normalization, so they overlap in the same ingestion logic.
  • JobDri-Developer/FrontEnd#71: Main PR refactors the mock-application home UI with retry/resume actions (MockApplicationHomePageClient, result card components) that overlap with the retrieved PR's re-apply flow and retry navigation.
  • JobDri-Developer/FrontEnd#69: Both PRs modify jobdri/src/components/common/lnb/Lnb.tsx — main PR adds className prop/clsx styling while retrieved PR changes the logo asset, so they affect the same component file.

Suggested Reviewers

  • yiyoonseo

🐰 Text and colors dance in harmony—loading dots spin with glee,
New cards and flows, a home redesigned, for every user to see!
Mock applications sorted, saved, resumed with care,
This PR brings the future with animations in the air! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main feature: implementing high-fidelity UI and API integration for the mock-application (모의지원) main screen, which aligns with the extensive UI changes and new components added throughout the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/JDDESIGN-3-main-ui-design

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

@minnngo minnngo changed the title Feature/jddesign 3 main UI design [JDDESIGN-3] Feat: 모의지원 메인 플로우 UI 구현 및 API 연동 May 28, 2026
@minnngo minnngo changed the title [JDDESIGN-3] Feat: 모의지원 메인 플로우 UI 구현 및 API 연동 [JDDESIGN-3] Feat: 모의지원 기본화면 HIFI UI 구현 및 API 연동 May 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
jobdri/src/components/mock-application/QuestionGenerationLoading.tsx (1)

45-52: ⚡ Quick win

Respect reduced-motion preference for looping Lottie animations.

Line 48/50 currently always animates. Please gate autoplay/loop with prefers-reduced-motion so motion-sensitive users get a static fallback.

♿ Proposed change
@@
 function LoadingGraphic({
   animationData,
   className,
+  shouldAnimate,
 }: {
   animationData: unknown;
   className: string;
+  shouldAnimate: boolean;
 }) {
   return (
     <LottiePlayer
       aria-hidden
       animationData={animationData}
-      autoplay
+      autoplay={shouldAnimate}
       className={className}
-      loop
+      loop={shouldAnimate}
       rendererSettings={{ preserveAspectRatio: "xMidYMid meet" }}
     />
   );
 }
@@
 export default function QuestionGenerationLoading({
   companyName,
   jobName,
   durationMs,
 }: QuestionGenerationLoadingProps) {
   const [activeStep, setActiveStep] = useState(0);
+  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
+
+  useEffect(() => {
+    const media = window.matchMedia("(prefers-reduced-motion: reduce)");
+    const onChange = () => setPrefersReducedMotion(media.matches);
+    onChange();
+    media.addEventListener("change", onChange);
+    return () => media.removeEventListener("change", onChange);
+  }, []);
@@
             <LoadingGraphic
               animationData={questionLoadingSparkle}
               className="h-5 w-5"
+              shouldAnimate={!prefersReducedMotion}
             />
@@
                 <LoadingGraphic
                   animationData={questionLoadingBook}
                   className="h-[223.27px] w-[280px] shrink-0"
+                  shouldAnimate={!prefersReducedMotion}
                 />

Also applies to: 74-77, 153-156

🤖 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/mock-application/QuestionGenerationLoading.tsx` around
lines 45 - 52, The LottiePlayer instances are always autoplaying/looping which
ignores users' prefers-reduced-motion setting; update each LottiePlayer (the
instances rendered in QuestionGenerationLoading) to detect
prefers-reduced-motion (e.g., via window.matchMedia('(prefers-reduced-motion:
reduce)') or a usePrefersReducedMotion hook) and only set autoplay and loop when
the preference is not reduced, otherwise render a static fallback (no
autoplay/loop) or disable animation; change the autoplay and loop props on the
LottiePlayer components accordingly (affecting the occurrences around the
existing LottiePlayer props).
🤖 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/QuestionsPageClient.tsx:
- Around line 25-36: The lookup for jobPostingId using
getMockApplyResumeRecords() is not guarded causing router.push to proceed
without a stable id; update the flow in QuestionsPageClient.tsx to check the
resolved jobPostingId after calling getMockApplyResumeRecords() (using the
mockApplyId), and if it is undefined fail fast or recover (e.g., surface an
error/toast, call an API to fetch/fallback, or abort) instead of executing
saveQuestions/updateMockApplyResumeStatus and calling router.push; only call
saveQuestions, updateMockApplyResumeStatus, and router.push when jobPostingId is
present (or when a confirmed fallback has been obtained).

In `@jobdri/src/app/apply/virtual/`[id]/write/WritePageClient.tsx:
- Around line 38-43: The code currently falls back to 0 for jobPostingId
(variable jobPostingId computed from jobPostingIdParam or
getMockApplyResumeRecords()), which can cause routing to
/apply/virtual/0/result; remove the `|| 0` fallback so jobPostingId is undefined
when missing and update the submit success flow that does the redirect to
`/apply/virtual/${jobPostingId}/result` to first validate jobPostingId (e.g., if
(!jobPostingId) handle error/log and abort navigation or surface a proper error)
so you never navigate to id 0; ensure both the jobPostingId computation and the
redirect logic that references jobPostingId are changed accordingly.

In `@jobdri/src/components/mock-application/home/applicationHomeUtils.ts`:
- Around line 153-156: The createRows function must guard against non-positive
or non-integer sizes to avoid invalid array lengths; update createRows to
validate the size parameter (e.g., if size is not a positive integer or size <=
0) and immediately throw a clear RangeError or TypeError (with a short message)
before computing Math.ceil/Array.from so callers get a fast, explicit failure;
ensure the check is placed at the top of createRows and references the existing
createRows function signature.
- Around line 55-60: The matching in findLocalResumeStatus currently treats
mockApplyId and jobPostingId as equivalent; change it to first attempt to find a
record where record.mockApplyId === item.mockApplyId and only if that returns
undefined, fall back to finding by record.jobPostingId === item.jobPostingId
(using getMockApplyResumeRecords and the same MockApplyHomeItem fields). Ensure
the comparison uses strict equality and return the record found by mockApplyId
when present so status routing and step navigation use mockApplyId as the
primary identity.

In `@jobdri/src/components/mock-application/home/ApplicationKebabButton.tsx`:
- Around line 28-38: The retry menu item is being shown when showRetry is true
even if no handler is provided; update the conditional that builds the actions
array in ApplicationKebabButton (the block that currently checks showRetry) to
only include the "재도전하기" entry when showRetry is true AND onRetryClick is a
function (or non-null), so the menu item is omitted if onRetryClick is missing;
preserve the existing behavior of closing via setOpen(false) and calling
onRetryClick() when present.

In `@jobdri/src/components/mock-application/MockApplicationHomePageClient.tsx`:
- Around line 217-229: The code currently calls
removeApplicationLocally(targetApplication) before awaiting deleteJobPosting,
which causes the UI/cache to drop the record even if the API delete fails;
change the flow so that deleteJobPosting(targetApplication.jobPostingId) is
awaited inside the try block first, then on success call
removeApplicationLocally(...) and setShowDeleteToast(true); on failure (catch)
do not remove from local state — instead setApplicationsErrorMessage(...) and
leave state intact (or re-add the application if you must keep the optimistic
removal), and still call setShowDeleteConfirm(false) and
setDeleteTargetApplication(null) as appropriate; update references to
setShowDeleteConfirm, setDeleteTargetApplication, removeApplicationLocally,
deleteJobPosting, setShowDeleteToast, and setApplicationsErrorMessage
accordingly.

In `@jobdri/src/styles/grid.css`:
- Around line 45-49: The .container-lnb rule forces a hard minimum by using
min-width: var(--width-lnb-min) alongside width: clamp(...), causing horizontal
overflow on narrow viewports; remove the min-width declaration (or override it
to min-width: 0 in the responsive container query) so the clamp can actually
shrink below --width-lnb-min. Update the CSS rule for .container-lnb (or the
`@container` content-frame-lnb override) to eliminate the min-width restriction so
the element can fit narrow screens.

---

Nitpick comments:
In `@jobdri/src/components/mock-application/QuestionGenerationLoading.tsx`:
- Around line 45-52: The LottiePlayer instances are always autoplaying/looping
which ignores users' prefers-reduced-motion setting; update each LottiePlayer
(the instances rendered in QuestionGenerationLoading) to detect
prefers-reduced-motion (e.g., via window.matchMedia('(prefers-reduced-motion:
reduce)') or a usePrefersReducedMotion hook) and only set autoplay and loop when
the preference is not reduced, otherwise render a static fallback (no
autoplay/loop) or disable animation; change the autoplay and loop props on the
LottiePlayer components accordingly (affecting the occurrences around the
existing LottiePlayer props).
🪄 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: c238a451-cb3b-4aa0-b7af-f459116e60e8

📥 Commits

Reviewing files that changed from the base of the PR and between 181dcbd and 371e1c3.

⛔ Files ignored due to path filters (6)
  • jobdri/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • jobdri/src/assets/ic_Text.svg is excluded by !**/*.svg
  • jobdri/src/assets/ic_text.svg is excluded by !**/*.svg
  • jobdri/src/assets/img_basic_Step1.svg is excluded by !**/*.svg
  • jobdri/src/assets/img_basic_Step2.svg is excluded by !**/*.svg
  • jobdri/src/assets/img_basic_Step3.svg is excluded by !**/*.svg
📒 Files selected for processing (30)
  • jobdri/components/common/LoadMotion.tsx
  • jobdri/package.json
  • jobdri/src/app/apply/virtual/[id]/(jd)/jd-input/JdInputPageClient.tsx
  • jobdri/src/app/apply/virtual/[id]/(jd)/jd-input/page.tsx
  • jobdri/src/app/apply/virtual/[id]/questions/QuestionsPageClient.tsx
  • jobdri/src/app/apply/virtual/[id]/write/WritePageClient.tsx
  • jobdri/src/assets/lottie/question-loading-book.json
  • jobdri/src/assets/lottie/question-loading-sparkle.json
  • jobdri/src/components/common/buttons/ButtonCtaModal.tsx
  • jobdri/src/components/common/buttons/TextButton.tsx
  • jobdri/src/components/common/cards/Method1Card.tsx
  • jobdri/src/components/common/icons/Icon.tsx
  • jobdri/src/components/common/lnb/Lnb.tsx
  • jobdri/src/components/mock-application/MockApplicationHomePageClient.tsx
  • jobdri/src/components/mock-application/QuestionGenerationLoading.tsx
  • jobdri/src/components/mock-application/home/ApplicationCardShared.tsx
  • jobdri/src/components/mock-application/home/ApplicationKebabButton.tsx
  • jobdri/src/components/mock-application/home/ApplicationProgressSteps.tsx
  • jobdri/src/components/mock-application/home/EmptyApplicationState.tsx
  • jobdri/src/components/mock-application/home/MockApplicationHomeIntro.tsx
  • jobdri/src/components/mock-application/home/PausedApplicationCard.tsx
  • jobdri/src/components/mock-application/home/ResultApplicationCard.tsx
  • jobdri/src/components/mock-application/home/SavedApplicationsModal.tsx
  • jobdri/src/components/mock-application/home/applicationHomeUtils.ts
  • jobdri/src/components/mock-application/home/homeSteps.ts
  • jobdri/src/components/mock-application/home/index.ts
  • jobdri/src/components/mock-application/home/types.ts
  • jobdri/src/lib/api/jobPostings.ts
  • jobdri/src/styles/grid.css
  • jobdri/src/styles/shadow.css

Comment on lines +25 to +36
const mockApplyId = Number(id);
const jobPostingId = getMockApplyResumeRecords().find(
(record) => record.mockApplyId === mockApplyId,
)?.jobPostingId;

await saveQuestions(Number(id), selectedQuestions);
updateMockApplyResumeStatus(Number(id), "ANSWER_WRITE");
router.push(`/apply/virtual/${id}/write`);
updateMockApplyResumeStatus(mockApplyId, "ANSWER_WRITE");
router.push(
`/apply/virtual/${id}/write${
jobPostingId ? `?jobPostingId=${jobPostingId}` : ""
}`,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard unresolved jobPostingId before navigation.

At Line 26-35, when the lookup misses, you still continue and push /write without jobPostingId. This leaves the next step without a stable identifier and can still lead to broken result routing later. Fail fast here (or recover via API) instead of silently continuing.

Suggested fix
   const handleConfirm = async () => {
     const mockApplyId = Number(id);
-    const jobPostingId = getMockApplyResumeRecords().find(
+    const jobPostingId = getMockApplyResumeRecords().find(
       (record) => record.mockApplyId === mockApplyId,
     )?.jobPostingId;
 
+    if (!jobPostingId) {
+      // TODO: show recoverable error modal (or refetch by mockApplyId) before routing
+      return;
+    }
+
     await saveQuestions(Number(id), selectedQuestions);
     updateMockApplyResumeStatus(mockApplyId, "ANSWER_WRITE");
-    router.push(
-      `/apply/virtual/${id}/write${
-        jobPostingId ? `?jobPostingId=${jobPostingId}` : ""
-      }`,
-    );
+    router.push(`/apply/virtual/${id}/write?jobPostingId=${jobPostingId}`);
   };
📝 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 mockApplyId = Number(id);
const jobPostingId = getMockApplyResumeRecords().find(
(record) => record.mockApplyId === mockApplyId,
)?.jobPostingId;
await saveQuestions(Number(id), selectedQuestions);
updateMockApplyResumeStatus(Number(id), "ANSWER_WRITE");
router.push(`/apply/virtual/${id}/write`);
updateMockApplyResumeStatus(mockApplyId, "ANSWER_WRITE");
router.push(
`/apply/virtual/${id}/write${
jobPostingId ? `?jobPostingId=${jobPostingId}` : ""
}`,
);
const mockApplyId = Number(id);
const jobPostingId = getMockApplyResumeRecords().find(
(record) => record.mockApplyId === mockApplyId,
)?.jobPostingId;
if (!jobPostingId) {
// TODO: show recoverable error modal (or refetch by mockApplyId) before routing
return;
}
await saveQuestions(Number(id), selectedQuestions);
updateMockApplyResumeStatus(mockApplyId, "ANSWER_WRITE");
router.push(`/apply/virtual/${id}/write?jobPostingId=${jobPostingId}`);
🤖 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/QuestionsPageClient.tsx around
lines 25 - 36, The lookup for jobPostingId using getMockApplyResumeRecords() is
not guarded causing router.push to proceed without a stable id; update the flow
in QuestionsPageClient.tsx to check the resolved jobPostingId after calling
getMockApplyResumeRecords() (using the mockApplyId), and if it is undefined fail
fast or recover (e.g., surface an error/toast, call an API to fetch/fallback, or
abort) instead of executing saveQuestions/updateMockApplyResumeStatus and
calling router.push; only call saveQuestions, updateMockApplyResumeStatus, and
router.push when jobPostingId is present (or when a confirmed fallback has been
obtained).

Comment on lines +38 to +43
const jobPostingId =
jobPostingIdParam ||
getMockApplyResumeRecords().find(
(record) => record.mockApplyId === mockApplyId,
)?.jobPostingId ||
0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not default jobPostingId to 0 in result routing flow.

At Line 38-43, the || 0 fallback can send users to /apply/virtual/0/result at Line 85 after a successful submit. That’s a high-impact failure path.

Suggested fix
-  const jobPostingId =
+  const jobPostingId =
     jobPostingIdParam ||
     getMockApplyResumeRecords().find(
       (record) => record.mockApplyId === mockApplyId,
-    )?.jobPostingId ||
-    0;
+    )?.jobPostingId;
...
-      router.push(
-        `/apply/virtual/${jobPostingId}/result?sequence=${savedSequence}`,
-      );
+      if (!jobPostingId) {
+        setShowAnalysisErrorModal(true);
+        shouldKeepLoading = false;
+        return;
+      }
+      router.push(`/apply/virtual/${jobPostingId}/result?sequence=${savedSequence}`);

Also applies to: 84-86

🤖 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]/write/WritePageClient.tsx around lines 38
- 43, The code currently falls back to 0 for jobPostingId (variable jobPostingId
computed from jobPostingIdParam or getMockApplyResumeRecords()), which can cause
routing to /apply/virtual/0/result; remove the `|| 0` fallback so jobPostingId
is undefined when missing and update the submit success flow that does the
redirect to `/apply/virtual/${jobPostingId}/result` to first validate
jobPostingId (e.g., if (!jobPostingId) handle error/log and abort navigation or
surface a proper error) so you never navigate to id 0; ensure both the
jobPostingId computation and the redirect logic that references jobPostingId are
changed accordingly.

Comment on lines +55 to +60
function findLocalResumeStatus(item: MockApplyHomeItem) {
const localRecord = getMockApplyResumeRecords().find(
(record) =>
record.mockApplyId === item.mockApplyId ||
record.jobPostingId === item.jobPostingId,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use mockApplyId as the primary identity when resolving local status.

Current matching can pick another record with the same jobPostingId, which can misclassify card status and route users to the wrong step.

🔧 Proposed fix
 function findLocalResumeStatus(item: MockApplyHomeItem) {
-  const localRecord = getMockApplyResumeRecords().find(
-    (record) =>
-      record.mockApplyId === item.mockApplyId ||
-      record.jobPostingId === item.jobPostingId,
-  );
+  const records = getMockApplyResumeRecords();
+  const localRecord =
+    records.find((record) => record.mockApplyId === item.mockApplyId) ??
+    records.find(
+      (record) =>
+        record.mockApplyId == null &&
+        record.jobPostingId === item.jobPostingId,
+    );

   return localRecord?.status;
 }
🤖 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/mock-application/home/applicationHomeUtils.ts` around
lines 55 - 60, The matching in findLocalResumeStatus currently treats
mockApplyId and jobPostingId as equivalent; change it to first attempt to find a
record where record.mockApplyId === item.mockApplyId and only if that returns
undefined, fall back to finding by record.jobPostingId === item.jobPostingId
(using getMockApplyResumeRecords and the same MockApplyHomeItem fields). Ensure
the comparison uses strict equality and return the record found by mockApplyId
when present so status routing and step navigation use mockApplyId as the
primary identity.

Comment on lines +153 to +156
export function createRows<T>(items: T[], size: number) {
return Array.from({ length: Math.ceil(items.length / size) }, (_, index) =>
items.slice(index * size, index * size + size),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard createRows against non-positive size.

size of 0 or negative values can crash with invalid array length. Add a fast validation.

🛡️ Proposed fix
 export function createRows<T>(items: T[], size: number) {
+  if (size <= 0) {
+    return [];
+  }
   return Array.from({ length: Math.ceil(items.length / size) }, (_, index) =>
     items.slice(index * size, index * size + size),
   );
 }
🤖 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/mock-application/home/applicationHomeUtils.ts` around
lines 153 - 156, The createRows function must guard against non-positive or
non-integer sizes to avoid invalid array lengths; update createRows to validate
the size parameter (e.g., if size is not a positive integer or size <= 0) and
immediately throw a clear RangeError or TypeError (with a short message) before
computing Math.ceil/Array.from so callers get a fast, explicit failure; ensure
the check is placed at the top of createRows and references the existing
createRows function signature.

Comment on lines +28 to +38
...(showRetry
? [
{
label: "재도전하기",
onClick: () => {
setOpen(false);
onRetryClick?.();
},
},
]
: []),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hide “재도전하기” when no retry handler is provided.

With showRetry=true and missing onRetryClick, users get a visible action that does nothing.

🔧 Proposed fix
-  const menuItems = [
+  const shouldShowRetry = showRetry && typeof onRetryClick === "function";
+  const menuItems = [
     {
       label: "삭제하기",
       onClick: () => {
         setOpen(false);
         onDeleteClick();
       },
     },
-    ...(showRetry
+    ...(shouldShowRetry
       ? [
           {
             label: "재도전하기",
             onClick: () => {
               setOpen(false);
               onRetryClick?.();
             },
           },
         ]
       : []),
   ];
🤖 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/mock-application/home/ApplicationKebabButton.tsx`
around lines 28 - 38, The retry menu item is being shown when showRetry is true
even if no handler is provided; update the conditional that builds the actions
array in ApplicationKebabButton (the block that currently checks showRetry) to
only include the "재도전하기" entry when showRetry is true AND onRetryClick is a
function (or non-null), so the menu item is omitted if onRetryClick is missing;
preserve the existing behavior of closing via setOpen(false) and calling
onRetryClick() when present.

Comment on lines +217 to +229
setShowDeleteConfirm(false);
setDeleteTargetApplication(null);
removeApplicationLocally(targetApplication);

try {
const deleted = await deleteApplicationRecord();

if (deleted) {
closeDeleteConfirm();
setShowDeleteToast(true);
}
await deleteJobPosting(targetApplication.jobPostingId);
setShowDeleteToast(true);
} catch (error) {
setApplicationsErrorMessage(
error instanceof Error
? error.message
: "지원 기록을 삭제하지 못했습니다.",
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid removing/caching the record before delete API success.

Line 219 removes the application locally before deleteJobPosting resolves. If the API fails on Lines 224-229, the UI/session cache stays deleted even though the backend record still exists.

Suggested fix
   const targetApplication = deleteTargetApplication;
   setIsDeleting(true);
   setShowDeleteConfirm(false);
   setDeleteTargetApplication(null);
-  removeApplicationLocally(targetApplication);

   try {
     await deleteJobPosting(targetApplication.jobPostingId);
+    removeApplicationLocally(targetApplication);
+    setApplicationsErrorMessage("");
     setShowDeleteToast(true);
   } catch (error) {
     setApplicationsErrorMessage(
       error instanceof Error
         ? error.message
         : "지원 기록을 삭제하지 못했습니다.",
     );
   } finally {
     setIsDeleting(false);
   }
🤖 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/mock-application/MockApplicationHomePageClient.tsx`
around lines 217 - 229, The code currently calls
removeApplicationLocally(targetApplication) before awaiting deleteJobPosting,
which causes the UI/cache to drop the record even if the API delete fails;
change the flow so that deleteJobPosting(targetApplication.jobPostingId) is
awaited inside the try block first, then on success call
removeApplicationLocally(...) and setShowDeleteToast(true); on failure (catch)
do not remove from local state — instead setApplicationsErrorMessage(...) and
leave state intact (or re-add the application if you must keep the optimistic
removal), and still call setShowDeleteConfirm(false) and
setDeleteTargetApplication(null) as appropriate; update references to
setShowDeleteConfirm, setDeleteTargetApplication, removeApplicationLocally,
deleteJobPosting, setShowDeleteToast, and setApplicationsErrorMessage
accordingly.

Comment on lines +45 to 49
width: clamp(var(--width-lnb-min), 100%, var(--width-lnb));
min-width: var(--width-lnb-min);
max-width: var(--width-lnb);
margin: 0 auto;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check all .container-lnb declarations and possible breakpoint overrides:"
rg -n -C3 '\.container-lnb|`@container`|`@media`' --type=css

echo
echo "Expected: no small-breakpoint rule should keep min-width: 912px on .container-lnb."

Repository: JobDri-Developer/FrontEnd

Length of output: 1584


Remove the hard minimum sizing on .container-lnb to prevent horizontal overflow on narrow widths.

.container-lnb sets width: clamp(var(--width-lnb-min), 100%, var(--width-lnb)) and min-width: var(--width-lnb-min), so when the available width drops below --width-lnb-min it will still be forced to that minimum; the @container content-frame-lnb (max-width: 911px) override only changes margin-inline and doesn’t address the sizing.

💡 Suggested fix
 .container-lnb {
-  width: clamp(var(--width-lnb-min), 100%, var(--width-lnb));
-  min-width: var(--width-lnb-min);
+  width: min(100%, var(--width-lnb));
   max-width: var(--width-lnb);
   margin: 0 auto;
 }
📝 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
width: clamp(var(--width-lnb-min), 100%, var(--width-lnb));
min-width: var(--width-lnb-min);
max-width: var(--width-lnb);
margin: 0 auto;
}
.container-lnb {
width: min(100%, var(--width-lnb));
max-width: var(--width-lnb);
margin: 0 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 `@jobdri/src/styles/grid.css` around lines 45 - 49, The .container-lnb rule
forces a hard minimum by using min-width: var(--width-lnb-min) alongside width:
clamp(...), causing horizontal overflow on narrow viewports; remove the
min-width declaration (or override it to min-width: 0 in the responsive
container query) so the clamp can actually shrink below --width-lnb-min. Update
the CSS rule for .container-lnb (or the `@container` content-frame-lnb override)
to eliminate the min-width restriction so the element can fit narrow screens.

Copy link
Copy Markdown
Contributor

@yiyoonseo yiyoonseo left a comment

Choose a reason for hiding this comment

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

수고해써 ㅠㅠ
🥹🥹

@minnngo minnngo merged commit 7e1cbee into develop May 28, 2026
1 check passed
This was referenced May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants