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
70 changes: 64 additions & 6 deletions jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,72 @@ import {
type JdReviewSection,
} from "@/components/mockApply/jd/jdReviewSections";

function subscribeToSessionStorage(onStoreChange: () => void) {
const handleStorage = () => onStoreChange();
export function subscribeToNotificationStream(
onMessage: (data: ApiNotificationItem) => void,
onError?: (error: unknown) => void,
) {
const headers = getAuthHeaders();
const ctrl = new AbortController();

if (!headers.Authorization) {
console.warn("둜그인 토큰이 μ—†μ–΄ μ‹€μ‹œκ°„ μ•Œλ¦Όμ„ μ—°κ²°ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.");
return () => ctrl.abort();
}

window.addEventListener("storage", handleStorage);
const tokenOnly = headers.Authorization.replace("Bearer ", "");

return () => {
window.removeEventListener("storage", handleStorage);
};
console.log(
"πŸ”Œ SSE μ—°κ²° μ‹œλ„ 쀑... URL:",
`${API_BASE_URL}/api/notifications/stream`,
);

fetchEventSource(
`${API_BASE_URL}/api/notifications/stream?accessToken=${tokenOnly}`,
{
method: "GET",
headers: {
...headers,
Accept: "text/event-stream",
},
signal: ctrl.signal,

onopen(response) {
if (response.ok) {
console.log("🟒 SSE μ—°κ²° 성곡!! μ„œλ²„μ™€ 슀트림 연결됨.");
} else {
console.error(
"πŸ”΄ SSE μ—°κ²° 응닡 이상:",
response.status,
response.statusText,
);
}
},

onmessage(event) {
console.log("πŸ“₯ SSE raw λ©”μ‹œμ§€ μˆ˜μ‹ :", event.data); // πŸ‘ˆ μ„œλ²„κ°€ 뭐라도 보내면 무쑰건 찍힘!

if (!event.data || !event.data.trim().startsWith("{")) {
return;
}

try {
const parsedData = JSON.parse(event.data) as ApiNotificationItem;
onMessage(parsedData);
} catch (e: unknown) {
console.error("SSE 데이터 νŒŒμ‹± μ‹€νŒ¨:", e);
}
},

onerror(err: unknown) {
console.error("❌ SSE 슀트림 μ—λŸ¬ λ°œμƒ:", err);
if (onError) onError(err);
},
},
).catch((err) => {
console.error("❌ SSE μ—°κ²° 래퍼 μ˜ˆμ™Έ λ°œμƒ:", err);
});

return () => ctrl.abort();
}
Comment on lines +31 to 97

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'page.tsx' -p 'jd-review' --exec ast-grep outline {} --items all
rg -nP '\bsubscribeToSessionStorage\b' --type=ts --type=tsx
rg -nP '\b(getAuthHeaders|API_BASE_URL|fetchEventSource|ApiNotificationItem)\b' -g '**/jd-review/page.tsx'

Repository: JobDri-Developer/FrontEnd

Length of output: 288


🏁 Script executed:

#!/bin/bash
set -u

echo "== candidate file =="
git ls-files | grep -F 'jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx' || true

echo "== file excerpt =="
cat -n 'jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx' | sed -n '1,120p'
echo "--- line 230-260 ---"
cat -n 'jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx' | sed -n '230,260p'

echo "== references =="
git ls-files | grep -E '\.(ts|tsx)$' | xargs grep -n 'subscribeToSessionStorage' 2>/dev/null || true
echo "== target identifiers in jd-review page =="
git ls-files | grep '/jd-review/page.tsx$' | xargs grep -n 'getAuthHeaders\|API_BASE_URL\|fetchEventSource\|ApiNotificationItem' 2>/dev/null || true

echo "== notification helper excerpt =="
cat -n jobdri/src/lib/api/notification.ts | sed -n '1,150p'

Repository: JobDri-Developer/FrontEnd

Length of output: 10443


Remove the accidentally pasted SSE helper from this page and restore subscribeToSessionStorage.

page.tsx now exports subscribeToNotificationStream, which references undefined getAuthHeaders, API_BASE_URL, fetchEventSource, and ApiNotificationItem; meanwhile useSyncExternalStore at line 247 still calls the missing subscribeToSessionStorage. Keep the notification SSE helper in jobdri/src/lib/api/notification.ts and restore the local session-storage subscribe helper here, unless this page intentionally needs that SSE 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]/(jd)/jd-review/page.tsx around lines
31 - 97, Remove the accidentally added subscribeToNotificationStream helper and
its SSE-related imports or references from this page. Restore a local
subscribeToSessionStorage function for the useSyncExternalStore call, preserving
sessionStorage change notifications and the existing snapshot behavior. Keep
notification SSE functionality in the existing notification API module instead.


function parseStoredSections(value: string | null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface ResumeAnalysisLoadingPageClientProps {
jobPostingId?: number;
applicationLabel?: string;
initialSequence?: number;
isError?: boolean;
}

function isFailedTaskStatus(status: string) {
Expand Down Expand Up @@ -51,6 +52,7 @@ export default function ResumeAnalysisLoadingPageClient({
jobPostingId,
applicationLabel,
initialSequence,
isError,
}: ResumeAnalysisLoadingPageClientProps) {
const router = useRouter();

Expand All @@ -59,7 +61,11 @@ export default function ResumeAnalysisLoadingPageClient({
const isValidMockApplyId = Number.isInteger(mockApplyId) && mockApplyId > 0;
const [pollingRetryKey, setPollingRetryKey] = useState(0);
const [errorMessage, setErrorMessage] = useState(
isValidMockApplyId ? "" : INVALID_MOCK_APPLY_MESSAGE,
isError
? `응닡 λŒ€κΈ° μ‹œκ°„μ΄ κΈΈμ–΄μ Έ μž‘μ—…μ„ λ©ˆμ·„μ–΄μš”.\nμ†Œλͺ¨λœ ν¬λ ˆλ”§μ΄ λ³΅κ΅¬λμ–΄μš”.`
: isValidMockApplyId
? ""
: INVALID_MOCK_APPLY_MESSAGE,
);

const moveToResult = useCallback(
Expand Down Expand Up @@ -95,7 +101,7 @@ export default function ResumeAnalysisLoadingPageClient({
}, [jobPostingId, mockApplyId, router]);

useEffect(() => {
if (!isValidMockApplyId) {
if (isError || !isValidMockApplyId) {
return;
}

Expand Down Expand Up @@ -251,7 +257,14 @@ export default function ResumeAnalysisLoadingPageClient({
window.clearInterval(pollTimer);
window.clearTimeout(timeoutTimer);
};
}, [isValidMockApplyId, mockApplyId, moveToResult, pollingRetryKey, taskId]);
}, [
isValidMockApplyId,
mockApplyId,
moveToResult,
pollingRetryKey,
taskId,
isError,
]);

const handleRetry = () => {
if (!isValidMockApplyId) {
Expand All @@ -274,17 +287,13 @@ export default function ResumeAnalysisLoadingPageClient({
{errorMessage && (
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-bg-lightbox-default">
<ModalNotice
type="confirmationModal"
title="뢄석 κ²°κ³Όλ₯Ό λΆˆλŸ¬μ˜€μ§€ λͺ»ν–ˆμ–΄μš”"
type="notice"
title="λ‚˜μ€‘μ— λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."
description={errorMessage}
onClose={handleRetry}
secondaryAction={{
label: "μžμ†Œμ„œλ‘œ λŒμ•„κ°€κΈ°",
onClick: moveBackToResume,
}}
onClose={moveBackToResume}
primaryAction={{
label: "λ‹€μ‹œ 확인",
onClick: handleRetry,
label: "확인",
onClick: moveBackToResume,
}}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface ResumeAnalysisLoadingPageProps {
taskId?: string;
jobPostingId?: string;
sequence?: string;
error?: string;
}>;
}

Expand All @@ -20,7 +21,7 @@ function parsePositiveNumber(value?: string) {
export default async function ResumeAnalysisLoadingPage({
searchParams,
}: ResumeAnalysisLoadingPageProps) {
const { taskId, jobPostingId, sequence } = await searchParams;
const { taskId, jobPostingId, sequence, error } = await searchParams;
const parsedJobPostingId = parsePositiveNumber(jobPostingId);
const parsedSequence = parsePositiveNumber(sequence);

Expand All @@ -30,6 +31,7 @@ export default async function ResumeAnalysisLoadingPage({
jobPostingId={parsedJobPostingId}
initialSequence={parsedSequence}
applicationLabel={formatApplicationSequenceLabel(parsedSequence)}
isError={error === "true"}
/>
);
}
Loading