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