Skip to content

Feat: 모의지원 전체 흐름 연결 및 재도전·삭제 기능 개선 [JDDEV-98] - #103

Merged
minnngo merged 10 commits into
developfrom
feature/JDDEV-98-home_retry—api-feat
Jul 27, 2026

Hidden character warning

The head ref may contain hidden characters: "feature/JDDEV-98-home_retry\u2014api-feat"
Merged

Feat: 모의지원 전체 흐름 연결 및 재도전·삭제 기능 개선 [JDDEV-98]#103
minnngo merged 10 commits into
developfrom
feature/JDDEV-98-home_retry—api-feat

Conversation

@minnngo

@minnngo minnngo commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

🔗 관련 이슈

  • JDDEV-98

📝 개요

  • 홈 화면의 모의지원 재도전·삭제 기능을 API와 연결하고, 자소서 작성부터 분석 결과까지의 데이터 흐름을 안정화했습니다.
  • LNB 최근 항목, 알림, 화면 스크롤 및 공통 모달 동작을 개선했습니다.

⌨️ 작업 상세 내용

  • 저장된 공고 기반 모의지원 생성 및 연결 공고 조회 API 연동
  • 홈·결과 화면의 재도전 및 모의지원 단건 삭제 API 연동
  • 재도전 중복 요청 방지 및 sequence 기반 지원 회차 표시
  • 자소서 후보 문항 초기화, 문항 추가·삭제 및 답변 자동 저장 개선
  • 서버 문항 ID 동기화와 저장 요청 순서 제어로 자동 저장 오류 방지
  • 답변 저장 후 비동기 분석 요청 및 로딩 화면 연결
  • LNB 최근 지원 목록 조회·이동·자동 갱신 기능 연결
  • 알림 SSE 예외 처리, 알림별 이동 경로 및 읽지 않은 알림 표시 개선
  • 확인 모달을 공통 ModalNotice 컴포넌트로 통일
  • 홈·크레딧 화면에서 LNB를 고정하고 우측 콘텐츠만 스크롤되도록 수정

💡 코드 설명 및 참고사항

문항 추가·삭제와 자동 저장 요청이 동시에 실행되지 않도록 저장 큐와 revision을 사용했습니다. 서버에서 반환한 문항 ID를 기준으로 목록을 다시 동기화하며, 입력 중인 로컬 답변은 유지합니다.

모의지원 생성·재도전·삭제 후에는 변경 이벤트를 발생시켜 홈 목록과 LNB 최근 항목이 새로고침 없이 갱신되도록 했습니다.

결과 화면에서 재도전한 경우에만 재도전 안내 토스트가 표시되며, 홈 화면에서는 별도 안내 없이 바로 자소서 작성 화면으로 이동합니다.

🔍 리뷰 요구사항 (Reviewers)

  • 공고 확인부터 자소서 작성, 채점 요청, 분석 결과 조회까지 전체 모의지원 흐름이 정상적으로 동작하는지 확인 부탁드립니다.
  • 홈·결과 화면에서 재도전했을 때 새로운 지원 회차가 생성되고, 자소서 작성 화면으로 정상 이동하는지 확인 부탁드립니다.

Summary by CodeRabbit

  • New Features

    • Added deletion for mock applications from the home dashboard.
    • Improved mock-application question selection, saving, retry, and submission reliability.
    • Added navigation to linked job postings from mock-application results.
    • Enhanced sidebar recent applications and notification updates.
    • Added search activation and credit navigation in the collapsed sidebar.
    • Added centered modal layouts and clearer confirmation, alert, and retry actions.
  • Bug Fixes

    • Improved notification handling and analysis failure recovery.
    • Prevented duplicate retry requests and conflicting saves.
    • Redirected production traffic to the canonical website address.
    • Improved page scrolling and viewport behavior across credit and application screens.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates mock-apply question persistence, retry and deletion flows, sidebar notifications, modal behavior, viewport layouts, and production host canonicalization.

Changes

Mock apply workflow

Layer / File(s) Summary
API contracts and persistence
jobdri/src/lib/api/questions.ts, jobdri/src/lib/api/mockApplies.ts
Question mapping, queued persistence responses, mock-apply payloads, deletion events, linked job-posting lookup, resume cleanup, and retry de-duplication are updated.
Question writing flow
jobdri/src/app/mockApply/[mockApplyId]/page.tsx
Question loading, normalization, autosave, structural synchronization, deletion, addition, submission, and job-posting linkage are revised.
Retry and results
jobdri/src/app/page.tsx, jobdri/src/app/mockApply/[mockApplyId]/result/*, jobdri/src/hooks/useReApply.ts, jobdri/src/components/mockApply/home/*
Retry redirects and guards, mock-apply deletion, result navigation, and retry-disabled card states are updated.

Sidebar and notifications

Layer / File(s) Summary
Sidebar wiring and recent items
jobdri/src/components/common/lnb/*, jobdri/src/components/common/searchbar/SearchBar.tsx
Folded navigation callbacks, exports, search activation, recent mock-apply synchronization, deletion handling, and recent-item navigation are wired.
Notification routing and SSE
jobdri/src/components/common/lnb/LnbNotification.tsx, jobdri/src/lib/api/notification.ts
Unread state, portal positioning, notification click routing, analysis handling, and SSE response validation are revised.

Modal and layout UI

Layer / File(s) Summary
ModalNotice contract
jobdri/src/components/common/modal/ModalNotice.tsx
Centered layouts and typed action styles are added.
Modal migrations and viewport containers
jobdri/src/app/mockApply/**, jobdri/src/components/common/MockApplyTemplate.tsx, jobdri/src/components/mockApply/ResumeAnalysisLoading.tsx, jobdri/src/app/credit/page.tsx
Confirmation and error dialogs use updated ModalNotice types, while credit and writing layouts adopt viewport-height and overflow changes.

Canonical-domain routing

Layer / File(s) Summary
Production host canonicalization
jobdri/src/proxy.ts
Non-canonical production hosts redirect to the canonical origin before authentication and public-route processing.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: 🎨 UI, ⭐ Feature, 🩵 민교

🚥 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.
Title check ✅ Passed 모의지원 전체 흐름 연결과 재도전·삭제 기능 개선이라는 핵심 변경을 잘 요약해 제목과 변경 내용이 일치합니다.
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.
✨ 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/JDDEV-98-home_retry—api-feat

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
jobdri/src/app/mockApply/[mockApplyId]/result/page.tsx (1)

103-121: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Retry failure produces no user feedback — the catch is unreachable.

reApply (in useReApply) swallows errors internally and returns null rather than throwing, so await reApply(...) never rejects and this catch never runs. On a failed retry the modal stays open and no error toast is shown. Gate on the return value instead.

🐛 Proposed fix
-    try {
-      await reApply(resolvedMockApplyId, {
-        getRedirectPath: (result) =>
-          `/mockApply/${result.mockApplyId}?retry=1&sequence=${result.sequence}`,
-      });
-    } catch {
-      setIsRetryModalOpen(false);
-      showTopToast("재도전을 시작하지 못했어요. 잠시 후 다시 시도해주세요.");
-    }
+    const result = await reApply(resolvedMockApplyId, {
+      getRedirectPath: (retryResult) =>
+        `/mockApply/${retryResult.mockApplyId}?retry=1&sequence=${retryResult.sequence}`,
+    });
+
+    if (!result) {
+      setIsRetryModalOpen(false);
+      showTopToast("재도전을 시작하지 못했어요. 잠시 후 다시 시도해주세요.");
+    }
🤖 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/mockApply/`[mockApplyId]/result/page.tsx around lines 103 -
121, Update handleRetryConfirm to inspect the value returned by reApply instead
of relying on the unreachable catch block. When reApply returns null or another
failure result, close the retry modal and show the existing failure toast;
preserve the redirect callback and successful retry behavior.
🤖 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/credit/page.tsx`:
- Around line 48-49: Update the parent content container’s overflow behavior
near the fixed-width main element to allow horizontal scrolling instead of
clipping. Change the `overflow-x-hidden` behavior on the surrounding `<div>` to
`overflow-x-auto`, while preserving the existing vertical scrolling and `main`
width constraints.

In `@jobdri/src/components/common/lnb/Lnb.tsx`:
- Around line 262-273: Guard newNotification.type in the
subscribeToNotificationStream callback before invoking startsWith, preserving
the loadRecentItems refresh when the type is present and starts with
"ANALYSIS_ASYNC_". Treat missing types as non-matching so the notification is
still mapped and stored without throwing.
- Around line 285-308: Update the handleMockApplyDeleted handler in the Lnb
useEffect to select the first remaining recent item when the deleted item was
selected, matching loadRecentItems’s mappedItems[0]?.id ?? "" fallback; retain
the current selection when a different item is deleted and clear it only when no
items remain.

---

Outside diff comments:
In `@jobdri/src/app/mockApply/`[mockApplyId]/result/page.tsx:
- Around line 103-121: Update handleRetryConfirm to inspect the value returned
by reApply instead of relying on the unreachable catch block. When reApply
returns null or another failure result, close the retry modal and show the
existing failure toast; preserve the redirect callback and successful retry
behavior.
🪄 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: 9f625ec5-14b4-4107-999b-7e98b826d8df

📥 Commits

Reviewing files that changed from the base of the PR and between 4971576 and c823e80.

📒 Files selected for processing (25)
  • jobdri/src/app/credit/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/result/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx
  • jobdri/src/app/mockApply/job/create/page.tsx
  • jobdri/src/app/mockApply/job/loading/page.tsx
  • jobdri/src/app/mockApply/job/review/page.tsx
  • jobdri/src/app/page.tsx
  • jobdri/src/components/common/MockApplyTemplate.tsx
  • jobdri/src/components/common/lnb/Lnb.tsx
  • jobdri/src/components/common/lnb/LnbFolded.tsx
  • jobdri/src/components/common/lnb/LnbNotification.tsx
  • jobdri/src/components/common/lnb/LnbShared.tsx
  • jobdri/src/components/common/lnb/index.ts
  • jobdri/src/components/common/modal/ModalNotice.tsx
  • jobdri/src/components/common/searchbar/SearchBar.tsx
  • jobdri/src/components/mockApply/ResumeAnalysisLoading.tsx
  • jobdri/src/components/mockApply/home/ResultApplicationCard.tsx
  • jobdri/src/components/mockApply/home/ResultApplicationList.tsx
  • jobdri/src/components/mockApply/home/applicationHomeUtils.ts
  • jobdri/src/hooks/useReApply.ts
  • jobdri/src/lib/api/mockApplies.ts
  • jobdri/src/lib/api/notification.ts
  • jobdri/src/lib/api/questions.ts

Comment on lines +48 to +49
<div className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto">
<main className="mx-auto w-full max-w-[1320px] min-w-[1060px] px-18 pt-12 pb-60">

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

Allow horizontal scrolling for the fixed-width content.

main remains min-w-[1060px], but its parent uses overflow-x-hidden. When the viewport is narrower than the LNB plus 1060px, the right side of the credit content is clipped and cannot be reached. Use overflow-x-auto or relax the minimum width at smaller breakpoints.

Proposed fix
-      <div className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto">
+      <div className="min-h-0 min-w-0 flex-1 overflow-x-auto overflow-y-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
<div className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto">
<main className="mx-auto w-full max-w-[1320px] min-w-[1060px] px-18 pt-12 pb-60">
<div className="min-h-0 min-w-0 flex-1 overflow-x-auto overflow-y-auto">
<main className="mx-auto w-full max-w-[1320px] min-w-[1060px] px-18 pt-12 pb-60">
🤖 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/credit/page.tsx` around lines 48 - 49, Update the parent
content container’s overflow behavior near the fixed-width main element to allow
horizontal scrolling instead of clipping. Change the `overflow-x-hidden`
behavior on the surrounding `<div>` to `overflow-x-auto`, while preserving the
existing vertical scrolling and `main` width constraints.

Comment thread jobdri/src/components/common/lnb/Lnb.tsx Outdated
Comment on lines +285 to 308
useEffect(() => {
const handleMockApplyDeleted = (event: Event) => {
const mockApplyId = (event as CustomEvent<number>).detail;

setRecentItems((current) =>
current.filter((item) => item.id !== String(mockApplyId)),
);
setSelectedRecentItemId((current) =>
current === String(mockApplyId) ? "" : current,
);
};

window.addEventListener(
MOCK_APPLY_DELETED_EVENT,
handleMockApplyDeleted,
);

return () => {
window.removeEventListener(
MOCK_APPLY_DELETED_EVENT,
handleMockApplyDeleted,
);
};
}, []);

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

Reselect fallback item after deleting the selected recent item.

Unlike loadRecentItems (which falls back to mappedItems[0]?.id ?? "" when the current selection is invalid), this handler just clears selectedRecentItemId to "" when the deleted item was selected, leaving nothing highlighted until the next full refresh.

♻️ Proposed fix
       setRecentItems((current) =>
         current.filter((item) => item.id !== String(mockApplyId)),
       );
       setSelectedRecentItemId((current) =>
-        current === String(mockApplyId) ? "" : current,
+        current === String(mockApplyId)
+          ? (recentItems.find((item) => item.id !== String(mockApplyId))?.id ?? "")
+          : current,
       );
🤖 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/lnb/Lnb.tsx` around lines 285 - 308, Update the
handleMockApplyDeleted handler in the Lnb useEffect to select the first
remaining recent item when the deleted item was selected, matching
loadRecentItems’s mappedItems[0]?.id ?? "" fallback; retain the current
selection when a different item is deleted and clear it only when no items
remain.

mingo added 4 commits July 26, 2026 12:48
…ome_retry—api-feat

# Conflicts:
#	jobdri/src/app/credit/page.tsx
#	jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx
#	jobdri/src/app/mockApply/[mockApplyId]/page.tsx
#	jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx
#	jobdri/src/app/page.tsx
#	jobdri/src/components/common/lnb/Lnb.tsx
#	jobdri/src/components/common/lnb/LnbNotification.tsx
#	jobdri/src/components/mockApply/home/applicationHomeUtils.ts
#	jobdri/src/lib/api/mockApplies.ts
#	jobdri/src/lib/api/notification.ts
…ome_retry—api-feat

# Conflicts:
#	jobdri/src/app/credit/page.tsx

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
jobdri/src/constants/policy.ts (2)

1-3: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Replace all publication placeholders before release.

[YYYY년 MM월 DD일] and [확인 후 기재] remain in the published terms and privacy policy. Populate the actual effective/revision dates and overseas-recipient contact details consistently before exposing these constants to users.

Also applies to: 119-121, 125-126, 249-249, 257-257

🤖 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/constants/policy.ts` around lines 1 - 3, Replace every publication
placeholder in TERMS_TEXT and the corresponding privacy-policy constants with
the approved effective/revision dates and overseas-recipient contact details.
Ensure no “[YYYY년 MM월 DD일]” or “[확인 후 기재]” placeholders remain before these
constants are exposed to users.

81-83: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Align the retention and deletion commitments.

The terms say user-entered data is retained until account deletion, while the JD section promises deletion on request and the privacy table again states retention until account deletion. A user’s deletion request could therefore produce behavior that contradicts one of the published policies.

Also applies to: 95-97, 154-157

🤖 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/constants/policy.ts` around lines 81 - 83, Align the retention and
deletion language across the policy entries near the shown text, including the
JD section and privacy table, so user-entered data is handled consistently when
a user requests deletion. Update the relevant retention commitments to describe
deletion on request while preserving legally required retention periods and the
existing post-account-deletion behavior.
jobdri/src/components/mockApply/home/applicationHomeUtils.ts (1)

86-86: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Normalize applyType before casting it.

profileColor is optional, so the fallback to DEFAULT is acceptable as the API default; applyType, however, is required and currently passes any decoded MockApplyHomeItem.applyType through unchecked. If the API can return bad values, coalesce them to MOCK/ACTUAL, or adjust mockApplies.ts’ client-side response typing so applyType is not accepted as-is.

🤖 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/mockApply/home/applicationHomeUtils.ts` at line 86,
Normalize MockApplyHomeItem.applyType before casting it to the API enum,
allowing only the valid MOCK or ACTUAL values and falling back to MOCK for
invalid or missing values. Update the relevant mapping alongside profileColor,
or tighten the client response typing in mockApplies.ts so unchecked applyType
values cannot pass through.
jobdri/src/app/credit/page.tsx (2)

48-54: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve a retry path after confirmation failure.

Because the redirect runs in finally, any network or server failure immediately removes paymentKey, orderId, and amount. Clear the callback URL only after success, or provide a retry action that can safely reuse the transaction.

🤖 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/credit/page.tsx` around lines 48 - 54, Update the confirmation
flow around the try/catch/finally block so router.replace("/credit") executes
only after successful payment confirmation, not in finally. Preserve the failure
state and callback parameters after errors so the user can retry the same
transaction safely.

25-25: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Gate the confirmation request while preventing rerun.

isConfirming only toggles the overlay; it is checked only after processConfirm() has already started another request and does not handle the React Strict Mode Development-only effect cancellation. Track the in-flight confirmation with a ref/guard before confirmPurchase(), and ensure the backend confirmation for paymentKey/orderId is idempotent.

🤖 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/credit/page.tsx` at line 25, Update the confirmation flow
around processConfirm and confirmPurchase to guard the request with an in-flight
ref before starting it, preventing duplicate calls including React Strict Mode
development-only reruns. Keep the UI loading state synchronized with the guard,
and make backend confirmation for the paymentKey/orderId pair idempotent so
repeated attempts produce one effective confirmation.

Source: MCP tools

🧹 Nitpick comments (1)
jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx (1)

289-315: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the declared ModalNotice type union for consistency.

"alertModal" resolves to a variant, but ModalNoticeProps.type is typed as string, so this literal is not enforced by the component contract. Narrow it to a union that includes "alertModal" alongside the existing un-suffixed names to keep callers from relying on naming/typo-dependent fallback behavior.

🤖 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/mockApply/`[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx
around lines 289 - 315, Update the ModalNoticeProps type declaration to use a
literal union for type, including "alertModal" and the existing unsuffixed
variants, instead of string. Preserve the current usages in
ResumeAnalysisLoadingPageClient and ensure invalid variant names are rejected at
compile time.
🤖 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/page.tsx`:
- Around line 222-227: Update the currentStep === 1 branch in the targetDraft
switch within the home navigation flow to resume the existing mock apply using
targetDraft.mockApplyId instead of routing through the job-posting review/create
flow. Preserve the existing mockApplyId and navigate directly to the in-progress
mock-apply route without calling the create endpoint.

In `@jobdri/src/components/common/lnb/Lnb.tsx`:
- Around line 217-218: Update the toast scheduling logic around setToastState
and window.setTimeout to retain the active dismissal timer, clear it before
scheduling a replacement, and clear the timer during component cleanup. Ensure
newer notifications remain visible for their full three-second duration.
- Around line 221-242: The fetchAndUpdateNotifications flow must ignore
responses that are older than a newer fetch, preventing stale initial or
SSE-triggered results from replacing current notificationItems and
hasNotification state. Track fetch ordering or request identity when starting
each fetch, and before applying mappedItems or triggering its toast, ensure the
response is still the latest request.

In `@jobdri/src/proxy.ts`:
- Around line 13-15: Update the host normalization logic around the
forwardedHost/request host fallback to parse the value against the canonical
scheme, remove default ports such as :443 for HTTPS while preserving non-default
ports, and use the normalized result consistently for canonical-host comparison
and redirect/header generation.
- Around line 7-15: Update getRequestHost so host-based routing uses an
ingress-overwritten or otherwise immutable host source instead of prioritizing
the client-controlled x-forwarded-host header; preserve normalization and
fallback behavior for the trusted value. Add a production-path integration test
proving a spoofed x-forwarded-host cannot bypass the canonical redirect.

---

Outside diff comments:
In `@jobdri/src/app/credit/page.tsx`:
- Around line 48-54: Update the confirmation flow around the try/catch/finally
block so router.replace("/credit") executes only after successful payment
confirmation, not in finally. Preserve the failure state and callback parameters
after errors so the user can retry the same transaction safely.
- Line 25: Update the confirmation flow around processConfirm and
confirmPurchase to guard the request with an in-flight ref before starting it,
preventing duplicate calls including React Strict Mode development-only reruns.
Keep the UI loading state synchronized with the guard, and make backend
confirmation for the paymentKey/orderId pair idempotent so repeated attempts
produce one effective confirmation.

In `@jobdri/src/components/mockApply/home/applicationHomeUtils.ts`:
- Line 86: Normalize MockApplyHomeItem.applyType before casting it to the API
enum, allowing only the valid MOCK or ACTUAL values and falling back to MOCK for
invalid or missing values. Update the relevant mapping alongside profileColor,
or tighten the client response typing in mockApplies.ts so unchecked applyType
values cannot pass through.

In `@jobdri/src/constants/policy.ts`:
- Around line 1-3: Replace every publication placeholder in TERMS_TEXT and the
corresponding privacy-policy constants with the approved effective/revision
dates and overseas-recipient contact details. Ensure no “[YYYY년 MM월 DD일]” or
“[확인 후 기재]” placeholders remain before these constants are exposed to users.
- Around line 81-83: Align the retention and deletion language across the policy
entries near the shown text, including the JD section and privacy table, so
user-entered data is handled consistently when a user requests deletion. Update
the relevant retention commitments to describe deletion on request while
preserving legally required retention periods and the existing
post-account-deletion behavior.

---

Nitpick comments:
In
`@jobdri/src/app/mockApply/`[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx:
- Around line 289-315: Update the ModalNoticeProps type declaration to use a
literal union for type, including "alertModal" and the existing unsuffixed
variants, instead of string. Preserve the current usages in
ResumeAnalysisLoadingPageClient and ensure invalid variant names are rejected at
compile time.
🪄 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: f4e82f8b-3dba-462d-8c5f-a11703776c7e

📥 Commits

Reviewing files that changed from the base of the PR and between c823e80 and 2d7af00.

📒 Files selected for processing (14)
  • jobdri/src/app/credit/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx
  • jobdri/src/app/mockApply/job/[jobPostingId]/review/page.tsx
  • jobdri/src/app/mockApply/job/create/page.tsx
  • jobdri/src/app/mockApply/job/loading/page.tsx
  • jobdri/src/app/page.tsx
  • jobdri/src/components/common/lnb/Lnb.tsx
  • jobdri/src/components/common/lnb/LnbNotification.tsx
  • jobdri/src/components/mockApply/home/applicationHomeUtils.ts
  • jobdri/src/constants/policy.ts
  • jobdri/src/lib/api/mockApplies.ts
  • jobdri/src/lib/api/notification.ts
  • jobdri/src/proxy.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • jobdri/src/components/common/lnb/LnbNotification.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/page.tsx

Comment thread jobdri/src/app/page.tsx
Comment on lines 222 to 227
switch (targetDraft.currentStep) {
case 1:
case 2:
router.push(
`/mockApply/job/${targetDraft.jobPostingId}/review`,
);
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether the review page resumes an existing mock apply or always creates a new one.
rg -n -A5 -B5 'createMockApplyFromJobPosting|MockApplyFromJobPostingPayload' jobdri/src/app/mockApply/job/\[jobPostingId\]/review/page.tsx jobdri/src/lib/api/mockApplies.ts

Repository: JobDri-Developer/FrontEnd

Length of output: 3484


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'page\.tsx$|mockApplies\.ts$' jobdri/src | sed 's#^\./##' | head -100

echo "== jobdri/src/app/page.tsx relevant section =="
sed -n '180,250p' jobdri/src/app/page.tsx

echo "== review page outline =="
fd 'page\.tsx$' jobdri/src/app/mockApply/job | sed -n '1,20p'
REVIEW=$(fd 'page\.tsx$' 'jobdri/src/app/mockApply/job/[\[jobPostingId\]\]' jobdri/src/app/mockApply/job | sed -n '1p') || true
if [ -n "${REVIEW:-}" ]; then
  echo "FILE=$REVIEW"
  ast-grep outline "$REVIEW" --view compact || true
  echo "== review page relevant sections =="
  wc -l "$REVIEW"
  sed -n '1,280p' "$REVIEW"
fi

echo "== mockApplies.ts relevant sections =="
sed -n '1,200p' jobdri/src/lib/api/mockApplies.ts

echo "== search for resume-existing mocks/routes =="
rg -n "currentStep|mockApplyId|sequence|mock-from-job-posting|from-job-posting|retry|resume|review" jobdri/src/app jobdri/src/lib -g '*.ts' -g '*.tsx' | head -240

Repository: JobDri-Developer/FrontEnd

Length of output: 34137


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== draft creation/status state =="
sed -n '1,130p' jobdri/src/app/page.tsx

echo "== review page create/next section =="
sed -n '50,280p' 'jobdri/src/app/mockApply/job/[jobPostingId]/review/page.tsx'

echo "== precise call sites to createApplyFromJobPosting =="
rg -n -C3 "createApplyFromJobPosting|MockApplyFromJobPostingPayload|sequence" 'jobdri/src/app/mockApply/job/[jobPostingId]/review/page.tsx' jobdri/src/app/mockApply/job/create/page.tsx jobdri/src/app/mockApply/job/loading/page.tsx jobdri/src/lib/api/mockApplies.ts

echo "== review component mock apply ID usages =="
python3 - <<'PY'
from pathlib import Path
import re
fp = Path('jobdri/src/app/mockApply/job/[jobPostingId]/review/page.tsx')
s = fp.read_text()
for pat in [
    r"mockApplyId",
    r"createApplyFromJobPosting\\(",
    r"jobPostingId",
]:
    print(f"\nmatches for {pat}:")
    for i, line in enumerate(s.splitlines(), 1):
        if re.search(pat, line):
            print(f"{i}: {line}")
PY

Repository: JobDri-Developer/FrontEnd

Length of output: 18853


Resume existing mock applies from currentStep === 1.

Drafts routed from currentStep === 1 already have mockApplyId and status "QUESTION_SELECT", but the home click sends them to the job-posting review page, which creates a new mock apply and pushes /mockApply/${createdApply.mockApplyId}. Keep these on the existing mockApplyId, or preserve and pass the in-progress mockApplyId directly before calling the create endpoint.

🤖 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/page.tsx` around lines 222 - 227, Update the currentStep === 1
branch in the targetDraft switch within the home navigation flow to resume the
existing mock apply using targetDraft.mockApplyId instead of routing through the
job-posting review/create flow. Preserve the existing mockApplyId and navigate
directly to the in-progress mock-apply route without calling the create
endpoint.

Comment on lines 217 to +218
setToastState({ message: toastMessage, variant: toastVariant });
setTimeout(() => setToastState(null), 3000);
window.setTimeout(() => setToastState(null), 3000);

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

Clear the prior toast dismissal timer.

Two notifications within three seconds leave both timers active; the earlier timer clears the newer toast early. Store and clear the previous timeout before scheduling the next one, and clear it during cleanup.

🤖 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/lnb/Lnb.tsx` around lines 217 - 218, Update the
toast scheduling logic around setToastState and window.setTimeout to retain the
active dismissal timer, clear it before scheduling a replacement, and clear the
timer during component cleanup. Ensure newer notifications remain visible for
their full three-second duration.

Comment on lines +221 to +242
const fetchAndUpdateNotifications = async () => {
try {
const data = await fetchNotifications();
if (data.isSuccess && data.result) {
const mappedItems = data.result.map(mapApiToLnbItem);

setNotificationItems((prevItems) => {
if (prevItems.length > 0 && mappedItems.length > 0) {
const latestNewItem = mappedItems[0];
const prevLatestItem = prevItems[0];

if (
latestNewItem.id !== prevLatestItem.id &&
!latestNewItem.read
) {
triggerToastBasedOnNotification(latestNewItem);
}
}
return mappedItems;
});

setHasNotification(mappedItems.some((item) => !item.read));

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

Prevent stale notification responses from overwriting newer state.

The initial fetch and SSE-triggered fetches can resolve out of order. An older response can replace a newer notification list and unread state.

Proposed fix
+    let notificationRequestId = 0;
+    let disposed = false;
+
     const fetchAndUpdateNotifications = async () => {
+      const requestId = ++notificationRequestId;
       try {
         const data = await fetchNotifications();
+        if (disposed || requestId !== notificationRequestId) return;
+
         if (data.isSuccess && data.result) {
           // ...
         }
       } catch (error) {
         console.error("알림 목록 갱신 실패:", error);
       }
     };
@@
     return () => {
+      disposed = true;
       unsubscribe();
       hasSubscribedRef.current = false;
     };
📝 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 fetchAndUpdateNotifications = async () => {
try {
const data = await fetchNotifications();
if (data.isSuccess && data.result) {
const mappedItems = data.result.map(mapApiToLnbItem);
setNotificationItems((prevItems) => {
if (prevItems.length > 0 && mappedItems.length > 0) {
const latestNewItem = mappedItems[0];
const prevLatestItem = prevItems[0];
if (
latestNewItem.id !== prevLatestItem.id &&
!latestNewItem.read
) {
triggerToastBasedOnNotification(latestNewItem);
}
}
return mappedItems;
});
setHasNotification(mappedItems.some((item) => !item.read));
let notificationRequestId = 0;
let disposed = false;
const fetchAndUpdateNotifications = async () => {
const requestId = ++notificationRequestId;
try {
const data = await fetchNotifications();
if (disposed || requestId !== notificationRequestId) return;
if (data.isSuccess && data.result) {
const mappedItems = data.result.map(mapApiToLnbItem);
setNotificationItems((prevItems) => {
if (prevItems.length > 0 && mappedItems.length > 0) {
const latestNewItem = mappedItems[0];
const prevLatestItem = prevItems[0];
if (
latestNewItem.id !== prevLatestItem.id &&
!latestNewItem.read
) {
triggerToastBasedOnNotification(latestNewItem);
}
}
return mappedItems;
});
setHasNotification(mappedItems.some((item) => !item.read));
🤖 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/lnb/Lnb.tsx` around lines 221 - 242, The
fetchAndUpdateNotifications flow must ignore responses that are older than a
newer fetch, preventing stale initial or SSE-triggered results from replacing
current notificationItems and hasNotification state. Track fetch ordering or
request identity when starting each fetch, and before applying mappedItems or
triggering its toast, ensure the response is still the latest request.

Comment thread jobdri/src/proxy.ts
Comment on lines +7 to +15
function getRequestHost(request: NextRequest) {
const forwardedHost = request.headers
.get("x-forwarded-host")
?.split(",")[0]
?.trim();

return (forwardedHost || request.headers.get("host") || request.nextUrl.host)
.toLowerCase()
.replace(/\.$/, "");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate proxy.ts"
fd -a 'proxy\.ts$' . || true

echo
echo "Show target file around relevant code"
if [ -f jobdri/src/proxy.ts ]; then
  nl -ba jobdri/src/proxy.ts | sed -n '1,120p'
fi

echo
echo "Search for request host parsing/proxies/deployment config hints"
rg -n "getRequestHost|redirectToCanonicalDomain|x-forwarded-host|host|trailingSlash|vercel|middleware|proxy|NextRequest|Rewrite|Redirect" jobdri .vercel 2>/dev/null || true

echo
echo "Show package/deploy config files if present"
for f in jobdri/package.json package.json vercel.json ./.vercelrc ./.vercel rc; do
  if [ -e "$f" ]; then
    echo "--- $f"
    sed -n '1,220p' "$f"
  fi
done

Repository: JobDri-Developer/FrontEnd

Length of output: 298


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Show target file around relevant code"
if [ -f jobdri/src/proxy.ts ]; then
  cat -n jobdri/src/proxy.ts | sed -n '1,140p'
fi

echo
echo "Search for request host parsing/proxies/deployment config hints"
rg -n "getRequestHost|redirectToCanonicalDomain|x-forwarded-host|host|trailingSlash|vercel|middleware|proxy|NextRequest|Rewrite|Redirect" jobdri .vercel 2>/dev/null || true

echo
echo "List possible config files"
for f in jobdri/package.json package.json vercel.json ./.vercelrc ./.vercel rc; do
  if [ -e "$f" ]; then
    echo "--- $f"
    cat -n "$f" | sed -n '1,220p'
  fi
done

echo
echo "List files by top-level repo directories"
git ls-files | sed 's#/.*##' | sort -u | sed -n '1,120p'

Repository: JobDri-Developer/FrontEnd

Length of output: 6255


🌐 Web query:

Vercel Next.js middleware request.headers x-forwarded-host client-controlled forwarded headers trusted source

💡 Result:

On the Vercel platform, you should never implicitly trust the x-forwarded-host header (or any client-controllable forwarded headers) for security-critical operations, such as authentication, authorization, or tenant identification [1][2]. When a request reaches a Vercel deployment, the platform manages headers according to its internal routing and edge infrastructure [3][4]. While x-forwarded-host is often present, its values are subject to platform behavior, such as rewrites and internal redirects, which can lead to inconsistencies or unexpected overwrites [5][6][7]. Crucially, because these headers are passed in the HTTP request from the client or an upstream proxy, they are inherently forgeable unless explicitly handled through a verified secure channel [3][8]. Key security considerations include: 1. Do Not Trust Client Input: Any header that a client can manipulate should be treated as untrusted [1][2]. Even if you place Vercel behind your own reverse proxy, a malicious user could spoof these headers before they reach your application [3][8]. 2. Verified Proxy: If you must use a reverse proxy in front of Vercel, use Vercel's Verified Proxy feature [8]. This allows Vercel to trust specific headers (like a custom connecting IP header) only when they originate from your known, static egress IP ranges [3][8]. This mechanism must be configured via Vercel's enterprise or professional support channels [8]. 3. Middleware and Proxy.ts: In Next.js (including the proxy.ts pattern introduced in Next.js 16), code running at the network boundary (formerly middleware) is a useful gateway, but it is not a substitute for cryptographically verifying identity [1][2]. Always verify authentication tokens (like JWTs) directly within your server-side code (e.g., Server Components or API routes) rather than relying on headers passed from the middleware [1][2]. 4. Header Behavior: Vercel documentation confirms that while x-forwarded-host is often provided, it is identical to the host header in most standard contexts [4]. However, users have reported that internal Next.js rewrites can sometimes reset or overwrite this value, leading to issues where the original host is lost [5][6][9]. For robust security, implement defense-in-depth: assume that any header set by a proxy or middleware could have been bypassed if a matcher or route configuration has a gap, and always perform final verification against an immutable source of truth, such as a secure cookie or a cryptographically signed token [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Node probe for host resolution logic used by getRequestHost and canonical redirect"
node - <<'JS'
const PUBLIC_ROUTES = ["/login", "/oauth2", "/components"];
const PRODUCTION_CANONICAL_ORIGIN = "https://www.jobdri.com";

function getRequestHost(request) {
  const forwardedHost = request.headers
    .get("x-forwarded-host")
    ?.split(",")[0]
    ?.trim();
  return (forwardedHost || request.headers.get("host") || request.nextUrl.host)
    .toLowerCase()
    .replace(/\.$/, "");
}

const canon = new URL(PRODUCTION_CANONICAL_ORIGIN);

function redirectToCanonicalDomain(request) {
  if (process.env.VERCEL_ENV !== "production") {
    return null;
  }
  if (getRequestHost(request) === canon.host) {
    return null;
  }
  const resp = { status: 307, url: null, headers: {} };
  resp.url = new URL(Object.assign(canon, { pathname: request.nextUrl.pathname, search: request.nextUrl.search }));
  return resp;
}

function proxy(request) {
  const canonicalRedirect = redirectToCanonicalDomain(request);
  if (canonicalRedirect) {
    return canonicalRedirect.url.href;
  }

  const token = request.cookies.get("jobdri_accessToken")?.value;
  const { pathname } = request.nextUrl;
  const isPublic = PUBLIC_ROUTES.some((route) => pathname.startsWith(route));
  if (!token && !isPublic) {
    return "Redirect: /login?redirect=...";
  }
  return "NoRedirect";
}

const cases = [
  {
    name: "spoofed x-forwarded-host canonical, actual non-canonical host",
    get: (headers) => headers.get("host") || requestHeaders.host,
    headers: headers => Object.assign(headers, { host: "evil-jobdri.com", "x-forwarded-host": "www.jobdri.com" }),
  },
  {
    name: "spoofed comma-delimited x-forwarded-host canonical first",
    get: (headers) => requestHeaders.host,
    headers: headers => Object.assign(headers, { host: "evil-jobdri.com", "x-forwarded-host": "www.jobdri.com, bad.example" }),
  },
  {
    name: "canonical actual host and forwarded host",
    get: (requestHeaders) => requestHeaders.host,
    headers: headers => Object.assign(headers, { host: "www.jobdri.com", "x-forwarded-host": "www.jobdri.com" }),
  },
  {
    name: "canonical with dot trailing in host, removed",
    headers: headers => Object.assign(headers, { host: "www.jobdri.com.", "x-forwarded-host": "www.jobdri.com." }),
  },
];

for (const c of cases) {
  headers = new Headers({ user_agent: "test" });
  c.headers(headers);
  const req = new Request("https://invalid.invalid/page", { headers });
  req.cookies = { get: () => null };
  req.nextUrl = { pathname: "/page", search: "" };
  Object.defineProperty(req, "headers", { get() { return headers } });
  console.log(`${c.name}:`, proxy(req));
}
JS

Repository: JobDri-Developer/FrontEnd

Length of output: 575


Treat x-forwarded-host as untrusted for host-based routing.

getRequestHost can let a forged forwarded host make non-canonical traffic skip the canonical redirect, so don’t prioritize or derive security-critical host decisions from it. Use an ingress-overwritten or immutable host value, and add a production-path integration test covering spoofed forwarded-host values.

🤖 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/proxy.ts` around lines 7 - 15, Update getRequestHost so host-based
routing uses an ingress-overwritten or otherwise immutable host source instead
of prioritizing the client-controlled x-forwarded-host header; preserve
normalization and fallback behavior for the trusted value. Add a production-path
integration test proving a spoofed x-forwarded-host cannot bypass the canonical
redirect.

Comment thread jobdri/src/proxy.ts
Comment on lines +13 to +15
return (forwardedHost || request.headers.get("host") || request.nextUrl.host)
.toLowerCase()
.replace(/\.$/, "");

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files matching proxy.ts:"
fd -a 'proxy\.ts$' . || true

echo
echo "jobdri/src/proxy.ts outline and contents:"
if [ -f jobdri/src/proxy.ts ]; then
  wc -l jobdri/src/proxy.ts
  cat -n jobdri/src/proxy.ts
fi

echo
echo "Search related terms:"
rg -n 'canonicalUrl|forwardedHost|request\.headers\.get\("host"\)|request\.nextUrl\.host|HostWithPort|URL' jobdri -S || true

Repository: JobDri-Developer/FrontEnd

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node <= 'JS'
const scenarios = [
  ["jobdri.com:443", "https://www.jobdri.com", "www.jobdri.com"],
  ["www.jobdri.com:443", "https://www.jobdri.com", "www.jobdri.com"],
  ["www.jobdri.com:9443", "https://www.jobdri.com", "www.jobdri.com:9443"],
  ["jobdri.com:8123", "https://www.jobdri.com", "jobdri.com:8123"],
];
for (const [host, canonicalOrigin, expectedDirectHost] of scenarios) {
  const canonicalUrl = new URL(canonicalOrigin);
  const isMatch = host === canonicalUrl.host;
  console.log(JSON.stringify({ host, canonicalUrlHost: canonicalUrl.host, isMatch, redirectToCanonical: !isMatch }));
}
JS

echo
echo "Relevant proxy.ts lines:"
sed -n '1,40p' jobdri/src/proxy.ts

Repository: JobDri-Developer/FrontEnd

Length of output: 206


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const canonicalOrigin = 'https://www.jobdri.com';
const cases = [
  ['jobdri.com:443'],
  ['www.jobdri.com:443'],
  ['www.jobdri.com:9443'],
  ['jobdri.com:8123'],
];
const canonicalUrl = new URL(canonicalOrigin);
console.log('canonicalUrl.host=' + canonicalUrl.host);
for (const [host] of cases) {
  console.log(JSON.stringify({ host, canonicalUrlHost: canonicalUrl.host, redirect: host !== canonicalUrl.host }));
}
JS

echo
echo "Relevant proxy.ts lines:"
sed -n '1,40p' jobdri/src/proxy.ts

Repository: JobDri-Developer/FrontEnd

Length of output: 1657


Normalize default ports before comparing canonical hosts.

Lines 8-16 normalize hosts to lowercase/strip trailing dots, but they leave ports intact. A forwarded or Host header like www.jobdri.com:443 still redirects to https://www.jobdri.com..., and the canonical host header is generated as www.jobdri.com:443, causing a redirect loop. Compare against a canonical origin parsed with the expected canonical scheme and omit default ports while keeping non-default ports.

🤖 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/proxy.ts` around lines 13 - 15, Update the host normalization
logic around the forwardedHost/request host fallback to parse the value against
the canonical scheme, remove default ports such as :443 for HTTPS while
preserving non-default ports, and use the normalized result consistently for
canonical-host comparison and redirect/header generation.

@minnngo
minnngo merged commit 6ae2be4 into develop Jul 27, 2026
1 check passed
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.

1 participant