Skip to content

Commit

Permalink
update feed scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
thebkht committed Oct 26, 2023
1 parent 7104796 commit 54d418a
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 253 deletions.
164 changes: 19 additions & 145 deletions app/(Main)/feed/page.tsx
Original file line number Diff line number Diff line change
@@ -1,161 +1,35 @@
"use client";
import { getSessionUser } from '@/components/get-session-user';
import { useSession } from 'next-auth/react';
import { useState, useEffect, useRef, Key, Suspense, useCallback } from 'react'
import { Icons } from '@/components/icon';
import PopularPosts from '@/components/feed/popular-posts';
import { usePathname, useRouter } from 'next/navigation';
import FeaturedDev from '@/components/feed/featured/featured-dev';
import { Skeleton } from '@/components/ui/skeleton';
import FeedComponent from '@/components/feed/feed';
import UserExplore from '@/components/explore/user/details';
import ExploreTabs from '@/components/explore/navbar/navbar';
import { revalidatePath } from 'next/cache';
import { fetchFeed } from '@/components/feed/get-feed';
import InfinitiveScrollFeed from '@/components/feed/feed';

export default function Feed() {
const { status, data: session } = useSession()
const sessionUser = getSessionUser()
const [feed, setFeed] = useState<any | null>([])
const [page, setPage] = useState(0)
const [fetching, setFetching] = useState<boolean>(true)
const [loading, setLoading] = useState(true)
const [feedLength, setFeedLength] = useState<number>(0)
const [popularPosts, setPopularPosts] = useState<any | null>([])
const [topUsers, setTopUsers] = useState<any | null>([])
const sentinelRef = useRef(null)
const route = useRouter()

const pathname = usePathname()

const fetchFeed = useCallback(async () => {
if (status !== "unauthenticated") {
const user = (await sessionUser)?.id
try {
const response = await fetch(`/api/feed?user=${user}&page=${page}`, { next: { revalidate: 3600 } });
if (!response.ok) {
throw new Error(`Fetch failed with status: ${response.status}`);
}
const data = await response.json();

setFeed((prevFeed: any) => [...prevFeed, ...data.feed]);
setFeedLength(data.feedLength);
setFetching(false);
setLoading(false);
} catch (error) {
console.error('Error fetching feed:', error);
setLoading(false);
setFetching(false);
}
} else {
setFetching(false)
route.push('/')
}
}, [page])

const fetchPopularPosts = useCallback(async () => {
if(status === "authenticated") {
try {
const response = await fetch(`/api/posts/popular`, { next: { revalidate: 3600 } });
if (!response.ok) {
throw new Error(`Fetch failed with status: ${response.status}`);
}
const data = await response.json();
setPopularPosts(data.popular);
} catch (error) {
console.error('Error fetching popular posts:', error);
}
}
}, [status])

const fetchTopUsers = useCallback(async () => {
if(status === "authenticated") {
try {
const response = await fetch(`/api/users/top?user=${(await sessionUser).id}`, {
method: "GET",
next: { revalidate: 3600 }
});
if (!response.ok) {
throw new Error(`Fetch failed with status: ${response.status}`);
}
const data = await response.json();
setTopUsers(data.topUsers);
} catch (error) {
console.error('Error fetching top users:', error);
}
}
}, [status])

useEffect(() => {
if(status === "authenticated") {
fetchPopularPosts()
fetchTopUsers()
}
}, [status])

useEffect(() => {
console.log('Feed length:', feedLength, 'Feed:', feed.length)
if (feedLength >= 0){
if (feed.length <= feedLength) {
fetchFeed()
}
}
}, [page])

useEffect(() => {
if (feed.length < feedLength) {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting && !loading && feed.length < feedLength) {
setPage(prevPage => prevPage + 1)
}
},
{ rootMargin: '0px 0px 100% 0px' }
)

if (sentinelRef.current) {
observer.observe(sentinelRef.current)
}

return () => {
if (sentinelRef.current) {
observer.unobserve(sentinelRef.current)
}
}
}
}, [loading])

if (status !== "authenticated") {
return null
}
export default async function Feed() {
const feed = await fetchFeed({ page: 0 });

return (
<>
<main className="flex flex-col items-center justify-between feed ">
<ExploreTabs />
<div className="md:flex lg:flex-nowrap flex-wrap md:mx-[-16px] w-full gap-8 my-8">
<UserExplore className='md:w-full lg:w-1/3 pt-4 lg:pt-0 h-fit md:my-4' />
<Suspense fallback={<Skeleton className='w-full h-28' />}>
<div className="md:my-4 md:w-[12.5%] lg:w-2/3">
{
fetching && (
<div className="feed__list_loadmore my-8 h-max">
<Icons.spinner className="h-10 animate-spin mr-2" /> Loading...
</div>
)
}
<FeedComponent feed={feed} isLoaded={!fetching}>
<div ref={sentinelRef} />
</FeedComponent>
<div className="md:flex lg:flex-nowrap flex-wrap md:mx-[-16px] w-full gap-8 my-8">
<UserExplore className='md:w-full lg:w-1/3 pt-4 lg:pt-0 md:my-4 h-fit' />
<div className="md:my-4 md:w-[12.5%] lg:w-2/3">
{!feed ? (
<div className="feed__list_loadmore my-8 h-max">
<Icons.spinner className="h-10 animate-spin mr-2" /> Loading...
</div>
</Suspense>
{/* Popular posts, blogs, and tags */}
<div className="md:my-4 md:w-1/4 lg:w-1/3 space-y-6">
<PopularPosts data={popularPosts} isloaded={!fetching} />
<FeaturedDev data={topUsers} isloaded={!fetching} />
</div>

) : (
<InfinitiveScrollFeed initialFeed={feed} />
)}
</div>
</main>
<div className="md:my-4 md:w-1/4 lg:w-1/3 space-y-6">
<PopularPosts />
{/* <FeaturedDev data={topUsers} isloaded={!fetching} /> */}
</div>
</div>
</main>
</>
)
}
60 changes: 41 additions & 19 deletions components/feed/feed.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
"use client";
import React, { Suspense } from "react";
import { useSession } from "next-auth/react";
import { Icons } from "../icon";
import { useEffect, useState } from 'react'
import { useInView } from 'react-intersection-observer'
import { Skeleton } from "../ui/skeleton";
import FeedPostCard from "../blog/feed-post-card";
import { fetchFeed } from './get-feed';
import { Icons } from '../icon';
import { getSessionUser } from '../get-session-user';

export default function FeedComponent({ feed, children, isLoaded }: { feed: any; children?: React.ReactNode; isLoaded: boolean; }) {
const { status, data: session } = useSession();
export default function InfinitiveScrollFeed({ initialFeed }: { initialFeed: any }) {
const [feed, setFeed] = useState<Array<any>>(initialFeed)
const [page, setPage] = useState<number>(0)
const [ref, inView] = useInView()

return (
<div className="feed__list">
{
async function loadMoreFeed() {
console.log("Loading more feed")
const next = page + 1
console.log("Next page", next)
const fetchedFeed = await fetch(`/api/feed?page=${next}&user=${(await getSessionUser()).id}`).then(res => res.json())
if (fetchedFeed?.feed.length) {
setPage(next)
setFeed(prev => [...(prev?.length ? prev : []), ...fetchedFeed.feed])
}
}

useEffect(() => {
if (inView) {
loadMoreFeed()
}
}, [inView])

return (
<div className="feed__list">
{/* {
isLoaded && feed.length === 0 && (
<div className="w-full max-h-screen my-auto flex justify-center items-center bg-background">
<div className="flex flex-col items-center justify-center space-y-4">
Expand All @@ -19,29 +40,30 @@ export default function FeedComponent({ feed, children, isLoaded }: { feed: any;
</div>
</div>
)
}
} */}

<div className="feed__list_item">
{feed.map((post: any) => (
<Suspense fallback={<Skeleton />} key={post.id}>
<FeedPostCard
<FeedPostCard
key={post.id}
id={post.id as string}
title={post.title}
content={post.subtitle}
date={post.createdAt}
author={post.author}
thumbnail={post.cover}
likes={post.likes}
comments={post.comments || "0"}
views={post.views} authorid={post.author?.userid} url={`/${post.author?.username}/${post.url}`} />
</Suspense>
likes={post._count.likes || "0"}
comments={post._count.comments || "0"}
views={post.views}
authorid={post.author?.userid}
url={`/${post.author?.username}/${post.url}`}
/>
))}


<div className="feed__list_loadmore my-8 h-max" ref={ref}>
<Icons.spinner className="h-10 animate-spin mr-2" /> Loading...
</div>
</div>

{children}
</div>
)
)
}
49 changes: 49 additions & 0 deletions components/feed/get-feed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import postgres from "@/lib/postgres";
import { getSessionUser } from "../get-session-user";

export const fetchFeed = async ({page = 0}: {page?: number | undefined}) => {
const user_id = await getSessionUser().then((user) => user?.id);
try{
const userFollowings = await postgres.follow.findMany({
select: {
followingId: true,
},
where: {
followerId: user_id,
},
});
const feed = await postgres.post.findMany({
where: {
authorId: {
in: userFollowings.map((user) => user.followingId),
},
},
orderBy: {
createdAt: "desc",
},
take: 5,
skip: page * 5,
include: {
author: {
include: {
Followers: true,
Followings: true,
},
},
_count: {
select: {
likes: true,
comments: true,
savedUsers: true,
},
},
tags: true,
},
})
await new Promise(resolve => setTimeout(resolve, 750))

return JSON.parse(JSON.stringify(feed));
} catch (error) {
return { error }
}
}
29 changes: 29 additions & 0 deletions components/feed/get-posts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import postgres from "@/lib/postgres";

export const fetchPosts = async () => {
try {
const popular = await postgres.post.findMany({
orderBy: {
views: "desc",
},
take: 5,
include: {
author: true,
_count: {
select: {
likes: true,
comments: true,
savedUsers: true,
},
},
tags: true,
},
});

await new Promise((resolve) => setTimeout(resolve, 750));

return JSON.parse(JSON.stringify(popular));
} catch (error) {
return { error };
}
}
Loading

0 comments on commit 54d418a

Please sign in to comment.