Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
at main 129 lines 3.9 kB view raw
1import { useState, useEffect } from "react"; 2import { Link } from "react-router-dom"; 3import { useAuth } from "../context/AuthContext"; 4import { getUserHighlights, deleteHighlight } from "../api/client"; 5import { HighlightIcon } from "../components/Icons"; 6import { HighlightCard } from "../components/AnnotationCard"; 7 8export default function Highlights() { 9 const { user, isAuthenticated, loading } = useAuth(); 10 const [highlights, setHighlights] = useState([]); 11 const [loadingHighlights, setLoadingHighlights] = useState(true); 12 const [error, setError] = useState(null); 13 14 useEffect(() => { 15 async function loadHighlights() { 16 if (!user?.did) return; 17 18 try { 19 setLoadingHighlights(true); 20 const data = await getUserHighlights(user.did); 21 setHighlights(data.items || []); 22 } catch (err) { 23 console.error("Failed to load highlights:", err); 24 setError(err.message); 25 } finally { 26 setLoadingHighlights(false); 27 } 28 } 29 30 if (isAuthenticated && user) { 31 loadHighlights(); 32 } 33 }, [isAuthenticated, user]); 34 35 const handleDelete = async (uri) => { 36 if (!confirm("Delete this highlight?")) return; 37 38 try { 39 const parts = uri.split("/"); 40 const rkey = parts[parts.length - 1]; 41 await deleteHighlight(rkey); 42 setHighlights((prev) => prev.filter((h) => (h.id || h.uri) !== uri)); 43 } catch (err) { 44 alert("Failed to delete: " + err.message); 45 } 46 }; 47 48 if (loading) 49 return ( 50 <div className="page-loading"> 51 <div className="spinner"></div> 52 </div> 53 ); 54 55 if (!isAuthenticated) { 56 return ( 57 <div className="new-page"> 58 <div className="card" style={{ textAlign: "center", padding: "48px" }}> 59 <h2>Sign in to view your highlights</h2> 60 <p style={{ color: "var(--text-secondary)", marginTop: "8px" }}> 61 You need to be logged in with your Bluesky account 62 </p> 63 <Link 64 to="/login" 65 className="btn btn-primary" 66 style={{ marginTop: "24px" }} 67 > 68 Sign in with Bluesky 69 </Link> 70 </div> 71 </div> 72 ); 73 } 74 75 return ( 76 <div className="feed-page"> 77 <div className="page-header"> 78 <h1 className="page-title">My Highlights</h1> 79 <p className="page-description"> 80 Text you&apos;ve highlighted across the web 81 </p> 82 </div> 83 84 {loadingHighlights ? ( 85 <div className="feed"> 86 {[1, 2, 3].map((i) => ( 87 <div key={i} className="card"> 88 <div 89 className="skeleton skeleton-text" 90 style={{ width: "40%" }} 91 ></div> 92 <div className="skeleton skeleton-text"></div> 93 <div 94 className="skeleton skeleton-text" 95 style={{ width: "60%" }} 96 ></div> 97 </div> 98 ))} 99 </div> 100 ) : error ? ( 101 <div className="empty-state"> 102 <div className="empty-state-icon"></div> 103 <h3 className="empty-state-title">Error loading highlights</h3> 104 <p className="empty-state-text">{error}</p> 105 </div> 106 ) : highlights.length === 0 ? ( 107 <div className="empty-state"> 108 <div className="empty-state-icon"> 109 <HighlightIcon size={32} /> 110 </div> 111 <h3 className="empty-state-title">No highlights yet</h3> 112 <p className="empty-state-text"> 113 Highlight text on any page using the browser extension. 114 </p> 115 </div> 116 ) : ( 117 <div className="feed"> 118 {highlights.map((highlight) => ( 119 <HighlightCard 120 key={highlight.id} 121 highlight={highlight} 122 onDelete={handleDelete} 123 /> 124 ))} 125 </div> 126 )} 127 </div> 128 ); 129}