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";
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};