Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion jobdri/src/app/mockApply/[mockApplyId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { useState, useEffect, use } from "react";
import { useState, useEffect, useRef, use } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { QuestionList } from "@/components/mockApply/Question/QuestionList";
import JDSidePanel from "@/components/mockApply/Question/SidePanel";
Expand All @@ -26,6 +26,7 @@ import { fetchMyJobPosting } from "@/lib/api/jobPostings";
import type { JDData } from "@/components/mockApply/Question/SidePanel";
import { saveJobPostingAnalysis } from "@/app/mockApply/job/jobPostingDraftStore";
import { ModalOverlay } from "@/components/common/modal/ModalOverlay";
import { useDebounce } from "@/hooks/useDebounce";

export default function MockApplyPage({
params,
Expand All @@ -52,6 +53,7 @@ export default function MockApplyPage({
: sequenceJobPostingId;
const [selectedId, setSelectedId] = useState<string | null>(null);
const [lastSavedTime, setLastSavedTime] = useState<string>("μ €μž₯ μ „");
const isInitialRender = useRef(true); // 처음 데이터λ₯Ό λΆˆλŸ¬μ™”μ„ λ•Œ μ €μž₯λ˜λŠ” 것 λ°©μ§€

const [toast, setToast] = useState<{
open: boolean;
Expand Down Expand Up @@ -335,6 +337,40 @@ export default function MockApplyPage({
!mappedQuestionForForm ||
questions.some((question) => !(question.answer || "").trim());

// βœ… 1. questions 배열을 1초 λ”œλ ˆμ΄μ‹œν‚€λŠ” λ””λ°”μš΄μŠ€ 생성
const debouncedQuestions = useDebounce(questions, 1000);

// βœ… 2. 1초 뒀에 κ°±μ‹ λœ debouncedQuestionsλ₯Ό κ°μ§€ν•΄μ„œ API 호좜!
useEffect(() => {
// λ‘œλ”© μ€‘μ΄κ±°λ‚˜ 데이터가 μ—†μœΌλ©΄ λ¬΄μ‹œ
if (isQuestionsLoading || debouncedQuestions.length === 0) return;

// 첫 마운트(데이터 패칭 직후) μ‹œμ μ—λŠ” μžλ™μ €μž₯ λ°©μ§€
if (isInitialRender.current) {
isInitialRender.current = false;
return;
}

const autoSave = async () => {
try {
// 방금 νŒŒμ•…ν•˜μ‹  λŒ€λ‘œ saveApply μ‚¬μš©!
await saveApply(
Number(mockApplyId),
getSubmitPayload(debouncedQuestions),
);

const now = new Date();
setLastSavedTime(
`${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`,
);
} catch (error) {
console.error("μžμ†Œμ„œ μžλ™ μ €μž₯ μ‹€νŒ¨:", error);
}
};

autoSave();
}, [debouncedQuestions, mockApplyId, isQuestionsLoading]);

return (
<MockApplyTemplate
mockApplyId={Number(mockApplyId)}
Expand Down
96 changes: 79 additions & 17 deletions jobdri/src/app/mockApply/job/[jobPostingId]/review/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
useMemo,
useState,
useEffect,
useRef,
type MouseEvent,
type ReactNode,
} from "react";
Expand Down Expand Up @@ -34,6 +35,7 @@ import {
createApplyFromJobPosting,
getSelectedApplyType,
} from "@/lib/api/mockApplies";
import { useDebounce } from "@/hooks/useDebounce";

function firstNonEmpty(...values: Array<string | null | undefined>) {
return values.find((value) => value?.trim())?.trim() ?? "";
Expand Down Expand Up @@ -142,6 +144,75 @@ export default function JobPostingReviewPage() {
const [isSaving, setIsSaving] = useState(false);
const [saveErrorMessage, setSaveErrorMessage] = useState("");

const [lastSavedTime, setLastSavedTime] = useState<string>("");

const isInitialRender = useRef(true);

const currentPayload: JobPostingSavePayload = useMemo(
() => ({
profileColor,
postingName: jobPostingName.trim(),
companyName: companyName.trim(),
companySize: companySize,
jobTitle: roleName.trim(),
detailClassificationId: detailClassificationId,
task: task.trim(),
requirement: requirements.trim(),
preferred: preferred.trim(),
}),
[
profileColor,
jobPostingName,
companyName,
companySize,
roleName,
detailClassificationId,
task,
requirements,
preferred,
],
);

const debouncedPayload = useDebounce(currentPayload, 1000);

useEffect(() => {
if (
isLoading ||
debouncedPayload.detailClassificationId <= 0 ||
!debouncedPayload.postingName ||
!debouncedPayload.companyName ||
!debouncedPayload.jobTitle
) {
return;
}

if (isInitialRender.current) {
isInitialRender.current = false;
return;
}

const autoSave = async () => {
try {
let savedResult;
if (jobPostingId) {
savedResult = await updateJobPosting(jobPostingId, debouncedPayload);
} else {
savedResult = await saveJobPosting(debouncedPayload);
setJobPostingId(savedResult.jobPostingId);
}

const now = new Date();
setLastSavedTime(
`${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`,
);
} catch (error) {
console.error("μ±„μš© 곡고 μžλ™ μ €μž₯ μ‹€νŒ¨:", error);
}
};

autoSave();
}, [debouncedPayload, jobPostingId, isLoading]);
Comment on lines +178 to +214

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸš€ Performance & Scalability | 🟠 Major | ⚑ Quick win

jobPostingId in the effect's dependency list causes a guaranteed duplicate save right after creating a new posting.

When a new posting is created inside autoSave (line 200), setJobPostingId fires and β€” because jobPostingId is a dependency of this same effect β€” re-triggers it with the unchanged debouncedPayload. Since isInitialRender.current is already false, this immediately issues a second, redundant updateJobPosting call with identical data right after the saveJobPosting POST. This happens on every first-time autosave, not just an edge case.

πŸ”§ Proposed fix β€” read `jobPostingId` via a ref instead of a dependency
+  const jobPostingIdRef = useRef(jobPostingId);
+  useEffect(() => {
+    jobPostingIdRef.current = jobPostingId;
+  }, [jobPostingId]);
+
   useEffect(() => {
     if (
       isLoading ||
       debouncedPayload.detailClassificationId <= 0 ||
       !debouncedPayload.postingName ||
       !debouncedPayload.companyName ||
       !debouncedPayload.jobTitle
     ) {
       return;
     }
 
     if (isInitialRender.current) {
       isInitialRender.current = false;
       return;
     }
 
     const autoSave = async () => {
       try {
         let savedResult;
-        if (jobPostingId) {
-          savedResult = await updateJobPosting(jobPostingId, debouncedPayload);
+        if (jobPostingIdRef.current) {
+          savedResult = await updateJobPosting(
+            jobPostingIdRef.current,
+            debouncedPayload,
+          );
         } else {
           savedResult = await saveJobPosting(debouncedPayload);
           setJobPostingId(savedResult.jobPostingId);
         }
         ...
       } catch (error) {
         console.error("μ±„μš© 곡고 μžλ™ μ €μž₯ μ‹€νŒ¨:", error);
       }
     };
 
     autoSave();
-  }, [debouncedPayload, jobPostingId, isLoading]);
+  }, [debouncedPayload, isLoading]);
πŸ“ Committable suggestion

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

Suggested change
useEffect(() => {
if (
isLoading ||
debouncedPayload.detailClassificationId <= 0 ||
!debouncedPayload.postingName ||
!debouncedPayload.companyName ||
!debouncedPayload.jobTitle
) {
return;
}
if (isInitialRender.current) {
isInitialRender.current = false;
return;
}
const autoSave = async () => {
try {
let savedResult;
if (jobPostingId) {
savedResult = await updateJobPosting(jobPostingId, debouncedPayload);
} else {
savedResult = await saveJobPosting(debouncedPayload);
setJobPostingId(savedResult.jobPostingId);
}
const now = new Date();
setLastSavedTime(
`${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`,
);
} catch (error) {
console.error("μ±„μš© 곡고 μžλ™ μ €μž₯ μ‹€νŒ¨:", error);
}
};
autoSave();
}, [debouncedPayload, jobPostingId, isLoading]);
const jobPostingIdRef = useRef(jobPostingId);
useEffect(() => {
jobPostingIdRef.current = jobPostingId;
}, [jobPostingId]);
useEffect(() => {
if (
isLoading ||
debouncedPayload.detailClassificationId <= 0 ||
!debouncedPayload.postingName ||
!debouncedPayload.companyName ||
!debouncedPayload.jobTitle
) {
return;
}
if (isInitialRender.current) {
isInitialRender.current = false;
return;
}
const autoSave = async () => {
try {
let savedResult;
if (jobPostingIdRef.current) {
savedResult = await updateJobPosting(
jobPostingIdRef.current,
debouncedPayload,
);
} else {
savedResult = await saveJobPosting(debouncedPayload);
setJobPostingId(savedResult.jobPostingId);
}
const now = new Date();
setLastSavedTime(
`${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`,
);
} catch (error) {
console.error("μ±„μš© 곡고 μžλ™ μ €μž₯ μ‹€νŒ¨:", error);
}
};
autoSave();
}, [debouncedPayload, isLoading]);
πŸ€– 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/job/`[jobPostingId]/review/page.tsx around lines 178
- 214, Remove jobPostingId from the autosave useEffect dependency trigger and
maintain its current value through a ref updated when the ID changes. Have
autoSave read the ref when deciding between saveJobPosting and updateJobPosting,
while preserving setJobPostingId after the initial save so creating a posting
cannot immediately trigger a duplicate update.


const { scrollAreaRef, scrollbarMetrics, updateScrollbarMetrics } =
useLnbScrollMetrics(true, "job-posting-review", { trackPadding: 28 });

Expand All @@ -158,11 +229,11 @@ export default function JobPostingReviewPage() {
[companyName, jobPostingName, roleName],
);

// 1. 초기 데이터 λ‘œλ”©
useEffect(() => {
const loadData = async () => {
try {
if (urlJobPostingId) {
// URL κ²½λ‘œμ— IDκ°€ 있으면 DBμ—μ„œ 패칭
const saved = await fetchMyJobPosting(Number(urlJobPostingId));
setProfileColor(saved.profileColor ?? "DEFAULT");
setJobPostingName(
Expand All @@ -183,7 +254,6 @@ export default function JobPostingReviewPage() {
setDetailClassificationId(saved.detailClassificationId ?? 0);
setJobPostingId(saved.jobPostingId ?? null);
} else {
// νŒŒλΌλ―Έν„°κ°€ μ—†μœΌλ©΄ μŠ€ν† μ–΄μ—μ„œ μž„μ‹œ 데이터 패칭
const result = getJobPostingAnalysis();
if (!result) {
router.replace("/mockApply/job/create");
Expand Down Expand Up @@ -246,6 +316,10 @@ export default function JobPostingReviewPage() {
router.replace("/");
} finally {
setIsLoading(false);
const now = new Date();
setLastSavedTime(
`${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`,
);
}
};

Expand All @@ -268,21 +342,9 @@ export default function JobPostingReviewPage() {
throw new Error("직무 λΆ„λ₯˜ 정보가 μ—†μ–΄ 곡고λ₯Ό μ €μž₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€.");
}

const payload: JobPostingSavePayload = {
profileColor,
postingName: jobPostingName.trim(),
companyName: companyName.trim(),
companySize: companySize,
jobTitle: roleName.trim(),
detailClassificationId: detailClassificationId,
task: task.trim(),
requirement: requirements.trim(),
preferred: preferred.trim(),
};

const savedJobPosting = jobPostingId
? await updateJobPosting(jobPostingId, payload)
: await saveJobPosting(payload);
? await updateJobPosting(jobPostingId, currentPayload)
: await saveJobPosting(currentPayload);
Comment on lines 345 to +347

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 | πŸ”΄ Critical | ⚑ Quick win

Missing setJobPostingId after creating a new posting in handleNext risks duplicate job-posting creation.

Unlike the autosave branch (line 201), this "create new" path never calls setJobPostingId(savedJobPosting.jobPostingId). If createApplyFromJobPosting (line 349) throws after the posting was successfully created, the catch block (lines 358-365) leaves jobPostingId as null and only shows saveErrorMessage. Any retry of "λ‹€μŒμœΌλ‘œ" β€” or a subsequent autosave firing β€” will call saveJobPosting again, creating a second orphaned job-posting record for the same edit session. saveJobPosting is a non-idempotent POST with no safeguard against this repeat call.

πŸ”§ Proposed fix
       const savedJobPosting = jobPostingId
         ? await updateJobPosting(jobPostingId, currentPayload)
         : await saveJobPosting(currentPayload);
 
+      if (!jobPostingId) {
+        setJobPostingId(savedJobPosting.jobPostingId);
+      }
+
       const createdApply = await createApplyFromJobPosting({
         jobPostingId: savedJobPosting.jobPostingId,
         applyType: getSelectedApplyType(),
       });
πŸ“ 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 savedJobPosting = jobPostingId
? await updateJobPosting(jobPostingId, payload)
: await saveJobPosting(payload);
? await updateJobPosting(jobPostingId, currentPayload)
: await saveJobPosting(currentPayload);
const savedJobPosting = jobPostingId
? await updateJobPosting(jobPostingId, currentPayload)
: await saveJobPosting(currentPayload);
if (!jobPostingId) {
setJobPostingId(savedJobPosting.jobPostingId);
}
const createdApply = await createApplyFromJobPosting({
jobPostingId: savedJobPosting.jobPostingId,
applyType: getSelectedApplyType(),
});
πŸ€– 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/job/`[jobPostingId]/review/page.tsx around lines 345
- 347, Update the new-posting branch in handleNext to call setJobPostingId with
savedJobPosting.jobPostingId immediately after saveJobPosting succeeds, before
createApplyFromJobPosting runs, while leaving the existing update path
unchanged.


const createdApply = await createApplyFromJobPosting({
jobPostingId: savedJobPosting.jobPostingId,
Expand Down Expand Up @@ -322,7 +384,7 @@ export default function JobPostingReviewPage() {
applicationLabel="첫 번째 지원"
currentStep={1}
steps={wizardSteps}
lastSavedAt="17:00"
lastSavedAt={lastSavedTime}
homeAction={{ label: "ν™ˆμœΌλ‘œ", onClick: handleHomeClick }}
className="min-w-[1100px] max-w-none shrink-0 self-stretch"
/>
Expand Down
72 changes: 45 additions & 27 deletions jobdri/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"use client";

import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/common/buttons";
Expand All @@ -16,12 +17,13 @@ import {
fetchMyJobPostings,
} from "@/lib/api/jobPostings";
import { saveJobPostingAnalysis } from "@/app/mockApply/job/jobPostingDraftStore";
import { formatDate } from "@/utils/date";
import { formatDate, formatRelativeDate } from "@/utils/date";
import {
DraftData,
ApplicationCardData,
} from "@/components/mockApply/home/types";
import { useReApply } from "@/hooks/useReApply";
import { mapMockApplyToApplication } from "@/components/mockApply/home/applicationHomeUtils";

export default function Home() {
const router = useRouter();
Expand All @@ -32,18 +34,27 @@ export default function Home() {
useEffect(() => {
const loadMockApplies = async () => {
try {
const [data, jobPostings] = await Promise.all([
const [data, fetchedJobPostings] = await Promise.all([
fetchMyMockApplies({ redirectOnUnauthorized: false }),
fetchMyJobPostings({ redirectOnUnauthorized: false }).catch(() => []),
]);

const jobPostings = Array.isArray(fetchedJobPostings)
? fetchedJobPostings
: [];

const inProgressList = data?.inProgress || [];
const completedList = data?.completed?.content || [];
Comment on lines +37 to +47

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)jobdri/src/(app/page\.tsx|lib/api/(jobPostings|mockApplies)\.ts)$' || true

echo "== page outline =="
ast-grep outline jobdri/src/app/page.tsx --view compact || true

echo "== relevant page lines =="
sed -n '1,90p' jobdri/src/app/page.tsx

echo "== jobPostings relevant lines =="
sed -n '1,220p' jobdri/src/lib/api/jobPostings.ts
sed -n '220,420p' jobdri/src/lib/api/jobPostings.ts

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

echo "== API pagination references =="
rg -n "PaginatedResponse|content|page|size|size=|page=" jobdri/src -S || true

Repository: JobDri-Developer/FrontEnd

Length of output: 49837


🌐 Web query:

Spring Framework RestControllerAdvice default exception handling pagination size parameter name content totalPages totalElements Page

πŸ’‘ Result:

In the Spring Framework, @RestControllerAdvice is a specialized @ControllerAdvice that automatically applies @ResponseBody to all @ExceptionHandler methods, making it ideal for global REST exception handling [1][2]. By default, it applies globally to all controllers unless specific scoping attributes (like basePackages or annotations) are used [1][2]. When serializing Spring Data Page objects (such as PageImpl) to JSON, Spring defaults to a specific structure containing pagination metadata [3][4][5]. The standard fields included in the page metadata section are [3][5]: - size: The number of items per page [3][6]. - totalElements: The total number of items available across all pages [3][6]. - totalPages: The total number of pages [3][6]. - number: The current page number (0-indexed) [3][6]. Note that the default JSON envelope structure for pagination is not formally specified and is subject to change [5]. If you need to customize these field names or the structure for your API, Spring provides several approaches: 1. DTO Wrapper: Create a custom DTO class to wrap the Page object and use @JsonProperty annotations to rename the fields as desired [7][8]. 2. Custom Serializer: Implement a custom Jackson JsonSerializer for the Page implementation and register it as a @JsonComponent [8]. 3. PagedModel: Use Spring Data's PagedModel, which is designed to provide stable JSON representations of a Page [9][5]. For global exception handling, extending ResponseEntityExceptionHandler is recommended, as it provides built-in handling for common Spring MVC exceptions using the RFC 9457 Problem Detail format [10][11]. If you need to return custom pagination information within an error response, you can extend ProblemDetail to include your specific pagination metadata fields [10].

Citations:


Load every paged result before rendering the dashboard.

Both Home data fetchers return page metadata (totalPages, content, size), but the API calls do not pass page/size and Home only maps the first returned content list. Users with more than one page of saved postings or completed applications will have stale/partial dashboard state. Collect totalPages and concatenate subsequent pages, or expose pagination arguments/API helpers that aggregate all pages before building lists.

πŸ“ Affects 3 files
  • jobdri/src/app/page.tsx#L37-L47 (this comment)
  • jobdri/src/lib/api/jobPostings.ts#L364-L381
  • jobdri/src/lib/api/mockApplies.ts#L132-L140
πŸ€– 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 37 - 47, Update page.tsx’s dashboard
loading flow to aggregate every page from fetchMyMockApplies and
fetchMyJobPostings before deriving inProgressList, completedList, and
jobPostings, using totalPages/content metadata and the existing page/size API
parameters. Update fetchMyJobPostings in jobPostings.ts and fetchMyMockApplies
in mockApplies.ts as needed to expose or support pagination, preserving current
unauthorized handling while concatenating all page contents.


const jobPostingById = new Map(
jobPostings.map((jobPosting) => [
jobPosting.jobPostingId,
jobPosting,
]),
);

const mappedDrafts = data.inProgress.map((item) => {
// 🌟 μž‘μ„± 쀑인 λͺ¨μ˜μ§€μ›(Drafts) λ§€ν•‘
const mappedDrafts = inProgressList.map((item) => {
const jobPosting = jobPostingById.get(item.jobPostingId);

return {
Expand All @@ -60,14 +71,19 @@ export default function Home() {
jobPosting?.detailClassificationName ||
"직무 λ―Έμ§€μ •",
currentStep: item.status === "ANSWER_WRITE" ? 2 : 1,
updatedAt: formatDate(item.createdAt),
updatedAt: item.createdAt
? formatRelativeDate(item.createdAt)
: "-",
};
});

const linkedJobPostingIds = new Set(
[...data.inProgress, ...data.completed].map(
[...inProgressList, ...completedList].map(
(item) => item.jobPostingId,
),
);

// 🌟 순수 μ±„μš© 곡고(Drafts) λ§€ν•‘
const savedOnlyDrafts = jobPostings
.filter(
(jobPosting) => !linkedJobPostingIds.has(jobPosting.jobPostingId),
Expand All @@ -82,31 +98,30 @@ export default function Home() {
jobPosting.detailClassificationName ||
"직무 λ―Έμ§€μ •",
currentStep: 1,
updatedAt: "-",
updatedAt: jobPosting.createdAt
? formatRelativeDate(jobPosting.createdAt)
: "-",
}));

const mappedResults = data.completed.map((item) => {
const mappedResults = completedList.map((item) => {
const jobPosting = jobPostingById.get(item.jobPostingId);

return {
id: item.mockApplyId,
jobPostingId: item.jobPostingId,
mockApplyId: item.mockApplyId,
company:
item.companyName || jobPosting?.companyName || "νšŒμ‚¬λͺ… λ―Έμž…λ ₯",
profileColor: jobPosting?.profileColor ?? "DEFAULT",
position:
item.jobTitle ||
jobPosting?.jobTitle ||
item.detailClassificationName ||
jobPosting?.detailClassificationName ||
"직무 λ―Έμ§€μ •",
createdAt: formatDate(item.createdAt),
score: item.score || 0,
version: item.version || 1,
status: "completed",
};
const cardData = mapMockApplyToApplication(
{
...item,
profileColor: jobPosting?.profileColor ?? "DEFAULT",
jobTitle: item.jobTitle || jobPosting?.jobTitle || "",
detailClassificationName:
item.detailClassificationName ||
jobPosting?.detailClassificationName ||
"",
},
"completed",
);

return cardData;
});

setDrafts([...savedOnlyDrafts, ...mappedDrafts]);
setResults(mappedResults);
} catch (error) {
Expand All @@ -130,6 +145,7 @@ export default function Home() {
console.error("μ±„μš© 곡고λ₯Ό μ‚­μ œν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.", error);
}
};

return (
<div className="flex min-h-screen w-full bg-[#F5F6F9] overflow-x-hidden ">
<Lnb className="shrink-0 z-50" />
Expand Down Expand Up @@ -179,7 +195,9 @@ export default function Home() {
generated: null,
saved,
});
router.push("/mockApply/job/review");
router.push(
`/mockApply/job/${targetDraft.jobPostingId}/review`,
);
})
.catch((error) => {
console.error("μ±„μš© 곡고λ₯Ό λΆˆλŸ¬μ˜€μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.", error);
Expand All @@ -191,7 +209,7 @@ export default function Home() {
case 1:
case 2:
router.push(
`/mockApply/${targetDraft.mockApplyId}?jobPostingId=${targetDraft.jobPostingId}`,
`/mockApply/job/${targetDraft.jobPostingId}/review`,
);
break;
case 3:
Expand Down
6 changes: 4 additions & 2 deletions jobdri/src/components/common/lnb/Lnb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
type LnbItemKey,
type LnbRecentItem,
type LnbNavItem,
defaultRecentItems,
} from "./LnbShared";

interface LnbProps {
Expand Down Expand Up @@ -89,7 +90,8 @@ export default function Lnb({
const [searchQuery, setSearchQuery] = useState("");
const [isRecentOpen, setIsRecentOpen] = useState(defaultRecentOpen);

const [recentItems, setRecentItems] = useState<LnbRecentItem[]>([]);
const [recentItems, setRecentItems] =
useState<LnbRecentItem[]>(defaultRecentItems);
const [selectedRecentItemId, setSelectedRecentItemId] = useState<string>("");

const [notificationItems, setNotificationItems] = useState<
Expand Down Expand Up @@ -173,7 +175,7 @@ export default function Lnb({

const unsubscribe = subscribeToNotificationStream(
(newNotification) => {
console.log("πŸ”₯ [SSE μˆ˜μ‹  μ™„λ£Œ] μ„œλ²„μ—μ„œ μ•Œλ¦Ό 옴!!!", newNotification);
// console.log("πŸ”₯ [SSE μˆ˜μ‹  μ™„λ£Œ] μ„œλ²„μ—μ„œ μ•Œλ¦Ό 옴!!!", newNotification);
setTimeout(() => {
fetchAndUpdateNotifications();
}, 500);
Expand Down
Loading