Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
at main 142 lines 5.4 kB view raw
1import React, { useState, useEffect } from "react"; 2import { X, Loader2, History } from "lucide-react"; 3import { formatDistanceToNow } from "date-fns"; 4import type { AnnotationItem, EditHistoryItem } from "../../types"; 5 6interface EditHistoryModalProps { 7 isOpen: boolean; 8 onClose: () => void; 9 item: AnnotationItem; 10} 11 12export default function EditHistoryModal({ 13 isOpen, 14 onClose, 15 item, 16}: EditHistoryModalProps) { 17 const [history, setHistory] = useState<EditHistoryItem[]>([]); 18 const [loading, setLoading] = useState(false); 19 const [error, setError] = useState<string | null>(null); 20 21 useEffect(() => { 22 const fetchHistory = async () => { 23 if (!item.uri) return; 24 25 try { 26 setLoading(true); 27 setError(null); 28 const res = await fetch( 29 `/api/annotations/history?uri=${encodeURIComponent(item.uri)}`, 30 ); 31 if (!res.ok) throw new Error("Failed to fetch history"); 32 const data = await res.json(); 33 setHistory(data); 34 } catch (err) { 35 console.error(err); 36 setError("Failed to load edit history"); 37 } finally { 38 setLoading(false); 39 } 40 }; 41 42 if (isOpen && item.uri) { 43 fetchHistory(); 44 document.body.style.overflow = "hidden"; 45 } 46 return () => { 47 document.body.style.overflow = "unset"; 48 }; 49 }, [isOpen, item.uri]); 50 51 if (!isOpen) return null; 52 53 return ( 54 <div 55 className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-fade-in" 56 onClick={onClose} 57 > 58 <div 59 className="w-full max-w-lg bg-white dark:bg-surface-900 rounded-3xl shadow-2xl overflow-hidden flex flex-col max-h-[80vh]" 60 onClick={(e) => e.stopPropagation()} 61 > 62 <div className="p-4 flex justify-between items-center border-b border-surface-100 dark:border-surface-800 shrink-0"> 63 <div className="flex items-center gap-2"> 64 <History className="text-surface-500" size={20} /> 65 <h2 className="text-xl font-display font-bold text-surface-900 dark:text-white"> 66 Edit History 67 </h2> 68 </div> 69 <button 70 onClick={onClose} 71 className="p-2 text-surface-400 hover:text-surface-900 dark:hover:text-white hover:bg-surface-50 dark:hover:bg-surface-800 rounded-full transition-colors" 72 > 73 <X size={20} /> 74 </button> 75 </div> 76 77 <div className="p-0 overflow-y-auto flex-1 custom-scrollbar"> 78 {loading ? ( 79 <div className="flex justify-center p-8"> 80 <Loader2 className="animate-spin text-primary-500" size={32} /> 81 </div> 82 ) : error ? ( 83 <div className="p-8 text-center text-red-500">{error}</div> 84 ) : history.length === 0 ? ( 85 <div className="p-8 text-center text-surface-500"> 86 No edit history found. 87 </div> 88 ) : ( 89 <div className="divide-y divide-surface-100 dark:divide-surface-800"> 90 <div className="p-4 bg-primary-50/50 dark:bg-primary-900/10"> 91 <div className="flex justify-between items-start mb-2"> 92 <span className="text-xs font-bold uppercase tracking-wider text-primary-600 dark:text-primary-400"> 93 Current Version 94 </span> 95 <span className="text-xs text-surface-400"> 96 {item.editedAt 97 ? `Edited ${formatDistanceToNow(new Date(item.editedAt))} ago` 98 : `Posted ${formatDistanceToNow(new Date(item.createdAt))} ago`} 99 </span> 100 </div> 101 <div className="text-surface-900 dark:text-white whitespace-pre-wrap text-sm"> 102 {item.text || item.body?.value} 103 </div> 104 </div> 105 106 {history.map((edit, index) => ( 107 <div 108 key={edit.cid || index} 109 className="p-4 hover:bg-surface-50 dark:hover:bg-surface-800/50 transition-colors" 110 > 111 <div className="flex justify-between items-start mb-2"> 112 <span className="text-xs font-medium text-surface-500"> 113 Previous Version 114 </span> 115 <span 116 className="text-xs text-surface-400" 117 title={new Date(edit.editedAt).toLocaleString()} 118 > 119 {formatDistanceToNow(new Date(edit.editedAt))} ago 120 </span> 121 </div> 122 <div className="text-surface-600 dark:text-surface-300 whitespace-pre-wrap text-sm"> 123 {edit.previousContent} 124 </div> 125 </div> 126 ))} 127 </div> 128 )} 129 </div> 130 131 <div className="p-4 border-t border-surface-100 dark:border-surface-800 bg-surface-50 dark:bg-surface-800/50 shrink-0"> 132 <button 133 onClick={onClose} 134 className="w-full py-2.5 bg-white dark:bg-surface-800 border border-surface-200 dark:border-surface-700 text-surface-700 dark:text-surface-200 font-medium rounded-xl hover:bg-surface-50 dark:hover:bg-surface-700 transition-colors" 135 > 136 Close 137 </button> 138 </div> 139 </div> 140 </div> 141 ); 142}