feat: quick-capture bookmarklet
One-click job capture from any posting, reusing the existing jobimport/preview parser. - AddJobModal accepts initialUrl and auto-imports once on open - App reads a /?add=<encoded url> param, opens Add Job pre-filled, and strips the param from the address bar - QuickCaptureCard in Settings offers a draggable bookmarklet (href set via ref since React blocks javascript: URLs) plus copyable code - EN/NB translations; README feature note - 2 frontend tests; full suite green (22 suites / 50 tests) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
|
||||
@@ -37,6 +37,7 @@ interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onCreated: () => void;
|
||||
initialUrl?: string;
|
||||
}
|
||||
|
||||
type DuplicateCandidate = {
|
||||
@@ -97,7 +98,7 @@ function normalizeLanguage(value?: string | null) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
export default function AddJobModal({ open, onClose, onCreated }: Props) {
|
||||
export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Props) {
|
||||
const { toast } = useToast();
|
||||
const { t, language } = useI18n();
|
||||
|
||||
@@ -137,6 +138,21 @@ export default function AddJobModal({ open, onClose, onCreated }: Props) {
|
||||
setCompanies(cachedCompanies);
|
||||
}, [cachedCompanies]);
|
||||
|
||||
// Quick-capture: when opened with a URL (from the bookmarklet), prefill and auto-import once.
|
||||
const autoImportedUrlRef = useRef<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
autoImportedUrlRef.current = null;
|
||||
return;
|
||||
}
|
||||
const url = initialUrl?.trim();
|
||||
if (!url || autoImportedUrlRef.current === url) return;
|
||||
autoImportedUrlRef.current = url;
|
||||
setJobUrl(url);
|
||||
void importFromUrl(url);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open, initialUrl]);
|
||||
|
||||
const resetForm = () => {
|
||||
setCompany(null);
|
||||
setCompanyInput("");
|
||||
@@ -223,16 +239,17 @@ export default function AddJobModal({ open, onClose, onCreated }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
const importFromUrl = async () => {
|
||||
const importFromUrl = async (urlArg?: string) => {
|
||||
if (importing) return;
|
||||
if (!jobUrl.trim()) {
|
||||
const url = (urlArg ?? jobUrl).trim();
|
||||
if (!url) {
|
||||
toast(t("addJobModalPasteUrlFirst"), "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
setImporting(true);
|
||||
try {
|
||||
const res = await api.post<JobImportResult>("/jobimport/preview", { url: jobUrl.trim() });
|
||||
const res = await api.post<JobImportResult>("/jobimport/preview", { url });
|
||||
const r = res.data;
|
||||
if (!r?.success) throw new Error(r?.error || t("addJobModalImportFailed"));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user