Job search tracker: CRUD, AI parse/tailor/cover via OmniRoute, legacy import
This commit is contained in:
85
src/app/jobs/new/page.tsx
Normal file
85
src/app/jobs/new/page.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
type Parsed = Record<string, string | number | null>;
|
||||
|
||||
export default function NewJobPage() {
|
||||
const router = useRouter();
|
||||
const [url, setUrl] = useState("");
|
||||
const [text, setText] = useState("");
|
||||
const [parsed, setParsed] = useState<Parsed | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [err, setErr] = useState("");
|
||||
|
||||
async function parse() {
|
||||
setBusy(true); setErr("");
|
||||
try {
|
||||
const res = await fetch("/api/ai/parse", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ url: url || undefined, text: text || undefined }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error ?? "parse failed");
|
||||
setParsed(data);
|
||||
} catch (e) { setErr(String(e)); } finally { setBusy(false); }
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!parsed) return;
|
||||
setBusy(true);
|
||||
const res = await fetch("/api/jobs", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(parsed),
|
||||
});
|
||||
const job = await res.json();
|
||||
router.push(`/jobs/${job.id}`);
|
||||
}
|
||||
|
||||
function set(k: string, v: string) { setParsed((p) => ({ ...p!, [k]: v || null })); }
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl space-y-4">
|
||||
<h1 className="text-2xl font-bold">Add Job</h1>
|
||||
{!parsed && (
|
||||
<>
|
||||
<input value={url} onChange={(e) => setUrl(e.target.value)} placeholder="Job URL (may fail on Cloudflare sites)"
|
||||
className="w-full rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-2 text-sm" />
|
||||
<div className="text-center text-zinc-400 text-sm">— or paste job text (more reliable) —</div>
|
||||
<textarea value={text} onChange={(e) => setText(e.target.value)} rows={10} placeholder="Paste the full job posting text here"
|
||||
className="w-full rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-2 text-sm" />
|
||||
<button onClick={parse} disabled={busy || (!url && !text)}
|
||||
className="rounded bg-blue-600 text-white px-4 py-2 text-sm disabled:opacity-50 hover:bg-blue-700">
|
||||
{busy ? "Parsing with AI…" : "Parse with AI"}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{err && <div className="rounded bg-red-100 dark:bg-red-900/40 text-red-800 dark:text-red-200 p-3 text-sm">{err}</div>}
|
||||
{parsed && (
|
||||
<div className="space-y-3">
|
||||
<h2 className="font-semibold">Review parsed fields</h2>
|
||||
{["title", "company", "url", "applyUrl", "location", "remoteType", "phEligibility", "payMin", "payMax", "payCurrency", "payPeriod", "tags"].map((k) => (
|
||||
<label key={k} className="block text-sm">
|
||||
<span className="text-zinc-500">{k}</span>
|
||||
<input value={String(parsed[k] ?? "")} onChange={(e) => set(k, e.target.value)}
|
||||
className="mt-1 w-full rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-2" />
|
||||
</label>
|
||||
))}
|
||||
{["description", "requirements"].map((k) => (
|
||||
<label key={k} className="block text-sm">
|
||||
<span className="text-zinc-500">{k}</span>
|
||||
<textarea value={String(parsed[k] ?? "")} onChange={(e) => set(k, e.target.value)} rows={4}
|
||||
className="mt-1 w-full rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-2" />
|
||||
</label>
|
||||
))}
|
||||
<div className="flex gap-2">
|
||||
<button onClick={save} disabled={busy} className="rounded bg-green-600 text-white px-4 py-2 text-sm hover:bg-green-700">Save Job</button>
|
||||
<button onClick={() => setParsed(null)} className="rounded border border-zinc-300 dark:border-zinc-700 px-4 py-2 text-sm">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user