tahamajs/IE / Projects /front /src /components /references /ReferenceImportModal.tsx
tahamajs's picture
download
raw
8.89 kB
// src/components/references/ReferenceImportModal.tsx
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useReferenceManager } from "@/hooks/useReferenceManager";
import { X, Loader2, Globe, FileText, Database, BookOpen } from "lucide-react";
import { toast } from "react-toastify";
interface ReferenceImportModalProps {
isOpen: boolean;
onClose: () => void;
docId: string;
onSuccess?: () => void;
}
export function ReferenceImportModal({
isOpen,
onClose,
docId,
onSuccess,
}: ReferenceImportModalProps) {
const { importFromDOI, importBibTeX, connectZotero, connectMendeley } =
useReferenceManager(docId);
const [mode, setMode] = useState<"doi" | "bibtex" | "zotero" | "mendeley">(
"doi"
);
const [doi, setDoi] = useState("");
const [bibtex, setBibtex] = useState("");
const [zoteroApiKey, setZoteroApiKey] = useState("");
const [zoteroUserId, setZoteroUserId] = useState("");
const [mendeleyToken, setMendeleyToken] = useState("");
const [loading, setLoading] = useState(false);
if (!isOpen) return null;
const handleImport = async () => {
setLoading(true);
try {
if (mode === "doi") {
if (!doi.trim()) throw new Error("Please enter a DOI");
await importFromDOI(doi.trim());
toast.success("Reference imported from DOI");
} else if (mode === "bibtex") {
if (!bibtex.trim()) throw new Error("Please paste a BibTeX entry");
await importBibTeX(bibtex.trim());
toast.success("BibTeX imported");
} else if (mode === "zotero") {
if (!zoteroApiKey.trim() || !zoteroUserId.trim())
throw new Error("API key and user ID are required");
await connectZotero(zoteroApiKey.trim(), zoteroUserId.trim());
toast.success("Zotero references imported");
} else if (mode === "mendeley") {
if (!mendeleyToken.trim())
throw new Error("Mendeley access token is required");
await connectMendeley(mendeleyToken.trim());
toast.success("Mendeley references imported");
}
onSuccess?.();
handleClose();
} catch (err: any) {
toast.error(err.message || "Import failed");
} finally {
setLoading(false);
}
};
const handleClose = () => {
// Reset all fields when closing
setDoi("");
setBibtex("");
setZoteroApiKey("");
setZoteroUserId("");
setMendeleyToken("");
onClose();
};
const tabs = [
{ key: "doi", label: "DOI", icon: Globe },
{ key: "bibtex", label: "BibTeX", icon: FileText },
{ key: "zotero", label: "Zotero", icon: Database },
{ key: "mendeley", label: "Mendeley", icon: BookOpen },
] as const;
return (
<AnimatePresence>
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
onClick={handleClose}
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
className="w-full max-w-lg rounded-2xl bg-white dark:bg-gray-800 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between border-b border-gray-200 dark:border-gray-700 p-4">
<h2 className="text-lg font-bold">Import Reference</h2>
<button
onClick={handleClose}
className="rounded-full p-1 text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
>
<X className="h-5 w-5" />
</button>
</div>
{/* Tab buttons */}
<div className="flex gap-2 border-b border-gray-200 dark:border-gray-700 p-4 pb-2">
{tabs.map((tab) => (
<button
key={tab.key}
onClick={() => setMode(tab.key)}
className={`flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm transition ${
mode === tab.key
? "bg-primary text-white"
: "bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"
}`}
>
<tab.icon className="h-3.5 w-3.5" />
{tab.label}
</button>
))}
</div>
{/* Form fields */}
<div className="p-4">
{mode === "doi" && (
<div className="mb-4">
<label className="mb-1 block text-sm font-medium">DOI</label>
<input
type="text"
placeholder="e.g., 10.1038/s41586-020-2649-2"
value={doi}
onChange={(e) => setDoi(e.target.value)}
className="w-full rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 p-2 dark:bg-gray-700"
/>
<p className="mt-1 text-xs text-gray-400">
Example: 10.1038/s41586-020-2649-2
</p>
</div>
)}
{mode === "bibtex" && (
<div className="mb-4">
<label className="mb-1 block text-sm font-medium">
BibTeX Entry
</label>
<textarea
rows={6}
placeholder="@article{key,\n title={...},\n author={...},\n year={...}\n}"
value={bibtex}
onChange={(e) => setBibtex(e.target.value)}
className="w-full rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 p-2 font-mono text-sm dark:bg-gray-700"
/>
</div>
)}
{mode === "zotero" && (
<div className="mb-4 space-y-3">
<div>
<label className="mb-1 block text-sm font-medium">
Zotero API Key
</label>
<input
type="text"
placeholder="Your Zotero API key"
value={zoteroApiKey}
onChange={(e) => setZoteroApiKey(e.target.value)}
className="w-full rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 p-2 dark:bg-gray-700"
/>
</div>
<div>
<label className="mb-1 block text-sm font-medium">
User ID
</label>
<input
type="text"
placeholder="Your Zotero user ID (numeric)"
value={zoteroUserId}
onChange={(e) => setZoteroUserId(e.target.value)}
className="w-full rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 p-2 dark:bg-gray-700"
/>
</div>
<p className="text-xs text-gray-400">
Get your API key from{" "}
<a
href="https://www.zotero.org/settings/keys"
target="_blank"
rel="noreferrer"
className="text-primary hover:underline"
>
Zotero settings
</a>
.
</p>
</div>
)}
{mode === "mendeley" && (
<div className="mb-4">
<label className="mb-1 block text-sm font-medium">
Mendeley Access Token
</label>
<input
type="text"
placeholder="Paste your Mendeley access token"
value={mendeleyToken}
onChange={(e) => setMendeleyToken(e.target.value)}
className="w-full rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 p-2 dark:bg-gray-700"
/>
<p className="mt-1 text-xs text-gray-400">
Generate a token in your Mendeley developer account.
</p>
</div>
)}
</div>
{/* Action buttons */}
<div className="flex gap-3 border-t border-gray-200 dark:border-gray-700 p-4">
<button
onClick={handleImport}
disabled={loading}
className="btn-primary flex flex-1 items-center justify-center gap-2 rounded-lg py-2"
>
{loading && <Loader2 className="h-4 w-4 animate-spin" />}
Import
</button>
<button
onClick={handleClose}
className="btn-secondary flex flex-1 rounded-lg py-2"
>
Cancel
</button>
</div>
</motion.div>
</div>
</AnimatePresence>
);
}

Xet Storage Details

Size:
8.89 kB
·
Xet hash:
0e5c8161a8f94c9ea8265cd3478524927cf9398d8989fe5fb75c9965d120250c

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.