a tool for shared writing and social publishing
1"use client"; 2import { PubListing } from "app/discover/PubListing"; 3import { ButtonPrimary } from "components/Buttons"; 4import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 5import { Json } from "supabase/database.types"; 6import { PublicationSubscription, getSubscriptions } from "./getSubscriptions"; 7import useSWRInfinite from "swr/infinite"; 8import { useEffect, useRef } from "react"; 9import { Cursor } from "./getReaderFeed"; 10 11export const SubscriptionsContent = (props: { 12 publications: PublicationSubscription[]; 13 nextCursor: Cursor | null; 14}) => { 15 const getKey = ( 16 pageIndex: number, 17 previousPageData: { 18 subscriptions: PublicationSubscription[]; 19 nextCursor: Cursor | null; 20 } | null, 21 ) => { 22 // Reached the end 23 if (previousPageData && !previousPageData.nextCursor) return null; 24 25 // First page, we don't have previousPageData 26 if (pageIndex === 0) return ["subscriptions", null] as const; 27 28 // Add the cursor to the key 29 return ["subscriptions", previousPageData?.nextCursor] as const; 30 }; 31 32 const { data, error, size, setSize, isValidating } = useSWRInfinite( 33 getKey, 34 ([_, cursor]) => getSubscriptions(cursor), 35 { 36 fallbackData: [ 37 { subscriptions: props.publications, nextCursor: props.nextCursor }, 38 ], 39 revalidateFirstPage: false, 40 }, 41 ); 42 43 const loadMoreRef = useRef<HTMLDivElement>(null); 44 45 // Set up intersection observer to load more when trigger element is visible 46 useEffect(() => { 47 const observer = new IntersectionObserver( 48 (entries) => { 49 if (entries[0].isIntersecting && !isValidating) { 50 const hasMore = data && data[data.length - 1]?.nextCursor; 51 if (hasMore) { 52 setSize(size + 1); 53 } 54 } 55 }, 56 { threshold: 0.1 }, 57 ); 58 59 if (loadMoreRef.current) { 60 observer.observe(loadMoreRef.current); 61 } 62 63 return () => observer.disconnect(); 64 }, [data, size, setSize, isValidating]); 65 66 const allPublications = data 67 ? data.flatMap((page) => page.subscriptions) 68 : []; 69 70 if (allPublications.length === 0 && !isValidating) 71 return <SubscriptionsEmpty />; 72 73 return ( 74 <div className="relative"> 75 <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 gap-3"> 76 {allPublications?.map((p, index) => <PubListing key={p.uri} {...p} />)} 77 </div> 78 {/* Trigger element for loading more subscriptions */} 79 <div 80 ref={loadMoreRef} 81 className="absolute bottom-96 left-0 w-full h-px pointer-events-none" 82 aria-hidden="true" 83 /> 84 {isValidating && ( 85 <div className="text-center text-tertiary py-4"> 86 Loading more subscriptions... 87 </div> 88 )} 89 </div> 90 ); 91}; 92 93const SubscriptionsEmpty = () => { 94 return ( 95 <div className="flex flex-col gap-2 container bg-[rgba(var(--bg-page),.7)] sm:p-4 p-3 justify-between text-center font-bold text-tertiary"> 96 You haven't subscribed to any publications yet! 97 <ButtonPrimary className="mx-auto place-self-center"> 98 <DiscoverSmall /> Discover Publications 99 </ButtonPrimary> 100 </div> 101 ); 102};