-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: QA 1차 반영 #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: QA 1차 반영 #111
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState, type MouseEvent } from "react"; | ||
| import { useEffect, useState, useRef, type MouseEvent } from "react"; | ||
| import { useRouter } from "next/navigation"; | ||
| import Header from "@/components/common/header/Header"; | ||
| import { LLMInput } from "@/components/common/input"; | ||
|
|
@@ -86,48 +86,82 @@ export default function JobPostingCreatePage() { | |
| const [showExitConfirm, setShowExitConfirm] = useState(false); | ||
| const [showInvalidDataModal, setShowInvalidDataModal] = useState(false); | ||
|
|
||
| const [jobPostingToastMessage, setJobPostingToastMessage] = useState< | ||
| string | null | ||
| >(null); | ||
| const [toastMessages, setToastMessages] = useState<string[]>([]); | ||
|
|
||
| const inputContainerRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| useEffect(() => { | ||
| function handleClickOutside(event: globalThis.MouseEvent) { | ||
| if ( | ||
| inputContainerRef.current && | ||
| !inputContainerRef.current.contains(event.target as Node) | ||
| ) { | ||
| const hasInput = | ||
| jobPostingInputValue.trim().length > 0 || attachedFiles.length > 0; | ||
|
|
||
| // 아무것도 입력되지 않았을 때만 원래대로 돌아감 | ||
| if (!hasInput) { | ||
| setIsInputActive(false); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| document.addEventListener("mousedown", handleClickOutside); | ||
| return () => { | ||
| document.removeEventListener("mousedown", handleClickOutside); | ||
| }; | ||
| }, [jobPostingInputValue, attachedFiles]); // 의존성 배열에 상태 추가 | ||
|
|
||
| // ✅ 교체할 부분: URL에서 여러 개의 에러를 가져오도록 useEffect 수정 | ||
| useEffect(() => { | ||
| const searchParams = new URLSearchParams(window.location.search); | ||
| const analysisError = searchParams.get("analysisError"); | ||
|
|
||
| if (analysisError === "not_saved") { | ||
| // getAll을 사용하여 배열 형태로 모두 가져옵니다 | ||
| const analysisErrors = searchParams.getAll("analysisError"); | ||
| const isCanceled = searchParams.get("analysisCanceled"); | ||
|
|
||
| if (analysisErrors.includes("not_saved")) { | ||
| const modalTimer = window.setTimeout(() => { | ||
| setShowInvalidDataModal(true); | ||
| window.history.replaceState(null, "", window.location.pathname); | ||
| }, 0); | ||
|
|
||
| return () => window.clearTimeout(modalTimer); | ||
| } | ||
|
|
||
| const toastMessage = | ||
| searchParams.get("analysisCanceled") === "1" | ||
| ? "공고 분석을 중단했습니다." | ||
| : analysisError === "1" | ||
| ? "업로드에 실패했습니다." | ||
| : analysisError; | ||
| const nextToastMessages: string[] = []; | ||
|
|
||
| if (isCanceled === "1") { | ||
| nextToastMessages.push("공고 분석을 중단했습니다."); | ||
| } | ||
|
|
||
| analysisErrors.forEach((err) => { | ||
| if (err === "1") nextToastMessages.push("업로드에 실패했습니다."); | ||
| else if (err !== "not_saved") nextToastMessages.push(err); | ||
| }); | ||
|
|
||
| if (!toastMessage) { | ||
| if (nextToastMessages.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| const openToastTimer = window.setTimeout(() => { | ||
| setJobPostingToastMessage(toastMessage); | ||
| setToastMessages(nextToastMessages); | ||
| window.history.replaceState(null, "", window.location.pathname); | ||
| }, 0); | ||
|
|
||
| const toastTimer = window.setTimeout(() => { | ||
| setJobPostingToastMessage(null); | ||
| }, 3000); | ||
| setToastMessages([]); | ||
| }, 4000); | ||
|
|
||
| return () => { | ||
| window.clearTimeout(openToastTimer); | ||
| window.clearTimeout(toastTimer); | ||
| }; | ||
| }, []); | ||
|
|
||
| const removeToast = (indexToRemove: number) => { | ||
| setToastMessages((prev) => prev.filter((_, i) => i !== indexToRemove)); | ||
| }; | ||
|
|
||
| const handleSubmit = () => { | ||
| saveJobPostingDraft({ | ||
| files: attachedFiles, | ||
|
|
@@ -172,44 +206,46 @@ export default function JobPostingCreatePage() { | |
| /> | ||
|
|
||
| <div className="flex min-h-0 flex-1 flex-col items-center justify-center self-stretch px-2 pb-2"> | ||
| <main className="flex h-full min-h-0 flex-col items-center justify-start self-stretch overflow-hidden rounded-card-l bg-fill-quaternary-assistive"> | ||
| <div | ||
| <main className="relative flex h-full min-h-0 w-full flex-col items-center justify-center overflow-hidden rounded-card-l bg-fill-quaternary-assistive"> | ||
| <section | ||
| ref={inputContainerRef} | ||
| className={clsx( | ||
| "flex flex-1 flex-col items-center self-stretch", | ||
| isInputActive | ||
| ? "justify-center gap-0 -translate-y-[4dvh]" | ||
| : "justify-center gap-[6.6dvh] pb-[2dvh]", | ||
| "z-10 flex w-full flex-col items-center gap-[60px] transition-transform duration-500 ease-in-out", | ||
| isInputActive ? "translate-y-0" : "-translate-y-[140px]", | ||
| )} | ||
| > | ||
| <section className="flex flex-col items-center gap-[60px]"> | ||
| <div className="flex flex-col items-center gap-2"> | ||
| <h1 className="text-center text-h28-bold text-text-neutral-title [font-feature-settings:'liga'_off,'clig'_off]"> | ||
| 지원하고자하는 기업의 공고를 붙여넣으세요 | ||
| </h1> | ||
| <p className="text-center text-b16-med text-text-neutral-description [font-feature-settings:'liga'_off,'clig'_off]"> | ||
| 첨부한 내용을 바탕으로 모의 공고가 생성되고 내가 작성한 | ||
| 자소서를 채점받을 수 있습니다. | ||
| </p> | ||
| </div> | ||
|
|
||
| <LLMInput | ||
| value={jobPostingInputValue} | ||
| onChange={setJobPostingInputValue} | ||
| onFilesChange={setAttachedFiles} | ||
| onFocus={() => setIsInputActive(true)} | ||
| onSubmit={handleSubmit} | ||
| defaultFiles={initialDraft.files} | ||
| /> | ||
| </section> | ||
|
|
||
| {!isInputActive && ( | ||
| <section className="flex items-start gap-5"> | ||
| {INTRO_STEPS.map((step) => ( | ||
| <JobPostingStepCard key={step.step} step={step} /> | ||
| ))} | ||
| </section> | ||
| <div className="flex flex-col items-center gap-2"> | ||
| <h1 className="text-center text-h28-bold text-text-neutral-title [font-feature-settings:'liga'_off,'clig'_off]"> | ||
| 지원하고자하는 기업의 공고를 붙여넣으세요 | ||
| </h1> | ||
| <p className="text-center text-b16-med text-text-neutral-description [font-feature-settings:'liga'_off,'clig'_off]"> | ||
| 첨부한 내용을 바탕으로 모의 공고가 생성되고 내가 작성한 자소서를 | ||
| 채점받을 수 있습니다. | ||
| </p> | ||
| </div> | ||
|
|
||
| <LLMInput | ||
| value={jobPostingInputValue} | ||
| onChange={setJobPostingInputValue} | ||
| onFilesChange={setAttachedFiles} | ||
| onFocus={() => setIsInputActive(true)} | ||
| onSubmit={handleSubmit} | ||
| defaultFiles={initialDraft.files} | ||
| /> | ||
| </section> | ||
|
|
||
| <section | ||
| className={clsx( | ||
| "absolute left-1/2 top-1/2 z-0 flex w-max -translate-x-1/2 items-start gap-5 transition-all duration-350 ease-in-out", | ||
| isInputActive | ||
| ? "pointer-events-none opacity-0 translate-y-20" | ||
| : "translate-y-10 opacity-100 ", | ||
| )} | ||
| </div> | ||
| > | ||
| {INTRO_STEPS.map((step) => ( | ||
| <JobPostingStepCard key={step.step} step={step} /> | ||
| ))} | ||
| </section> | ||
| </main> | ||
| </div> | ||
|
|
||
|
|
@@ -247,13 +283,18 @@ export default function JobPostingCreatePage() { | |
| </div> | ||
| )} | ||
|
|
||
| {jobPostingToastMessage && ( | ||
| <Toast | ||
| message={jobPostingToastMessage} | ||
| variant="warning" | ||
| onClose={() => setJobPostingToastMessage(null)} | ||
| className="!right-7 !bottom-7 !max-w-none !rounded-card" | ||
| /> | ||
| {toastMessages.length > 0 && ( | ||
| <div className="fixed bottom-7 right-7 z-50 flex flex-col gap-3"> | ||
| {toastMessages.map((msg, index) => ( | ||
| <Toast | ||
| key={index} | ||
| message={msg} | ||
| variant="warning" | ||
| onClose={() => removeToast(index)} | ||
| className="!static !max-w-none !rounded-card" | ||
| /> | ||
| ))} | ||
| </div> | ||
|
Comment on lines
+286
to
+297
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: In Tailwind CSS v4, the recommended syntax for the important modifier has changed from a leading exclamation mark (e.g.,!bg-red-500) to a trailing exclamation mark (e.g., bg-red-500!) [1][2][3]. While the leading exclamation mark syntax used in v3 remains supported for backwards compatibility, it is officially deprecated [2][3]. The trailing syntax is the new standard [1]. This change was implemented to provide more consistent placement of the exclamation mark, especially when combined with variants [1]. For example, in v3, a variant utility like hover:!bg-red-500 placed the modifier in the middle of the class name, whereas in v4, hover:bg-red-500! places it at the very end of the class name [1]. Official documentation and tooling, such as the official ESLint plugin, now recommend and enforce the trailing syntax [2][4]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== repo files likely relevant ==="
git ls-files | rg '(^|/)Toast\.tsx$|jobdri/src/app/mockApply/job/create/page\.tsx$|^(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$|tailwind\.config\.(js|ts|cjs|mjs)|postcss\.config\.(js|cjs|mjs|ts)$' || true
echo
echo "=== package tailwind versions ==="
if [ -f package.json ]; then cat package.json | jq -r '.dependencies.tailwind?:.devDependencies.tailwind?//empty, .dependencies."tailwindcss"?:.devDependencies."tailwindcss"?//empty' 2>/dev/null || true; fi
if [ -f pnpm-lock.yaml ]; then rg -n "tailwindcss|`@tailwindcss`" pnpm-lock.yaml | head -40 || true; fi
if [ -f package-lock.json ]; then cat package-lock.json | jq -r '.packages[""]?.dependencies?.["tailwindcss"], .packages[""]?.devDependencies?.["tailwindcss"], .packages[""]?.dependencies?.tailwind, .packages[""]?.devDependencies?.tailwind' 2>/dev/null || true; fi
echo
echo "=== Toast file ==="
toast_file="$(git ls-files | rg '(^|/)Toast\.tsx$' | head -1)"
if [ -n "${toast_file:-}" ]; then
wc -l "$toast_file"
sed -n '1,120p' "$toast_file"
fi
echo
echo "=== create page relevant lines ==="
page_file="jobdri/src/app/mockApply/job/create/page.tsx"
if [ -f "$page_file" ]; then
wc -l "$page_file"
sed -n '240,315p' "$page_file"
fi
echo
echo "=== removeToast/addToast usages in page ==="
rg -n "toastMessages|removeToast|addToast|useState|Array\.from\(" "$page_file" || true
echo
echo "=== Tailwind postcss tailwind config snippets ==="
for f in $(git ls-files | rg '(^|/)tailwind\.config\.(js|ts|cjs|jsx|tsx|mjs)$|(^|/)postcss\.config\.(js|cjs|mjs|ts)$|(^|/)package\.json$'); do
echo "--- $f"
sed -n '1,220p' "$f" | rg -n "tailwind|postcss|content|plugins|daisyui|v4|css" -C 2 || true
doneRepository: JobDri-Developer/FrontEnd Length of output: 5768 🌐 Web query:
💡 Result: In Tailwind CSS v4, the use of a leading exclamation mark (e.g., Citations:
🌐 Web query:
💡 Result: In Tailwind CSS v4, the important modifier syntax has been updated, but the legacy syntax remains supported for backward compatibility [1][2]. The current recommendation is to place the exclamation mark (!) at the end of the class name (e.g., bg-red-500!) rather than at the beginning (e.g.,!bg-red-500) [1][3]. This change was implemented to ensure consistent placement of the modifier, particularly when used with variants [3]. While the leading exclamation mark syntax (which was standard in v3) is officially deprecated, it is still supported by Tailwind CSS v4 to allow for easier transitions and compatibility with existing codebases [1][2][4]. Citations:
Use stable toast IDs and avoid index-based dismissals.
🤖 Prompt for AI Agents |
||
| )} | ||
| </div> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,6 +32,17 @@ const loadingStatusMessages = [ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type LoadingStepStatus = "done" | "active" | "pending"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface InvalidField { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [key: string]: unknown; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface ParsedError { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| invalidFields?: InvalidField[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [key: string]: unknown; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function createRandomLoadingDurationMs() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MIN_LOADING_DURATION_MS + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -134,39 +145,129 @@ export default function JobPostingLoadingPage() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!rawText && !imageObjectKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("분석할 채용 공고가 없습니다."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error(JSON.stringify(["분석할 채용 공고가 없습니다."])); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const accepted = await ingestJobPosting({ rawText, imageObjectKey }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const status = await waitForJobPostingIngest(accepted.taskId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let status; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status = await waitForJobPostingIngest(accepted.taskId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err: unknown) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (err instanceof Error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parsedStatus = JSON.parse(err.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsedStatus && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (parsedStatus.error || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsedStatus.failureReason || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsedStatus.status === "FAILED") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status = parsedStatus; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (status.status === "FAILED" || status.error || status.failureReason) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const errorMessages: string[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rawErrorData = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status.error || status.failureReason || status.message; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let parsedError: ParsedError | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (rawErrorData) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof rawErrorData === "object") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsedError = rawErrorData as ParsedError; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (typeof rawErrorData === "string") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsedError = JSON.parse(rawErrorData) as ParsedError; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fixedJsonStr = rawErrorData.replace(/'/g, '"'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsedError = JSON.parse(fixedJsonStr) as ParsedError; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorMessages.push(rawErrorData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+188
to
+203
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Single-quote→double-quote coercion can mangle user-facing messages. Line 196 replaces every 🩹 Suggested guard } else if (typeof rawErrorData === "string") {
try {
- parsedError = JSON.parse(rawErrorData) as ParsedError;
+ const parsed: unknown = JSON.parse(rawErrorData);
+ if (parsed && typeof parsed === "object") {
+ parsedError = parsed as ParsedError;
+ } else {
+ errorMessages.push(rawErrorData);
+ }
} catch {
- try {
- const fixedJsonStr = rawErrorData.replace(/'/g, '"');
- parsedError = JSON.parse(fixedJsonStr) as ParsedError;
- } catch {
- errorMessages.push(rawErrorData);
- }
+ errorMessages.push(rawErrorData);
}
}If the backend really does emit Python-repr payloads, fix it at the source rather than with a quote swap here. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (parsedError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Array.isArray(parsedError.invalidFields) && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsedError.invalidFields.length > 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsedError.invalidFields.forEach((field: InvalidField) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (field.message) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorMessages.push(field.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (errorMessages.length === 0 && parsedError.message) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorMessages.push(parsedError.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (errorMessages.length === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorMessages.push( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status.message || "채용 공고 분석에 실패했습니다.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error(JSON.stringify(errorMessages)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!status.result) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("채용 공고 분석 결과를 확인할 수 없습니다."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JSON.stringify(["채용 공고 분석 결과를 확인할 수 없습니다."]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| saveJobPostingAnalysis(status.result); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const resultJobPostingId = status.result.saved?.jobPostingId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isSavedToDb = status.result.savedToDatabase ?? true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ID가 존재하고 DB 저장이 정상적으로 완료되었을 때만 리뷰 페이지로 이동 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return resultJobPostingId && isSavedToDb | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? `/mockApply/job/${resultJobPostingId}/review` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : "/mockApply/job/create?analysisError=not_saved"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!analysisPromiseRef.current) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| analysisPromiseRef.current = analyzeJobPosting().catch((error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const message = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error instanceof Error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? error.message | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : "채용 공고 분석에 실패했습니다."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let params = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rawMessage = error instanceof Error ? error.message : ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `/mockApply/job/create?analysisError=${encodeURIComponent(message)}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (rawMessage.startsWith("[") && rawMessage.endsWith("]")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const messages = JSON.parse(rawMessage) as string[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (messages.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params = messages | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map((msg) => `analysisError=${encodeURIComponent(msg)}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .join("&"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params = `analysisError=${encodeURIComponent(rawMessage || "채용 공고 분석에 실패했습니다.")}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params = `analysisError=${encodeURIComponent("채용 공고 분석에 실패했습니다.")}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `/mockApply/job/create?${params}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void analysisPromiseRef.current.then((destination) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void analysisPromiseRef.current?.then((destination) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isActive) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.replace(destination); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
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
Query params survive when there are no toasts to show.
The early return at line 142 skips
history.replaceState, so a stray?analysisError=(e.g. the empty-array case fromloading/page.tsx) stays in the URL and is re-read on any remount. Clear the params before the early return.🩹 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents