diff --git a/jobdri/src/app/page.tsx b/jobdri/src/app/page.tsx index 348526e..dca8400 100644 --- a/jobdri/src/app/page.tsx +++ b/jobdri/src/app/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/common/buttons"; import { BusinessFooter } from "@/components/common/footer"; @@ -25,136 +25,237 @@ import type { } from "@/components/mockApply/home/types"; import { useReApply } from "@/hooks/useReApply"; import { mapMockApplyToApplication } from "@/components/mockApply/home/applicationHomeUtils"; -import { Tooltip } from "@/components/common/tooltip"; +import { Toast, ToastVariant } from "@/components/common/toast"; + +// 🌟 ν•„μš”ν•œ API ν•¨μˆ˜λ“€ import +import { subscribeAnalysisTaskStream } from "@/lib/api/result"; +import { subscribeToNotificationStream } from "@/lib/api/notification"; export default function Home() { const router = useRouter(); const { reApply, isSaving: isRetrying } = useReApply(); const [drafts, setDrafts] = useState([]); const [results, setResults] = useState([]); + const [toast, setToast] = useState<{ + show: boolean; + message: string; + variant: ToastVariant; + }>({ + show: false, + message: "", + variant: "normal", + }); - useEffect(() => { - const loadMockApplies = async () => { - try { - 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 || []; - - const jobPostingById = new Map( - jobPostings.map((jobPosting) => [ - jobPosting.jobPostingId, - jobPosting, - ]), - ); + const showToast = (message: string, variant: ToastVariant) => { + setToast({ show: true, message, variant }); + setTimeout(() => { + setToast((prev) => ({ ...prev, show: false })); + }, 3000); + }; + + const loadMockApplies = useCallback(async () => { + try { + const [data, fetchedJobPostings] = await Promise.all([ + fetchMyMockApplies({ redirectOnUnauthorized: false, size: 100 }), + fetchMyJobPostings({ redirectOnUnauthorized: false }).catch(() => []), + ]); + + const jobPostings = Array.isArray(fetchedJobPostings) + ? fetchedJobPostings + : []; + const inProgressList = data?.inProgress || []; + const completedList = data?.completed?.content || []; + + const jobPostingById = new Map( + jobPostings.map((jobPosting) => [jobPosting.jobPostingId, jobPosting]), + ); + + // μž‘μ„± 쀑인 λͺ¨μ˜μ§€μ›(Drafts) λ§€ν•‘ + const mappedDrafts: DraftData[] = inProgressList.map((item) => { + const jobPosting = jobPostingById.get(item.jobPostingId); + + let currentStep = 1; + if (item.taskId) { + currentStep = 3; // 채점 쀑 + } else if (item.status === "ANSWER_WRITE") { + currentStep = 2; // μž‘μ„± 쀑 + } - // 🌟 μž‘μ„± 쀑인 λͺ¨μ˜μ§€μ›(Drafts) λ§€ν•‘ - const mappedDrafts: DraftData[] = inProgressList.map((item) => { - const jobPosting = jobPostingById.get(item.jobPostingId); + return { + id: String(item.mockApplyId), + jobPostingId: item.jobPostingId, + mockApplyId: item.mockApplyId, + companyName: + item.companyName || jobPosting?.companyName || "νšŒμ‚¬λͺ… λ―Έμž…λ ₯", + profileColor: jobPosting?.profileColor ?? "DEFAULT", + position: + item.jobTitle || + jobPosting?.jobTitle || + item.detailClassificationName || + jobPosting?.detailClassificationName || + "직무 λ―Έμ§€μ •", + currentStep, + taskId: item.taskId, + updatedAt: item.createdAt ? formatRelativeDate(item.createdAt) : "-", + createdAtTime: item.createdAt + ? new Date(item.createdAt).getTime() + : 0, + }; + }); - return { - id: String(item.mockApplyId), - jobPostingId: item.jobPostingId, - mockApplyId: item.mockApplyId, - companyName: - item.companyName || jobPosting?.companyName || "νšŒμ‚¬λͺ… λ―Έμž…λ ₯", + const linkedJobPostingIds = new Set( + [...inProgressList, ...completedList].map((item) => item.jobPostingId), + ); + + // 순수 μ±„μš© 곡고(Drafts) λ§€ν•‘ + const savedOnlyDrafts: DraftData[] = jobPostings + .filter( + (jobPosting) => !linkedJobPostingIds.has(jobPosting.jobPostingId), + ) + .map((jobPosting) => ({ + id: `job-posting-${jobPosting.jobPostingId}`, + jobPostingId: jobPosting.jobPostingId, + companyName: jobPosting.companyName || "νšŒμ‚¬λͺ… λ―Έμž…λ ₯", + profileColor: jobPosting.profileColor, + position: + jobPosting.jobTitle || + jobPosting.detailClassificationName || + "직무 λ―Έμ§€μ •", + currentStep: 1, + taskId: jobPosting.taskId, + updatedAt: jobPosting.createdAt + ? formatRelativeDate(jobPosting.createdAt) + : "-", + createdAtTime: jobPosting.createdAt + ? new Date(jobPosting.createdAt).getTime() + : 0, + })); + + const mappedResults = completedList.map((item) => { + const jobPosting = jobPostingById.get(item.jobPostingId); + return mapMockApplyToApplication( + { + ...item, profileColor: jobPosting?.profileColor ?? "DEFAULT", - position: - item.jobTitle || - jobPosting?.jobTitle || + jobTitle: item.jobTitle || jobPosting?.jobTitle || "", + detailClassificationName: item.detailClassificationName || jobPosting?.detailClassificationName || - "직무 λ―Έμ§€μ •", - currentStep: item.status === "ANSWER_WRITE" ? 2 : 1, - updatedAt: item.createdAt - ? formatRelativeDate(item.createdAt) - : "-", - createdAtTime: item.createdAt - ? new Date(item.createdAt).getTime() - : 0, - }; - }); - - const linkedJobPostingIds = new Set( - [...inProgressList, ...completedList].map( - (item) => item.jobPostingId, - ), + "", + }, + "completed", ); + }); + + const sortedDrafts = [...savedOnlyDrafts, ...mappedDrafts].sort( + (a, b) => { + const aIsAnalyzing = a.currentStep === 3; + const bIsAnalyzing = b.currentStep === 3; - // 🌟 순수 μ±„μš© 곡고(Drafts) λ§€ν•‘ - const savedOnlyDrafts: DraftData[] = jobPostings - .filter( - (jobPosting) => !linkedJobPostingIds.has(jobPosting.jobPostingId), - ) - .map((jobPosting) => ({ - id: `job-posting-${jobPosting.jobPostingId}`, - jobPostingId: jobPosting.jobPostingId, - companyName: jobPosting.companyName || "νšŒμ‚¬λͺ… λ―Έμž…λ ₯", - profileColor: jobPosting.profileColor, - position: - jobPosting.jobTitle || - jobPosting.detailClassificationName || - "직무 λ―Έμ§€μ •", - currentStep: 1, - updatedAt: jobPosting.createdAt - ? formatRelativeDate(jobPosting.createdAt) - : "-", - createdAtTime: jobPosting.createdAt - ? new Date(jobPosting.createdAt).getTime() - : 0, - })); - - const mappedResults = completedList.map((item) => { - const jobPosting = jobPostingById.get(item.jobPostingId); - - const cardData = mapMockApplyToApplication( - { - ...item, - profileColor: jobPosting?.profileColor ?? "DEFAULT", - jobTitle: item.jobTitle || jobPosting?.jobTitle || "", - detailClassificationName: - item.detailClassificationName || - jobPosting?.detailClassificationName || - "", - }, - "completed", + if (aIsAnalyzing && !bIsAnalyzing) return -1; + if (!aIsAnalyzing && bIsAnalyzing) return 1; + + const timeDifference = + (b.createdAtTime ?? 0) - (a.createdAtTime ?? 0); + if (timeDifference !== 0) return timeDifference; + return ( + (b.mockApplyId ?? b.jobPostingId) - + (a.mockApplyId ?? a.jobPostingId) ); + }, + ); + + setDrafts(sortedDrafts); + setResults(mappedResults); + } catch (error) { + console.error("데이터λ₯Ό λΆˆλŸ¬μ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.", error); + } + }, []); - return cardData; - }); + // μ»΄ν¬λ„ŒνŠΈ 마운트 μ‹œ 졜초 1회 λͺ©λ‘ 뢈러였기 + useEffect(() => { + const fetchInitialData = async () => { + await loadMockApplies(); + }; + void fetchInitialData(); + }, [loadMockApplies]); - const sortedDrafts = [...savedOnlyDrafts, ...mappedDrafts].sort( - (a, b) => { - const timeDifference = - (b.createdAtTime ?? 0) - (a.createdAtTime ?? 0); + // // 🌟 1. μžμ†Œμ„œ 뢄석(채점 쀑) κ°œλ³„ ꡬ독 + // useEffect(() => { + // const analyzingDrafts = drafts.filter( + // (draft) => draft.currentStep === 3 && draft.taskId && draft.mockApplyId, + // ); - if (timeDifference !== 0) { - return timeDifference; - } + // if (analyzingDrafts.length === 0) return; - return ( - (b.mockApplyId ?? b.jobPostingId) - - (a.mockApplyId ?? a.jobPostingId) - ); - }, - ); + // const abortControllers: AbortController[] = []; - setDrafts(sortedDrafts); - setResults(mappedResults); - } catch (error) { - console.error("데이터λ₯Ό λΆˆλŸ¬μ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.", error); - } - }; + // analyzingDrafts.forEach((draft) => { + // const controller = new AbortController(); + // abortControllers.push(controller); - loadMockApplies(); - }, []); + // void subscribeAnalysisTaskStream(draft.mockApplyId!, draft.taskId!, { + // signal: controller.signal, + // onEvent: (event) => { + // try { + // const data = JSON.parse(event.data); + // if (data.status === "SUCCEEDED") { + // showToast("μžμ†Œμ„œ 뢄석이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€!", "check"); + // loadMockApplies(); + // controller.abort(); + // } else if (data.status === "FAILED") { + // showToast("μžμ†Œμ„œ 뢄석에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.", "warning"); + // loadMockApplies(); + // controller.abort(); + // } + // } catch (e) {} + // }, + // }); + // }); + + // return () => { + // abortControllers.forEach((controller) => controller.abort()); + // }; + // }, [drafts, loadMockApplies]); + + useEffect(() => { + const analyzingJobPostings = drafts.filter( + (draft) => draft.currentStep === 1 && draft.taskId && !draft.mockApplyId, + ); + + if (analyzingJobPostings.length === 0) return; + + const abortControllers: AbortController[] = []; + + analyzingJobPostings.forEach((draft) => { + const controller = new AbortController(); + abortControllers.push(controller); + + void subscribeAnalysisTaskStream(draft.jobPostingId, draft.taskId!, { + signal: controller.signal, + onEvent: (event) => { + try { + const data = JSON.parse(event.data); + const status = data.result?.status || data.status; + + // console.log(`✨ [Task ID: ${draft.taskId}] μƒνƒœ 감지:`, status); + + if (status === "SUCCEEDED" || status === "FAILED") { + void loadMockApplies(); + controller.abort(); + } + } catch (e) {} + }, + }).catch((error) => { + if (error.name === "AbortError" || error.message?.includes("aborted")) + return; + }); + }); + + return () => { + abortControllers.forEach((controller) => controller.abort()); + }; + }, [drafts, loadMockApplies]); const deletePosting = async (jobPostingId: number) => { try { @@ -183,6 +284,7 @@ export default function Home() { console.error("λͺ¨μ˜ μ„œλ₯˜ 지원을 μ‚­μ œν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.", error); } }; + return (
@@ -292,6 +394,14 @@ export default function Home() { {/* ν•˜λ‹¨ ν‘Έν„° */}
+ {toast.show && ( + setToast((prev) => ({ ...prev, show: false }))} + /> + )} ); } diff --git a/jobdri/src/components/common/lnb/Lnb.tsx b/jobdri/src/components/common/lnb/Lnb.tsx index 1924c98..775309d 100644 --- a/jobdri/src/components/common/lnb/Lnb.tsx +++ b/jobdri/src/components/common/lnb/Lnb.tsx @@ -27,7 +27,6 @@ import { import { getResumePath } from "@/components/mockApply/home/applicationHomeUtils"; import { fetchNotifications, - subscribeToNotificationStream, type LnbNotificationItem, } from "@/lib/api/notification"; import LnbDefault from "./LnbDefault"; @@ -40,6 +39,9 @@ import { } from "./LnbShared"; import { useCreditStore } from "@/lib/store/useCreditStore"; +// μ•Œλ¦Ό 폴링 μ£ΌκΈ° (15초) +const NOTIFICATION_POLL_INTERVAL_MS = 15_000; + interface LnbProps { initialActiveItem?: LnbItemKey; email?: string; @@ -108,13 +110,16 @@ export default function Lnb({ >([]); const [hasNotification, setHasNotification] = useState(false); + // 🌟 ν† μŠ€νŠΈ μƒνƒœ λΆ€ν™œ const [toastState, setToastState] = useState<{ message: string; variant: ToastVariant; } | null>(null); - // 쀑볡 ꡬ독 λ°©μ§€μš© 락 Ref μΆ”κ°€ - const hasSubscribedRef = useRef(false); + // 🌟 쀑볡 μ•Œλ¦Ό μ›μ²œ μ°¨λ‹¨μš© Refs + const seenAnalysisIdsRef = useRef>(new Set()); + const seenToastIdsRef = useRef>(new Set()); + const isFirstNotificationLoadRef = useRef(true); const loadRecentItems = useCallback(async () => { try { @@ -167,7 +172,6 @@ export default function Lnb({ const initialLoadTimer = window.setTimeout(() => { void loadRecentItems(); }, 0); - return () => window.clearTimeout(initialLoadTimer); }, [loadRecentItems]); @@ -175,7 +179,6 @@ export default function Lnb({ const refreshRecentItems = () => { void loadRecentItems(); }; - window.addEventListener(MOCK_APPLY_CHANGED_EVENT, refreshRecentItems); window.addEventListener("focus", refreshRecentItems); @@ -185,112 +188,110 @@ export default function Lnb({ }; }, [loadRecentItems]); - useEffect(() => { - if (hasSubscribedRef.current) return; - hasSubscribedRef.current = true; - - const triggerToastBasedOnNotification = (item: LnbNotificationItem) => { - let toastMessage = item.title || "μƒˆλ‘œμš΄ μ•Œλ¦Όμ΄ λ„μ°©ν–ˆμŠ΅λ‹ˆλ‹€."; - let toastVariant: ToastVariant = "check"; - - switch (item.apiType) { - case "JOB_POSTING_ASYNC_SUCCEEDED": - toastMessage = "곡고 뢄석이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€!"; - break; - case "JOB_POSTING_ASYNC_FAILED": - toastMessage = "곡고 뢄석에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."; - toastVariant = "warning"; - break; - case "ANALYSIS_ASYNC_SUCCEEDED": - toastMessage = "μžμ†Œμ„œ 뢄석이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€!"; - break; - case "ANALYSIS_ASYNC_FAILED": - toastMessage = "μžμ†Œμ„œ 뢄석에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."; - toastVariant = "warning"; - break; - default: - if (item.type === "fail") { - toastVariant = "warning"; - } - break; - } - - setToastState({ message: toastMessage, variant: toastVariant }); - window.setTimeout(() => setToastState(null), 3000); - }; + // 🌟 ν† μŠ€νŠΈ λ„μš°λŠ” ν•¨μˆ˜ (μˆœμˆ˜ν•˜κ²Œ λ©”μ‹œμ§€λ§Œ μ„ΈνŒ…ν•¨) + const triggerToast = useCallback((item: LnbNotificationItem) => { + let toastMessage = item.title || "μƒˆλ‘œμš΄ μ•Œλ¦Όμ΄ λ„μ°©ν–ˆμŠ΅λ‹ˆλ‹€."; + let toastVariant: ToastVariant = "check"; + + const isJobPostingSuccess = + item.apiType === "JOB_POSTING_ANALYSIS_SUCCESS" || + item.title?.includes("곡고 뢄석 μ™„λ£Œ"); + const isJobPostingFailed = + item.apiType === "JOB_POSTING_ANALYSIS_FAILED" || + item.title?.includes("곡고 뢄석 μ‹€νŒ¨"); + + const isResumeAnalysisSuccess = + item.apiType === "MOCK_APPLY_ANALYSIS_SUCCESS" || + item.title?.includes("μžμ†Œμ„œ 뢄석 μ™„λ£Œ"); + const isResumeAnalysisFailed = + item.apiType === "MOCK_APPLY_ANALYSIS_FAILED" || + item.title?.includes("μžμ†Œμ„œ 뢄석 μ‹€νŒ¨"); + + if (isJobPostingSuccess) { + toastMessage = "곡고 뢄석이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€!"; + } else if (isJobPostingFailed) { + toastMessage = "곡고 뢄석에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."; + toastVariant = "warning"; + } else if (isResumeAnalysisSuccess) { + toastMessage = "μžμ†Œμ„œ 뢄석이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€!"; + } else if (isResumeAnalysisFailed) { + toastMessage = "μžμ†Œμ„œ 뢄석에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."; + toastVariant = "warning"; + } else if (item.type === "fail") { + toastVariant = "warning"; + } - 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; - }); + setToastState({ message: toastMessage, variant: toastVariant }); + window.setTimeout(() => setToastState(null), 3000); + }, []); - setHasNotification(mappedItems.some((item) => !item.read)); + const fetchAndUpdateNotifications = useCallback(async () => { + try { + const data = await fetchNotifications(); + if (!data.isSuccess || !data.result) return; + + const mappedItems = data.result.map(mapApiToLnbItem); + + if (isFirstNotificationLoadRef.current) { + mappedItems.forEach((item) => { + if (item.id) seenToastIdsRef.current.add(String(item.id)); + }); + isFirstNotificationLoadRef.current = false; + } else { + const newUnreadItems = mappedItems.filter( + (item) => + item.id && + !seenToastIdsRef.current.has(String(item.id)) && + !item.read, + ); + + if (newUnreadItems.length > 0) { + triggerToast(newUnreadItems[0]); } - } catch (error) { - console.error("μ•Œλ¦Ό λͺ©λ‘ κ°±μ‹  μ‹€νŒ¨:", error); + + mappedItems.forEach((item) => { + if (item.id) seenToastIdsRef.current.add(String(item.id)); + }); } - }; - void fetchAndUpdateNotifications(); + setNotificationItems(mappedItems); + setHasNotification(mappedItems.some((item) => !item.read)); - const unsubscribe = subscribeToNotificationStream( - (newNotification) => { - window.setTimeout(() => { - void fetchAndUpdateNotifications(); - }, 500); + // 리슀트 κ°±μ‹  둜직 (뢄석 μ™„λ£Œ μ‹œ) + const analysisIds = mappedItems + .filter((item) => item.apiType?.includes("ANALYSIS")) + .map((item) => String(item.id)); - if (newNotification.type.startsWith("ANALYSIS_ASYNC_")) { - void loadRecentItems(); - } - }, - (error) => { - console.error("μ‹€μ‹œκ°„ μ•Œλ¦Ό μ—°κ²° 문제 λ°œμƒ:", error); - }, - ); + const hasNewAnalysis = analysisIds.some( + (id) => !seenAnalysisIdsRef.current.has(id), + ); + seenAnalysisIdsRef.current = new Set(analysisIds); - return () => { - unsubscribe(); - hasSubscribedRef.current = false; - }; - }, [loadRecentItems]); + if (hasNewAnalysis) { + void loadRecentItems(); + } + } catch (error) { + console.error("μ•Œλ¦Ό λͺ©λ‘ κ°±μ‹  μ‹€νŒ¨:", error); + } + }, [loadRecentItems, triggerToast]); useEffect(() => { - const handleMockApplyDeleted = (event: Event) => { - const mockApplyId = (event as CustomEvent).detail; + void fetchAndUpdateNotifications(); - setRecentItems((current) => - current.filter((item) => item.id !== String(mockApplyId)), - ); - setSelectedRecentItemId((current) => - current === String(mockApplyId) ? "" : current, - ); - }; + const intervalId = window.setInterval(() => { + void fetchAndUpdateNotifications(); + }, NOTIFICATION_POLL_INTERVAL_MS); - window.addEventListener(MOCK_APPLY_DELETED_EVENT, handleMockApplyDeleted); + const handleFocus = () => { + void fetchAndUpdateNotifications(); + }; + window.addEventListener("focus", handleFocus); return () => { - window.removeEventListener( - MOCK_APPLY_DELETED_EVENT, - handleMockApplyDeleted, - ); + window.clearInterval(intervalId); + window.removeEventListener("focus", handleFocus); }; - }, []); + }, [fetchAndUpdateNotifications]); // Credit Fetch useEffect(() => { @@ -298,7 +299,7 @@ export default function Lnb({ fetchCreditBalance({ redirectOnUnauthorized: false }) .then(setCreditCount) .catch(() => {}); - }, []); + }, [disableCreditFetch, setCreditCount]); // Handlers const handleToggleFold = () => setIsFold((prev) => !prev); @@ -419,6 +420,7 @@ export default function Lnb({ document.body, )} + {/* 🌟 λ Œλ”λ§μ—μ„œ 빠져있던 ν† μŠ€νŠΈ μ»΄ν¬λ„ŒνŠΈ λΆ€ν™œ! */} {toastState && ( { @@ -76,10 +77,7 @@ export const APPLY_TYPE_STORAGE_KEY = "jobdri.applyType"; export const MOCK_APPLY_DELETED_EVENT = "jobdri:mock-apply-deleted"; export const MOCK_APPLY_CHANGED_EVENT = "jobdri:mock-apply-changed"; const MOCK_APPLY_RESUME_STORAGE_KEY = "jobdri.mockApplyResumeRecords"; -const retryMockApplyRequests = new Map< - number, - Promise ->(); +const retryMockApplyRequests = new Map>(); export function notifyMockApplyChanged() { if (typeof window !== "undefined") { @@ -147,11 +145,16 @@ export function createApplyFromJobPosting({ export async function fetchMyMockApplies({ signal, redirectOnUnauthorized = true, + size = 100, }: { signal?: AbortSignal; redirectOnUnauthorized?: boolean; + size?: number; } = {}) { - const response = await fetch(`${API_BASE_URL}/api/mock-applies/me`, { + const url = new URL(`${API_BASE_URL}/api/mock-applies/me`); + url.searchParams.set("size", String(size)); + + const response = await fetch(url.toString(), { headers: getAuthHeaders(), cache: "no-store", signal, diff --git a/jobdri/src/lib/api/notification.ts b/jobdri/src/lib/api/notification.ts index d481c3a..4882585 100644 --- a/jobdri/src/lib/api/notification.ts +++ b/jobdri/src/lib/api/notification.ts @@ -49,6 +49,7 @@ export interface LnbNotificationItem { savedToDatabase?: boolean; apiType?: string; readAt?: string; + createdAt?: string; } export interface NotificationResponse { @@ -130,12 +131,11 @@ export function subscribeToNotificationStream( } try { - const parsedData = JSON.parse(event.data) as ApiNotificationItem; - - if (!parsedData.title || parsedData.title.trim() === "") { - return; - } + const rawParsed = JSON.parse(event.data); + const parsedData = (rawParsed.result || + rawParsed) as ApiNotificationItem; + console.log("πŸ”” [SSE μ‹€μ‹œκ°„ μˆ˜μ‹  성곡]:", parsedData); onMessage(parsedData); } catch (error: unknown) { console.error("SSE 데이터 νŒŒμ‹± μ‹€νŒ¨:", error); @@ -159,13 +159,10 @@ export function subscribeToNotificationStream( } if (error instanceof NotificationStreamResponseError) { - console.warn( - `μ‹€μ‹œκ°„ μ•Œλ¦Ό 연결을 κ±΄λ„ˆλœλ‹ˆλ‹€: ${error.message}`, - { - status: error.status, - contentType: error.contentType, - }, - ); + console.warn(`μ‹€μ‹œκ°„ μ•Œλ¦Ό 연결을 κ±΄λ„ˆλœλ‹ˆλ‹€: ${error.message}`, { + status: error.status, + contentType: error.contentType, + }); return; }