JB/web/src/components/SaveJobButton.tsx

160 lines
4.5 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
interface SaveJobButtonProps {
jobId: string;
initialIsSaved?: boolean;
className?: string;
variant?: "icon" | "button" | "small";
onSavedChange?: (isSaved: boolean) => void;
}
export function SaveJobButton({
jobId,
initialIsSaved,
className = "",
variant = "button",
onSavedChange,
}: SaveJobButtonProps) {
const { data: session } = useSession();
const router = useRouter();
const [isSaved, setIsSaved] = useState<boolean>(initialIsSaved ?? false);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
if (initialIsSaved === undefined && session?.user) {
// Fetch initial save state if not provided explicitly
fetch(`/api/saved-jobs/${jobId}`)
.then((res) => res.json())
.then((data) => {
if (data && typeof data.isSaved === "boolean") {
setIsSaved(data.isSaved);
}
})
.catch(() => {});
}
}, [jobId, session, initialIsSaved]);
const toggleSave = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!session?.user) {
router.push("/login");
return;
}
setLoading(true);
try {
if (isSaved) {
// Unsave job
const res = await fetch(`/api/saved-jobs/${jobId}`, {
method: "DELETE",
});
if (res.ok) {
setIsSaved(false);
if (onSavedChange) onSavedChange(false);
}
} else {
// Save job
const res = await fetch("/api/saved-jobs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ jobId }),
});
if (res.ok) {
setIsSaved(true);
if (onSavedChange) onSavedChange(true);
}
}
} catch (err) {
console.error("Failed to toggle save status:", err);
} finally {
setLoading(false);
}
};
if (variant === "icon") {
return (
<button
onClick={toggleSave}
disabled={loading}
title={isSaved ? "Unsave job" : "Save job"}
className={`p-2 rounded-full transition-colors ${
isSaved
? "text-yellow-500 bg-yellow-50 hover:bg-yellow-100"
: "text-zinc-400 hover:text-zinc-600 hover:bg-zinc-100"
} ${className}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill={isSaved ? "currentColor" : "none"}
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="w-5 h-5"
>
<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" />
</svg>
</button>
);
}
if (variant === "small") {
return (
<button
onClick={toggleSave}
disabled={loading}
className={`inline-flex items-center space-x-1 px-2.5 py-1 text-xs font-medium rounded border transition-colors ${
isSaved
? "bg-yellow-50 text-yellow-700 border-yellow-300 hover:bg-yellow-100"
: "bg-white text-zinc-700 border-zinc-300 hover:bg-zinc-50"
} ${className}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill={isSaved ? "currentColor" : "none"}
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
>
<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" />
</svg>
<span>{isSaved ? "Saved" : "Save"}</span>
</button>
);
}
return (
<button
onClick={toggleSave}
disabled={loading}
className={`inline-flex items-center space-x-2 px-4 py-2 text-sm font-medium rounded-md border shadow-sm transition-colors ${
isSaved
? "bg-yellow-50 text-yellow-800 border-yellow-300 hover:bg-yellow-100"
: "bg-white text-zinc-700 border-zinc-300 hover:bg-zinc-50"
} ${className}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill={isSaved ? "currentColor" : "none"}
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="w-4 h-4"
>
<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" />
</svg>
<span>{isSaved ? "Saved to Bookmarks" : "Save Job"}</span>
</button>
);
}