Skip to content

Commit

Permalink
Merge pull request #127 from Huilensolis/95-feature-add-infinite-scro…
Browse files Browse the repository at this point in the history
…ll-to-posts-grid

95 feature add infinite scroll to posts grid
  • Loading branch information
huilensolis authored Mar 14, 2024
2 parents d9a6380 + d25b052 commit ecf5fb2
Show file tree
Hide file tree
Showing 27 changed files with 704 additions and 361 deletions.
110 changes: 76 additions & 34 deletions src/app/(site)/app/components/feed/index.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,85 @@
import { getSuapabaseServerComponent } from "@/supabase/models/index.models";
"use client";

import { Heading } from "@/components/ui/typography/heading";
import { PostsGrid } from "@/components/feature/posts-grid";
import { Suspense } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { useEffect, useState } from "react";
import { useSupabase } from "@/hooks/use-supabase";
import { Database } from "@/supabase/types";
import { PostsGrid } from "@/components/feature/posts-grid/posts-grid.component";
import { PostsGridSkeleton } from "@/components/feature/posts-grid/components/posts-grid-skeleton";

export function Feed() {
const { supabase } = useSupabase();

const [posts, setPosts] = useState<
Database["public"]["Tables"]["posts"]["Row"][]
>([]);

const [error, setError] = useState<boolean>(false);

const [isLoading, setIsLoading] = useState<boolean>(true);
const [isFetching, setIsFetching] = useState<boolean>(false);

const [lastPostIndex, setLastPostIndex] = useState<number>(1);

useEffect(() => {
const controller = new AbortController();

try {
setIsLoading(true);
setIsFetching(true);

supabase
.from("posts")
.select("*")
.order("id", { ascending: false })
.limit(32)
.range(lastPostIndex, lastPostIndex + 32)
.abortSignal(controller.signal)
.then(({ data: posts, error }) => {
if (error) return;

if (!posts) return;

setPosts((prev) => [...prev, ...posts]);
});
} catch (error: unknown) {
if ((error as Error).name === "AbortError") return;

setError(true);
} finally {
setIsLoading(false);
setIsFetching(false);
}

export async function Feed() {
const supabase = await getSuapabaseServerComponent();
return () => {
controller.abort();
};

const { data: posts, error } = await supabase
.from("posts")
.select("*")
.order("id", { ascending: false })
.limit(24);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lastPostIndex]);

const doesPostsExist = posts && posts.length > 0 && !error;
function handleScroll() {
setLastPostIndex((prev) => prev + 32);
}

return (
<>
{doesPostsExist ? (
<main className="w-full h-full px-2">
<Suspense
fallback={
<ul className="break-inside-avoid gap-2 px-2 [column-count:3] md:[column-count:3]">
{Array(16)
.fill(" ")
.map((_, i) => (
<Skeleton key={i} className="w-full h-96 mb-2" />
))}
</ul>
}
>
<PostsGrid posts={posts} />
</Suspense>
</main>
) : (
<article className="flex items-center justify-center w-full max-h-96 py-16 text-center border-y border-neutral-300">
<Heading level={7}>Something wen wrong, reload the page</Heading>
</article>
<main className="w-full h-full flex flex-col gap-2">
{isLoading && <PostsGridSkeleton cuantity={32} />}
{!isLoading && !isFetching && (
<>
{!error ? (
<div>
<PostsGrid posts={posts} onFetchNewPosts={handleScroll} />
</div>
) : (
<article className="flex items-center justify-center w-full max-h-96 py-32 text-center border-y border-neutral-300">
<Heading level={10}>
Something wen wrong, please reload the page
</Heading>
</article>
)}
</>
)}
</>
</main>
);
}
2 changes: 1 addition & 1 deletion src/app/(site)/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function AppLayout({ children }: { children: ReactNode }) {
<div className="md:hidden w-full flex fixed left-0 bottom-0 z-50">
<MobileNavMenu />
</div>
<div className="h-full w-full min-h-screen border-x border-neutral-300 dark:border-cm-lighter-gray">
<div className="h-full px-2 w-full min-h-screen border-x border-neutral-300 dark:border-cm-lighter-gray">
{children}
</div>
<SyncTheme />
Expand Down
18 changes: 2 additions & 16 deletions src/app/(site)/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,8 @@ export default function AppPage() {

function DesktopLayout() {
return (
<main className="w-full h-full flex flex-col justify-start py-2">
<Suspense
fallback={
<ul className="w-full flex flex-wrap gap-2">
{Array(8)
.fill("")
.map((_, i) => (
<li key={i}>
<Skeleton className="w-full bg-red-500 rounded-md h-[500px]" />
</li>
))}
</ul>
}
>
<Feed />
</Suspense>
<main className="w-full h-full flex flex-col justify-start">
<Feed />
</main>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export function PostOptions({
return router.push("/app"); // Navigate to '/app' if no history
}
}
router.refresh();
} catch (e) {
console.log(e);
}
Expand Down
75 changes: 37 additions & 38 deletions src/app/(site)/app/post/[postid]/components/post/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,47 @@ export async function Post({
.single();

return (
<article className="w-full h-full flex lg:gap-4 gap-2 flex-col-reverse lg:flex-row-reverse items-start justify-end bg-neutral-200 dark:bg-neutral-900 overflow-hidden">
<header className="flex items-center justify-between w-96">
<section className="w-full flex flex-col items-start justify-center gap-4">
<PostOptions
post_id={post.id}
doesUserOwnPost={doesUserOwnPost}
image_url={post.asset_url}
/>
<h3 className="w-full text-neutral-800 dark:text-neutral-300 font-bold text-2xl">
{title}
</h3>
{postOwnerProfile && (
<Link
href={`/app/profile/${postOwnerProfile.username}`}
className="w-full"
>
<section className="flex flex-none gap-4 w-full items-center justify-start">
{postOwnerProfile.avatar_url ? (
<LazyImage
src={postOwnerProfile.avatar_url}
alt={post.title}
className="w-12 h-12 rounded-full object-cover object-center flex-0"
skeletonClassName="w-12 h-12 rounded-full"
width={48}
height={48}
/>
) : (
<div className="h-12 w-12 rounded-full bg-neutral-300" />
)}
<h3 className="text-neutral-800 dark:text-neutral-300 font-semibold text-xl">
{postOwnerProfile.name}
</h3>
</section>
</Link>
)}
</section>
<article className="w-full h-full flex flex-col-reverse gap-2 items-start justify-end bg-neutral-200 dark:bg-neutral-900">
<header className="flex flex-col items-start justify-center gap-2 w-max xl:min-w-64 min-w-full">
<h3 className="w-full text-neutral-800 dark:text-neutral-300 font-bold text-2xl">
{title}
</h3>
{postOwnerProfile && (
<Link
href={`/app/profile/${postOwnerProfile.username}`}
className="w-full"
>
<section className="flex gap-2 w-full justify-start items-center">
{postOwnerProfile.avatar_url ? (
<LazyImage
src={postOwnerProfile.avatar_url}
alt={post.title}
className="w-12 h-12 rounded-full object-cover object-center flex-0"
skeletonClassName="w-12 h-12 rounded-full"
width={48}
height={48}
/>
) : (
<div className="h-12 w-12 rounded-full bg-neutral-300" />
)}
<h3 className="text-neutral-800 dark:text-neutral-300 font-semibold text-xl">
{postOwnerProfile.name}
</h3>
</section>
</Link>
)}
<PostOptions
post_id={post.id}
doesUserOwnPost={doesUserOwnPost}
image_url={post.asset_url}
/>
</header>
<LazyImage
src={asset_url}
alt={title}
className="w-full h-full max-h-[100vh] lg:max-h-[80vh] object-cover object-center rounded-md"
skeletonClassName="w-full"
className="w-full h-full max-h-[80vh] object-cover object-center rounded-md"
skeletonClassName="w-full h-96 rounded-md"
containerClassname="w-full"
skeletonBgColor={post.asset_color || undefined}
/>
</article>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./recent-pots.component";
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use client";

import { PostsGridSkeleton } from "@/components/feature/posts-grid/components/posts-grid-skeleton";
import { PostsGrid } from "@/components/feature/posts-grid/posts-grid.component";
import { Heading } from "@/components/ui/typography/heading";
import { useSupabase } from "@/hooks/use-supabase";
import { Database } from "@/supabase/types";
import { useEffect, useState } from "react";

export function RecentPosts({
excludedPostId,
}: {
excludedPostId: Database["public"]["Tables"]["posts"]["Row"]["id"];
}) {
const [posts, setPosts] = useState<
Database["public"]["Tables"]["posts"]["Row"][]
>([]);

const [isLoading, setIsLoading] = useState<boolean>(true);
const [isFetching, setIsFetching] = useState<boolean>(false);

const [lastPostIndex, setLastPostIndex] = useState<number>(1);
const [error, setError] = useState<boolean>(false);

const { supabase } = useSupabase();

useEffect(() => {
const controller = new AbortController();

try {
setIsLoading(true);
setIsFetching(true);

supabase
.from("posts")
.select("*")
.order("created_at", { ascending: false })
.limit(32)
.range(lastPostIndex, lastPostIndex + 32)
.abortSignal(controller.signal)
.then(({ data: posts, error }) => {
if (error) return;

if (!posts) return;

setPosts((prev) => [
...prev,
...posts.filter((post) => post.id !== excludedPostId),
]);
});
} catch (error: unknown) {
if ((error as Error).name === "AbortError") return;

setError(true);
} finally {
setIsLoading(false);
setIsFetching(false);
}

return () => {
controller.abort();
};

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lastPostIndex]);

function handleScroll() {
if (isLoading || isFetching) return;

setLastPostIndex((prev) => prev + 32);
}

return (
<section>
{isLoading && <PostsGridSkeleton cuantity={32} />}
{!isLoading && !isFetching && (
<>
{!error ? (
<div>
<PostsGrid posts={posts} onFetchNewPosts={handleScroll} />
</div>
) : (
<article className="flex items-center justify-center w-full max-h-96 py-32 text-center border-y border-neutral-300">
<Heading level={10}>
Something wen wrong, please reload the page
</Heading>
</article>
)}
</>
)}
</section>
);
}
Loading

0 comments on commit ecf5fb2

Please sign in to comment.