a tool for shared writing and social publishing

oops forgot the new files

+118
+9
app/(home-pages)/reader/GlobalContent.tsx
··· 1 + "use client"; 2 + 3 + export const GlobalContent = () => { 4 + return ( 5 + <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"> 6 + Nothing here yet… 7 + </div> 8 + ); 9 + };
+100
app/(home-pages)/reader/InboxContent.tsx
··· 1 + "use client"; 2 + import { ButtonPrimary } from "components/Buttons"; 3 + import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 4 + import type { Cursor, Post } from "./getReaderFeed"; 5 + import useSWRInfinite from "swr/infinite"; 6 + import { getReaderFeed } from "./getReaderFeed"; 7 + import { useEffect, useRef } from "react"; 8 + import Link from "next/link"; 9 + import { PostListing } from "components/PostListing"; 10 + 11 + export const InboxContent = (props: { 12 + posts: Post[]; 13 + nextCursor: Cursor | null; 14 + }) => { 15 + const getKey = ( 16 + pageIndex: number, 17 + previousPageData: { 18 + posts: Post[]; 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 ["reader-feed", null] as const; 27 + 28 + // Add the cursor to the key 29 + return ["reader-feed", previousPageData?.nextCursor] as const; 30 + }; 31 + 32 + const { data, size, setSize, isValidating } = useSWRInfinite( 33 + getKey, 34 + ([_, cursor]) => getReaderFeed(cursor), 35 + { 36 + fallbackData: [{ posts: props.posts, nextCursor: props.nextCursor }], 37 + revalidateFirstPage: false, 38 + }, 39 + ); 40 + 41 + const loadMoreRef = useRef<HTMLDivElement>(null); 42 + 43 + // Set up intersection observer to load more when trigger element is visible 44 + useEffect(() => { 45 + const observer = new IntersectionObserver( 46 + (entries) => { 47 + if (entries[0].isIntersecting && !isValidating) { 48 + const hasMore = data && data[data.length - 1]?.nextCursor; 49 + if (hasMore) { 50 + setSize(size + 1); 51 + } 52 + } 53 + }, 54 + { threshold: 0.1 }, 55 + ); 56 + 57 + if (loadMoreRef.current) { 58 + observer.observe(loadMoreRef.current); 59 + } 60 + 61 + return () => observer.disconnect(); 62 + }, [data, size, setSize, isValidating]); 63 + 64 + const allPosts = data ? data.flatMap((page) => page.posts) : []; 65 + 66 + if (allPosts.length === 0 && !isValidating) return <ReaderEmpty />; 67 + 68 + return ( 69 + <div className="flex flex-col gap-3 relative"> 70 + {allPosts.map((p) => ( 71 + <PostListing {...p} key={p.documents.uri} /> 72 + ))} 73 + {/* Trigger element for loading more posts */} 74 + <div 75 + ref={loadMoreRef} 76 + className="absolute bottom-96 left-0 w-full h-px pointer-events-none" 77 + aria-hidden="true" 78 + /> 79 + {isValidating && ( 80 + <div className="text-center text-tertiary py-4"> 81 + Loading more posts... 82 + </div> 83 + )} 84 + </div> 85 + ); 86 + }; 87 + 88 + export const ReaderEmpty = () => { 89 + return ( 90 + <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"> 91 + Nothing to read yet… <br /> 92 + Subscribe to publications and find their posts here! 93 + <Link href={"/discover"}> 94 + <ButtonPrimary className="mx-auto place-self-center"> 95 + <DiscoverSmall /> Discover Publications 96 + </ButtonPrimary> 97 + </Link> 98 + </div> 99 + ); 100 + };
+9
app/(home-pages)/reader/LocalContent.tsx
··· 1 + "use client"; 2 + 3 + export const LocalContent = () => { 4 + return ( 5 + <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"> 6 + Nothing here yet… 7 + </div> 8 + ); 9 + };