diff --git a/src/app/(site)/app/components/feed/index.tsx b/src/app/(site)/app/components/feed/index.tsx index c33a748..f65b5d3 100644 --- a/src/app/(site)/app/components/feed/index.tsx +++ b/src/app/(site)/app/components/feed/index.tsx @@ -1,85 +1,29 @@ "use client"; - -import { Heading } from "@/components/ui/typography/heading"; -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(false); - - const [isLoading, setIsLoading] = useState(true); - const [isFetching, setIsFetching] = useState(false); - - const [lastPostIndex, setLastPostIndex] = useState(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); - } - - return () => { - controller.abort(); - }; - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [lastPostIndex]); - - function handleScroll() { - setLastPostIndex((prev) => prev + 32); + async function fetchNewPosts({ + currentPage, + signal, + pageSize, + }: { + currentPage: number; + signal: AbortSignal; + pageSize: number; + }) { + return await supabase + .from("posts") + .select("*") + .range(currentPage, currentPage + pageSize) + .abortSignal(signal); } return (
- {isLoading && } - {!isLoading && !isFetching && ( - <> - {!error ? ( -
- -
- ) : ( -
- - Something wen wrong, please reload the page - -
- )} - - )} +
); } diff --git a/src/app/(site)/app/page.tsx b/src/app/(site)/app/page.tsx index 270338d..62d58b4 100644 --- a/src/app/(site)/app/page.tsx +++ b/src/app/(site)/app/page.tsx @@ -1,7 +1,5 @@ import { Feed } from "./components/feed"; import { protectRouteFromUnauthUsers } from "@/utils/auth/server-side-validations"; -import { Suspense } from "react"; -import { Skeleton } from "@/components/ui/skeleton"; export const dynamic = "force-dynamic"; @@ -30,21 +28,7 @@ function DesktopLayout() { function MobileLayout() { return (
- - {Array(6) - .fill("") - .map((_, i) => ( -
  • - -
  • - ))} - - } - > - -
    +
    ); } diff --git a/src/app/(site)/app/post/[postid]/components/post/index.tsx b/src/app/(site)/app/post/[postid]/components/post/index.tsx index b09c154..54f4077 100644 --- a/src/app/(site)/app/post/[postid]/components/post/index.tsx +++ b/src/app/(site)/app/post/[postid]/components/post/index.tsx @@ -60,7 +60,7 @@ export async function Post({ ([]); - - const [isLoading, setIsLoading] = useState(true); - const [isFetching, setIsFetching] = useState(false); - - const [lastPostIndex, setLastPostIndex] = useState(1); - const [error, setError] = useState(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); + async function fetchNewPosts({ + currentPage, + signal, + pageSize, + }: { + currentPage: number; + signal: AbortSignal; + pageSize: number; + }) { + return supabase + .from("posts") + .select("*") + .neq("id", excludedPostId) // exclude the posts that has the id equal to excludedPostId + .order("title", { ascending: false }) + .range(currentPage, currentPage + pageSize) + .abortSignal(signal); } - return ( -
    - {isLoading && } - {!isLoading && !isFetching && ( - <> - {!error ? ( -
    - -
    - ) : ( -
    - - Something wen wrong, please reload the page - -
    - )} - - )} -
    - ); + return ; } diff --git a/src/app/(site)/app/profile/[username]/components/user-posts/user-posts.component.tsx b/src/app/(site)/app/profile/[username]/components/user-posts/user-posts.component.tsx index d2e0caf..1498b18 100644 --- a/src/app/(site)/app/profile/[username]/components/user-posts/user-posts.component.tsx +++ b/src/app/(site)/app/profile/[username]/components/user-posts/user-posts.component.tsx @@ -1,92 +1,32 @@ "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 UserPosts({ profileId }: { profileId: string }) { const { supabase } = useSupabase(); - const [posts, setPosts] = useState< - Database["public"]["Tables"]["posts"]["Row"][] - >([]); - - const [error, setError] = useState(false); - - const [isLoading, setIsLoading] = useState(true); - const [isFetching, setIsFetching] = useState(false); - - const [lastPostIndex, setLastPostIndex] = useState(1); - - const [userHasNoMorePosts, setUserHasNoMorePosts] = useState(false); - - useEffect(() => { - if (userHasNoMorePosts) return; - - const controller = new AbortController(); - - try { - setIsLoading(true); - setIsFetching(true); - - supabase - .from("posts") - .select("*") - .eq("profile_id", profileId) - .order("id", { ascending: false }) - .limit(32) - .range(lastPostIndex, lastPostIndex + 32) - .abortSignal(controller.signal) - .then(({ data: posts, error }) => { - if (error) return; - - if (!posts) return; - - if (posts.length === 0) setUserHasNoMorePosts(true); - - setPosts((prev) => [...prev, ...posts]); - }); - } 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() { - setLastPostIndex((prev) => prev + 32); + async function fetchNewPosts({ + currentPage, + signal, + pageSize, + }: { + currentPage: number; + signal: AbortSignal; + pageSize: number; + }) { + return await supabase + .from("posts") + .select("*") + .eq("profile_id", profileId) + .order("created_at", { ascending: false }) + .range(currentPage, currentPage + pageSize) + .abortSignal(signal); } return (
    - {isLoading && } - {!isLoading && !isFetching && ( - <> - {!error ? ( -
    - -
    - ) : ( -
    - - Something wen wrong, please reload the page - -
    - )} - - )} +
    ); } diff --git a/src/app/(site)/app/search/components/searched-posts-grid/searched-posts-grid.component.tsx b/src/app/(site)/app/search/components/searched-posts-grid/searched-posts-grid.component.tsx index b65d39f..83c8aae 100644 --- a/src/app/(site)/app/search/components/searched-posts-grid/searched-posts-grid.component.tsx +++ b/src/app/(site)/app/search/components/searched-posts-grid/searched-posts-grid.component.tsx @@ -1,121 +1,34 @@ "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 SearchedPostsGrid({ searchValue }: { searchValue: string }) { - const { supabase } = useSupabase(); - - const [posts, setPosts] = useState< - Database["public"]["Tables"]["posts"]["Row"][] - >([]); - - const [error, setError] = useState(false); - - const [isLoading, setIsLoading] = useState(true); - const [isFetching, setIsFetching] = useState(false); - - const [lastPostIndex, setLastPostIndex] = useState(1); - - const [notFound, setNotFound] = useState(false); - - function getAndSetPosts({ signal }: { signal: AbortSignal }) { - try { - setIsLoading(true); - setIsFetching(true); - - setNotFound(false); - - supabase - .from("posts") - .select("*") - .like("title", `%${searchValue}%`) - .order("created_at", { ascending: false }) - .range(lastPostIndex, lastPostIndex + 32) - .abortSignal(signal) - .limit(32) - .then(({ data: posts, error }) => { - if (error) return; - - if (!posts) return; - - if (posts.length === 0) { - setNotFound(true); - return; - } - setNotFound(false); - - setPosts((prev) => [...prev, ...posts]); - }); - } catch (error: unknown) { - if ((error as Error).name === "AbortError") return; + if (!searchValue || searchValue.trim().length === 0) searchValue = "painting"; - setError(true); - } finally { - setIsLoading(false); - setIsFetching(false); - } - } - - useEffect(() => { - const controller = new AbortController(); - - getAndSetPosts({ signal: controller.signal }); - - return () => { - controller.abort(); - }; - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [lastPostIndex]); - - useEffect(() => { - const controller = new AbortController(); - - if (isLoading || isFetching) return; - - setLastPostIndex(1); - - setPosts([]); - - getAndSetPosts({ signal: controller.signal }); - - return () => { - controller.abort(); - }; - }, [searchValue]); + const { supabase } = useSupabase(); - function handleScroll() { - setLastPostIndex((prev) => prev + 32); + async function fetchNewPosts({ + signal, + currentPage, + pageSize, + }: { + currentPage: number; + signal: AbortSignal; + pageSize: number; + }) { + return await supabase + .from("posts") + .select("*") + .ilike("title", `%${searchValue}%`) + .order("created_at", { ascending: false }) + .range(currentPage, currentPage + pageSize) + .abortSignal(signal); } return (
    - {isLoading && } - {!isLoading && !isFetching && ( - <> - {!error ? ( -
    - -
    - ) : ( -
    - - Something wen wrong, please reload the page - -
    - )} - - )} - {notFound && posts.length === 0 && ( -
    - 404 not found -
    - )} +
    ); } diff --git a/src/app/(site)/app/search/page.tsx b/src/app/(site)/app/search/page.tsx index e068359..a593559 100644 --- a/src/app/(site)/app/search/page.tsx +++ b/src/app/(site)/app/search/page.tsx @@ -1,16 +1,16 @@ import { SearchForm } from "./components/SearchForm/SearchForm"; import { SearchedPostsGrid } from "./components/searched-posts-grid"; -interface SearchParams { +type SearchParams = { search_query?: string; -} +}; -interface Props { +export default async function SearchPage({ + searchParams, +}: { searchParams: SearchParams; -} - -const SearchPage: React.FC = async ({ searchParams }) => { - const searchValue = searchParams.search_query ?? "painting"; +}) { + const searchValue = searchParams.search_query ?? " "; return (
    @@ -19,6 +19,4 @@ const SearchPage: React.FC = async ({ searchParams }) => {
    ); -}; - -export default SearchPage; +} diff --git a/src/components/feature/lazy-image/index.tsx b/src/components/feature/lazy-image/index.tsx index 5bab7b4..273aaec 100644 --- a/src/components/feature/lazy-image/index.tsx +++ b/src/components/feature/lazy-image/index.tsx @@ -59,7 +59,8 @@ export function LazyImage({ loading="lazy" className={loading ? "opacity-0" : className} ref={imageRef} - style={{ height, width }} + width={width} + height={height} /> )} {loading && !error && ( diff --git a/src/components/feature/posts-grid/components/posts-grid-container/posts-grid-container.component.tsx b/src/components/feature/posts-grid/components/posts-grid-container/posts-grid-container.component.tsx index 4276768..7b2c550 100644 --- a/src/components/feature/posts-grid/components/posts-grid-container/posts-grid-container.component.tsx +++ b/src/components/feature/posts-grid/components/posts-grid-container/posts-grid-container.component.tsx @@ -10,7 +10,7 @@ type TRef = HTMLUListElement; const PostsGridContainer = forwardRef(({ children }, ref) => { return (
      {children} diff --git a/src/components/feature/posts-grid/components/posts-grid-row/posts-grid-row.component.tsx b/src/components/feature/posts-grid/components/posts-grid-row/posts-grid-row.component.tsx index a920fea..e3435f7 100644 --- a/src/components/feature/posts-grid/components/posts-grid-row/posts-grid-row.component.tsx +++ b/src/components/feature/posts-grid/components/posts-grid-row/posts-grid-row.component.tsx @@ -14,9 +14,12 @@ export function PostsGridRow({ }) { const imageHeight = (post.asset_height * columnWidth) / post.asset_width; return ( -
    • +
    • diff --git a/src/components/feature/posts-grid/components/posts-grid-skeleton/posts-grid-skeleton.component.tsx b/src/components/feature/posts-grid/components/posts-grid-skeleton/posts-grid-skeleton.component.tsx index 50ab527..9d3bb10 100644 --- a/src/components/feature/posts-grid/components/posts-grid-skeleton/posts-grid-skeleton.component.tsx +++ b/src/components/feature/posts-grid/components/posts-grid-skeleton/posts-grid-skeleton.component.tsx @@ -1,14 +1,13 @@ import { Skeleton } from "@/components/ui/skeleton"; -import { PostsGridContainer } from "../posts-grid-container"; export function PostsGridSkeleton({ cuantity = 16 }: { cuantity?: number }) { return ( - + <> {Array(cuantity) .fill(" ") .map((_, i) => ( - + ))} - + ); } diff --git a/src/components/feature/posts-grid/posts-grid.component.tsx b/src/components/feature/posts-grid/posts-grid.component.tsx index 4ba9fe5..ce17e76 100644 --- a/src/components/feature/posts-grid/posts-grid.component.tsx +++ b/src/components/feature/posts-grid/posts-grid.component.tsx @@ -1,35 +1,65 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { TPostsGridItem } from "./posts-grid.models"; import { PostsGridRow } from "./components/posts-grid-row"; import { PostsGridContainer } from "./components/posts-grid-container"; +import { type Database } from "@/supabase/types"; +import { type PostgrestSingleResponse } from "@supabase/supabase-js"; + +type TPost = Database["public"]["Tables"]["posts"]["Row"]; + +type TOnFetchNewPostsProps = { + currentPage: number; + signal: AbortSignal; + pageSize: number; +}; + +const PAGE_SIZE = 30; + +type TOnFetchNewPosts = ({ + pageSize, + signal, + currentPage, +}: TOnFetchNewPostsProps) => Promise>; export function PostsGrid({ - posts, onFetchNewPosts, }: { - posts: TPostsGridItem[]; - onFetchNewPosts: () => void; + onFetchNewPosts: TOnFetchNewPosts; }) { const [columnWidth, setColumnWidth] = useState(null); - const containerRef = useRef(null); + const containerRef = useRef(null); + + const setContainerRef = useCallback((node: HTMLUListElement) => { + if (!node) return; + + calculateColumnWidth(node.offsetWidth); + containerRef.current = node; + return; + }, []); + + function calculateColumnWidth(containerWidth: number) { + if (typeof window === "undefined") return; + + if (containerWidth <= 0 || !containerWidth) return; + + const columnCount = window.innerWidth > 1024 ? 3 : 2; + setColumnWidth((containerWidth - 8 * 2) / columnCount); + } useEffect(() => { - function calculateColumnWidth() { - if (!containerRef.current) return; + if (typeof window === "undefined") return; - const containerWidth = containerRef.current.offsetWidth; - if (containerWidth <= 0 || !containerWidth) return; + console.log("running use Effect"); - const columnCount = window.innerWidth > 1024 ? 3 : 2; - setColumnWidth((containerWidth - 8 * 2) / columnCount); + function eventListenerForWidthResize() { + if (containerRef.current === null) return; + calculateColumnWidth(containerRef.current.offsetWidth); } - calculateColumnWidth(); - - window.addEventListener("resize", calculateColumnWidth); + window.addEventListener("resize", eventListenerForWidthResize); - return () => window.removeEventListener("resize", calculateColumnWidth); + return () => + window.removeEventListener("resize", eventListenerForWidthResize); }, []); const [lastElementRef, setLastElementRef] = useState( @@ -51,7 +81,7 @@ export function PostsGrid({ threshold: 1.0, }; - const observer = new IntersectionObserver(onFetchNewPosts, observerOptions); + const observer = new IntersectionObserver(handleScroll, observerOptions); observer.observe(lastElementRef); @@ -62,22 +92,80 @@ export function PostsGrid({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [lastElementRef]); + const [posts, setPosts] = useState([]); + const [page, setPage] = useState(1); + const [isLastPage, setIsLastPage] = useState(false); + + const [isFetching, setIsFetching] = useState(false); + + function handleScroll() { + setPage((prev) => prev + 32); + } + + useEffect(() => { + const controller = new AbortController(); + + async function fetchNewPosts() { + try { + setIsFetching(true); + const { data: newPosts, error } = await onFetchNewPosts({ + signal: controller.signal, + currentPage: page, + pageSize: PAGE_SIZE, + }); + + if (error) { + if (error instanceof Error && error.message === "AbortError") { + // error is caused by an abortcontroller abort signal + return; + } + if (error.code === "20") { + return; // this means there is been throwed an error because the request has been aborted + } + throw new Error("eror fetching new posts"); + } + + if (newPosts.length === 0) { + setIsLastPage(true); + return; + } + + setPosts((prev) => [...prev, ...newPosts]); + } catch (error) { + console.log(error); + } finally { + setIsFetching(false); + } + } + + if (isFetching) return; + if (isLastPage) return; + + fetchNewPosts(); + + return () => { + controller.abort(); + }; + }, [page]); + return ( - - {posts.length > 0 && containerRef.current && ( - <> - {posts.map((post, i) => ( - - ))} -
      - - )} - + <> + + {posts.length > 0 && columnWidth && ( + <> + {posts.map((post, i) => ( + + ))} +
      + + )} + + ); } diff --git a/src/components/feature/posts-grid/posts-grid.models.ts b/src/components/feature/posts-grid/posts-grid.models.ts deleted file mode 100644 index dcaec5b..0000000 --- a/src/components/feature/posts-grid/posts-grid.models.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Database } from "@/supabase/types"; - -export type TPostsGridItem = Database["public"]["Tables"]["posts"]["Row"];