import { useState, useCallback, useEffect, useRef } from 'react'; import type { BookmarkContextValue } from './bookmarkContext'; import { BookmarkContext } from './bookmarkContext'; import { atprotoClient } from '../api/client'; import type { PostView } from '../api/types'; async function fetchAllBookmarks() { const uris = new Set(); const posts = new Map(); let cursor: string | undefined; do { const resp = await atprotoClient.getBookmarks(100, cursor); for (const bm of resp.bookmarks) { uris.add(bm.subject.uri); const item = bm.item as PostView | { $type: string; uri: string; notFound?: true; blocked?: boolean }; if (item && '$type' in item && (item as { $type: string }).$type === 'app.bsky.feed.defs#postView') { posts.set(bm.subject.uri, item as unknown as PostView); } } cursor = resp.cursor; } while (cursor); return { uris, posts }; } export default function BookmarkProvider({ children }: { children: React.ReactNode }) { const [bookmarkedUris, setBookmarkedUris] = useState>(new Set()); const [bookmarkPosts, setBookmarkPosts] = useState>(new Map()); const [loaded, setLoaded] = useState(false); const mountedRef = useRef(true); const loadBookmarks = useCallback(async () => { try { const { uris, posts } = await fetchAllBookmarks(); if (mountedRef.current) { setBookmarkedUris(uris); setBookmarkPosts(posts); setLoaded(true); } } catch (err) { console.error('Failed to load bookmarks:', err); if (mountedRef.current) setLoaded(true); } }, []); useEffect(() => { mountedRef.current = true; let cancelled = false; (async () => { try { const { uris, posts } = await fetchAllBookmarks(); if (!cancelled) { setBookmarkedUris(uris); setBookmarkPosts(posts); setLoaded(true); } } catch (err) { console.error('Failed to load bookmarks:', err); if (!cancelled) setLoaded(true); } })(); return () => { cancelled = true; mountedRef.current = false; }; }, []); const addBookmark = useCallback(async (postUri: string, postCid: string) => { try { await atprotoClient.createBookmark(postUri, postCid); setBookmarkedUris((prev) => new Set(prev).add(postUri)); } catch (err) { console.error('Failed to add bookmark:', err); } }, []); const removeBookmark = useCallback(async (postUri: string) => { try { await atprotoClient.deleteBookmark(postUri); setBookmarkedUris((prev) => { const next = new Set(prev); next.delete(postUri); return next; }); setBookmarkPosts((prev) => { const next = new Map(prev); next.delete(postUri); return next; }); } catch (err) { console.error('Failed to remove bookmark:', err); } }, []); const isBookmarked = useCallback((postUri: string) => { return bookmarkedUris.has(postUri); }, [bookmarkedUris]); const value: BookmarkContextValue = { bookmarkedUris, bookmarkPosts, loaded, addBookmark, removeBookmark, isBookmarked, reload: loadBookmarks, }; return ( {children} ); }