diff --git a/app/(home-pages)/reader/SubscriptionsContent.tsx b/app/(home-pages)/reader/SubscriptionsContent.tsx index ca9cce13..9645822f 100644 --- a/app/(home-pages)/reader/SubscriptionsContent.tsx +++ b/app/(home-pages)/reader/SubscriptionsContent.tsx @@ -32,7 +32,7 @@ export const SubscriptionsContent = (props: { const { data, error, size, setSize, isValidating } = useSWRInfinite( getKey, - ([_, cursor]) => getSubscriptions(cursor), + ([_, cursor]) => getSubscriptions(null, cursor), { fallbackData: [ { subscriptions: props.publications, nextCursor: props.nextCursor }, diff --git a/app/(home-pages)/reader/getSubscriptions.ts b/app/(home-pages)/reader/getSubscriptions.ts index 98f7934f..bff1cd6d 100644 --- a/app/(home-pages)/reader/getSubscriptions.ts +++ b/app/(home-pages)/reader/getSubscriptions.ts @@ -8,12 +8,21 @@ import { supabaseServerClient } from "supabase/serverClient"; import { idResolver } from "./idResolver"; import { Cursor } from "./getReaderFeed"; -export async function getSubscriptions(cursor?: Cursor | null): Promise<{ +export async function getSubscriptions( + did?: string | null, + cursor?: Cursor | null, +): Promise<{ nextCursor: null | Cursor; subscriptions: PublicationSubscription[]; }> { - let auth_res = await getIdentityData(); - if (!auth_res?.atp_did) return { subscriptions: [], nextCursor: null }; + // If no DID provided, use logged-in user's DID + let identity = did; + if (!identity) { + const auth_res = await getIdentityData(); + if (!auth_res?.atp_did) return { subscriptions: [], nextCursor: null }; + identity = auth_res.atp_did; + } + let query = supabaseServerClient .from("publication_subscriptions") .select(`*, publications(*, documents_in_publications(*, documents(*)))`) @@ -25,7 +34,7 @@ export async function getSubscriptions(cursor?: Cursor | null): Promise<{ }) .limit(1, { referencedTable: "publications.documents_in_publications" }) .limit(25) - .eq("identity", auth_res.atp_did); + .eq("identity", identity); if (cursor) { query = query.or( diff --git a/app/p/[didOrHandle]/(profile)/subscriptions/SubscriptionsContent.tsx b/app/p/[didOrHandle]/(profile)/subscriptions/SubscriptionsContent.tsx new file mode 100644 index 00000000..9e5e4ca8 --- /dev/null +++ b/app/p/[didOrHandle]/(profile)/subscriptions/SubscriptionsContent.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import useSWRInfinite from "swr/infinite"; +import { PubListing } from "app/(home-pages)/discover/PubListing"; +import { + getSubscriptions, + type PublicationSubscription, +} from "app/(home-pages)/reader/getSubscriptions"; +import { Cursor } from "app/(home-pages)/reader/getReaderFeed"; + +export const ProfileSubscriptionsContent = (props: { + did: string; + subscriptions: PublicationSubscription[]; + nextCursor: Cursor | null; +}) => { + const getKey = ( + pageIndex: number, + previousPageData: { + subscriptions: PublicationSubscription[]; + nextCursor: Cursor | null; + } | null, + ) => { + // Reached the end + if (previousPageData && !previousPageData.nextCursor) return null; + + // First page, we don't have previousPageData + if (pageIndex === 0) + return ["profile-subscriptions", props.did, null] as const; + + // Add the cursor to the key + return [ + "profile-subscriptions", + props.did, + previousPageData?.nextCursor, + ] as const; + }; + + const { data, size, setSize, isValidating } = useSWRInfinite( + getKey, + ([_, did, cursor]) => getSubscriptions(did, cursor), + { + fallbackData: [ + { subscriptions: props.subscriptions, nextCursor: props.nextCursor }, + ], + revalidateFirstPage: false, + }, + ); + + const loadMoreRef = useRef(null); + + // Set up intersection observer to load more when trigger element is visible + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && !isValidating) { + const hasMore = data && data[data.length - 1]?.nextCursor; + if (hasMore) { + setSize(size + 1); + } + } + }, + { threshold: 0.1 }, + ); + + if (loadMoreRef.current) { + observer.observe(loadMoreRef.current); + } + + return () => observer.disconnect(); + }, [data, size, setSize, isValidating]); + + const allSubscriptions = data + ? data.flatMap((page) => page.subscriptions) + : []; + + if (allSubscriptions.length === 0 && !isValidating) { + return ( +
No subscriptions yet
+ ); + } + + return ( +
+
+ {allSubscriptions.map((sub) => ( + + ))} +
+ {/* Trigger element for loading more subscriptions */} + + ); +}; diff --git a/app/p/[didOrHandle]/(profile)/subscriptions/page.tsx b/app/p/[didOrHandle]/(profile)/subscriptions/page.tsx index d9c79f04..bdbe3c56 100644 --- a/app/p/[didOrHandle]/(profile)/subscriptions/page.tsx +++ b/app/p/[didOrHandle]/(profile)/subscriptions/page.tsx @@ -1,3 +1,28 @@ -export default function ProfileSubscriptionsPage() { - return
subscriptions here!
; +import { idResolver } from "app/(home-pages)/reader/idResolver"; +import { getSubscriptions } from "app/(home-pages)/reader/getSubscriptions"; +import { ProfileSubscriptionsContent } from "./SubscriptionsContent"; + +export default async function ProfileSubscriptionsPage(props: { + params: Promise<{ didOrHandle: string }>; +}) { + const params = await props.params; + const didOrHandle = decodeURIComponent(params.didOrHandle); + + // Resolve handle to DID if necessary + let did = didOrHandle; + if (!didOrHandle.startsWith("did:")) { + const resolved = await idResolver.handle.resolve(didOrHandle); + if (!resolved) return null; + did = resolved; + } + + const { subscriptions, nextCursor } = await getSubscriptions(did); + + return ( + + ); }