import { useState, useEffect, useRef } from "react";
import type { FileVersionSummary, FileVersion, VersionSource } from "../../lib/api";
interface VersionHistoryDropdownProps {
currentVersion: number;
versions: FileVersionSummary[];
loading: boolean;
selectedVersion: FileVersion | null;
loadingVersion: boolean;
onSelectVersion: (version: number) => void;
onRestoreVersion: (version: number) => void;
onClearSelection: () => void;
restoring: boolean;
}
function formatDate(dateString: string): string {
const date = new Date(dateString);
return date.toLocaleString("en-US", {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
}
function getSourceLabel(source: VersionSource): string {
switch (source) {
case "user":
return "User";
case "llm":
return "AI";
case "system":
return "System";
}
}
function getSourceColor(source: VersionSource): string {
switch (source) {
case "user":
return "text-blue-400";
case "llm":
return "text-purple-400";
case "system":
return "text-gray-400";
}
}
export function VersionHistoryDropdown({
currentVersion,
versions,
loading,
selectedVersion,
loadingVersion,
onSelectVersion,
onRestoreVersion,
onClearSelection,
restoring,
}: VersionHistoryDropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [versionToRestore, setVersionToRestore] = useState<number | null>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
// Close dropdown when clicking outside
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const handleVersionClick = (version: number) => {
if (version === currentVersion) return;
onSelectVersion(version);
setIsOpen(false);
};
const handleRestoreClick = (version: number) => {
setVersionToRestore(version);
setShowConfirm(true);
};
const handleConfirmRestore = () => {
if (versionToRestore !== null) {
onRestoreVersion(versionToRestore);
}
setShowConfirm(false);
setVersionToRestore(null);
};
const handleCancelRestore = () => {
setShowConfirm(false);
setVersionToRestore(null);
onClearSelection();
};
// If showing the selected version preview
if (selectedVersion) {
return (
<div className="border border-[#3f6fb3]/50 bg-[#0d1b2d] p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<span className="font-mono text-xs text-[#75aafc] uppercase">Viewing Version</span>
<span className="font-mono text-sm text-[#dbe7ff] font-bold">
v{selectedVersion.version}
</span>
<span className={`font-mono text-xs ${getSourceColor(selectedVersion.source)}`}>
({getSourceLabel(selectedVersion.source)})
</span>
</div>
<button
onClick={onClearSelection}
className="font-mono text-xs text-[#555] hover:text-white/70"
>
Close
</button>
</div>
<div className="text-[10px] font-mono text-[#555] mb-3">
{formatDate(selectedVersion.createdAt)}
{selectedVersion.changeDescription && (
<span className="ml-2 text-[#9bc3ff]">- {selectedVersion.changeDescription}</span>
)}
</div>
{/* Preview of version content */}
<div className="max-h-48 overflow-y-auto mb-4 border border-[#333] bg-black/20 p-3">
{selectedVersion.body.length === 0 ? (
<div className="text-[#555] text-xs italic">No content</div>
) : (
<div className="space-y-2">
{selectedVersion.body.map((element, index) => (
<div key={index} className="font-mono text-xs text-white/70">
{element.type === "heading" && (
<div className="text-[#9bc3ff] font-bold">
{"#".repeat(element.level)} {element.text}
</div>
)}
{element.type === "paragraph" && (
<div className="text-white/60">{element.text}</div>
)}
{element.type === "chart" && (
<div className="text-purple-400">[Chart: {element.title || element.chartType}]</div>
)}
{element.type === "image" && (
<div className="text-green-400">[Image: {element.alt || element.src}]</div>
)}
</div>
))}
</div>
)}
</div>
{/* Restore button */}
{!showConfirm ? (
<button
onClick={() => handleRestoreClick(selectedVersion.version)}
disabled={restoring}
className="w-full px-3 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50"
>
{restoring ? "Restoring..." : "Restore This Version"}
</button>
) : (
<div className="border border-yellow-500/30 bg-yellow-500/10 p-3">
<div className="font-mono text-xs text-yellow-400 mb-2">
Are you sure you want to restore to version {versionToRestore}?
</div>
<div className="font-mono text-[10px] text-white/50 mb-3">
This will create a new version with the content from v{versionToRestore}.
Your current changes will be preserved as a separate version.
</div>
<div className="flex gap-2">
<button
onClick={handleConfirmRestore}
disabled={restoring}
className="flex-1 px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-yellow-600/50 border border-yellow-500/50 hover:bg-yellow-600/70 transition-colors disabled:opacity-50"
>
{restoring ? "Restoring..." : "Confirm Restore"}
</button>
<button
onClick={handleCancelRestore}
disabled={restoring}
className="px-3 py-1.5 font-mono text-xs text-[#555] border border-[#333] hover:text-white/70"
>
Cancel
</button>
</div>
</div>
)}
</div>
);
}
return (
<div ref={dropdownRef} className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
>
<span>v{currentVersion}</span>
<svg
width="10"
height="6"
viewBox="0 0 10 6"
fill="none"
className={`transition-transform ${isOpen ? "rotate-180" : ""}`}
>
<path d="M1 1L5 5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
{isOpen && (
<div className="absolute top-full left-0 mt-1 w-64 max-h-72 overflow-y-auto bg-[#0d1b2d] border border-[#3f6fb3]/50 shadow-lg z-50">
<div className="p-2 border-b border-[#333] font-mono text-[10px] text-[#555] uppercase">
Version History
</div>
{loading ? (
<div className="p-4 text-center font-mono text-xs text-[#555]">Loading...</div>
) : versions.length === 0 ? (
<div className="p-4 text-center font-mono text-xs text-[#555]">No versions found</div>
) : (
<div>
{versions.map((version) => (
<button
key={version.version}
onClick={() => handleVersionClick(version.version)}
disabled={version.version === currentVersion || loadingVersion}
className={`w-full text-left px-3 py-2 font-mono text-xs transition-colors ${
version.version === currentVersion
? "bg-[#0f3c78]/50 text-[#dbe7ff]"
: "hover:bg-[#0f3c78]/30 text-white/70"
} ${loadingVersion ? "opacity-50" : ""}`}
>
<div className="flex items-center justify-between">
<span className="font-bold">v{version.version}</span>
<span className={getSourceColor(version.source)}>
{getSourceLabel(version.source)}
</span>
</div>
<div className="text-[10px] text-[#555] mt-0.5">
{formatDate(version.createdAt)}
</div>
{version.changeDescription && (
<div className="text-[10px] text-[#75aafc] mt-0.5 truncate">
{version.changeDescription}
</div>
)}
{version.version === currentVersion && (
<div className="text-[10px] text-green-400 mt-0.5">(current)</div>
)}
</button>
))}
</div>
)}
</div>
)}
</div>
);
}