import { useState, useEffect, useCallback } from "react"; import { Link } from "react-router-dom"; import { Plus } from "lucide-react"; import { useAuth } from "../context/AuthContext"; import { getUserBookmarks, deleteBookmark, createBookmark, getURLMetadata, } from "../api/client"; import { BookmarkIcon } from "../components/Icons"; import BookmarkCard from "../components/BookmarkCard"; import AddToCollectionModal from "../components/AddToCollectionModal"; export default function Bookmarks() { const { user, isAuthenticated, loading } = useAuth(); const [bookmarks, setBookmarks] = useState([]); const [loadingBookmarks, setLoadingBookmarks] = useState(true); const [error, setError] = useState(null); const [showAddForm, setShowAddForm] = useState(false); const [newUrl, setNewUrl] = useState(""); const [newTitle, setNewTitle] = useState(""); const [submitting, setSubmitting] = useState(false); const [fetchingTitle, setFetchingTitle] = useState(false); const [collectionModalState, setCollectionModalState] = useState({ isOpen: false, uri: null, }); const loadBookmarks = useCallback(async () => { if (!user?.did) return; try { setLoadingBookmarks(true); const data = await getUserBookmarks(user.did); setBookmarks(data.items || []); } catch (err) { console.error("Failed to load bookmarks:", err); setError(err.message); } finally { setLoadingBookmarks(false); } }, [user]); useEffect(() => { if (isAuthenticated && user) { loadBookmarks(); } }, [isAuthenticated, user, loadBookmarks]); const handleDelete = async (uri) => { if (!confirm("Delete this bookmark?")) return; try { const parts = uri.split("/"); const rkey = parts[parts.length - 1]; await deleteBookmark(rkey); setBookmarks((prev) => prev.filter((b) => (b.id || b.uri) !== uri)); } catch (err) { alert("Failed to delete: " + err.message); } }; const handleUrlBlur = async () => { if (!newUrl.trim() || newTitle.trim()) return; try { new URL(newUrl); } catch { return; } try { setFetchingTitle(true); const data = await getURLMetadata(newUrl.trim()); if (data.title && !newTitle) { setNewTitle(data.title); } } catch (err) { console.error("Failed to fetch title:", err); } finally { setFetchingTitle(false); } }; const handleAddBookmark = async (e) => { e.preventDefault(); if (!newUrl.trim()) return; try { setSubmitting(true); await createBookmark(newUrl.trim(), newTitle.trim() || undefined); setNewUrl(""); setNewTitle(""); setShowAddForm(false); await loadBookmarks(); } catch (err) { alert("Failed to add bookmark: " + err.message); } finally { setSubmitting(false); } }; if (loading) return (
); if (!isAuthenticated) { return (

Sign in to view your bookmarks

You need to be logged in with your Bluesky account

Sign in with Bluesky
); } return (

My Bookmarks

Pages you've saved for later

{showAddForm && (

Add a Bookmark

setNewUrl(e.target.value)} onBlur={handleUrlBlur} className="input" style={{ width: "100%" }} required autoFocus />
setNewTitle(e.target.value)} className="input" style={{ width: "100%" }} />
)} {loadingBookmarks ? (
{[1, 2, 3].map((i) => (
))}
) : error ? (
⚠️

Error loading bookmarks

{error}

) : bookmarks.length === 0 ? (

No bookmarks yet

Click "Add Bookmark" above to save a page, or use the browser extension.

) : (
{bookmarks.map((bookmark) => ( setCollectionModalState({ isOpen: true, uri: bookmark.uri || bookmark.id, }) } /> ))}
)} {collectionModalState.isOpen && ( setCollectionModalState({ isOpen: false, uri: null })} annotationUri={collectionModalState.uri} /> )}
); }