160 lines
4.5 KiB
TypeScript
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>
|
|
);
|
|
}
|