diff --git a/app/(content)/(gaming)/games/page.tsx b/app/(content)/(gaming)/games/page.tsx index cacec504f4e0851e6f435fb3ff096d95b3d7f7b8..4ff601ba9e793611a00380291f9f29d1510f6ae6 100644 --- a/app/(content)/(gaming)/games/page.tsx +++ b/app/(content)/(gaming)/games/page.tsx @@ -1,22 +1,26 @@ import Sort from "@/components/filter-sort-games"; import { InfiniteScrollGames } from "@/components/infinity-scroll"; +import ScrollToTop from "@/components/scroll-to-top"; import SearchInput from "@/components/search-input"; -// renders a list of games infinitely (presumably) +// renders a list of games infinitely export default async function GamesPage() { return ( - <main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_240px]"> - <div className="grid"> - <div className="flex flex-col gap-10 items-center w-full"> - <SearchInput className="p-3 lg:w-2/3 2xl:w-1/3" /> + <> + <main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_240px]"> + <div className="grid"> + <div className="flex flex-col gap-10 items-center w-full"> + <SearchInput className="p-3 lg:w-2/3 2xl:w-1/3" /> + </div> + <InfiniteScrollGames /> </div> - <InfiniteScrollGames /> - </div> - <div className="hidden xl:block flex-col md:flex"> - <div className="sticky top-0"> + <div className="hidden xl:block flex-col md:flex"> <Sort /> </div> + </main> + <div className="fixed top-6 left-1/2 -ml-7"> + <ScrollToTop /> </div> - </main> + </> ) } \ No newline at end of file diff --git a/app/api/games/route.ts b/app/api/games/route.ts index f411fce9454181f205c9f0e4c39bb27a8be2d4e4..7ad46c19e55a8bc94cada8bad8a4d147e0dab992 100644 --- a/app/api/games/route.ts +++ b/app/api/games/route.ts @@ -30,12 +30,12 @@ export async function GET(req: NextRequest) { } const games = await getGames(page, - search ? search : '', + search ? search : undefined, category ? EGameCategory[category as keyof typeof EGameCategory] : undefined, genre ? EGameGenres[genre as keyof typeof EGameGenres] : undefined, filteredPlatforms, - sortby ? sortby : '', - order ? order : '' + sortby ? sortby : undefined, + order ? order : undefined ); return NextResponse.json(games); } catch (error) { diff --git a/components/filter-sort-games.tsx b/components/filter-sort-games.tsx index a229a73fdb5076c1a415d26b9d42d850f0c9ec03..b1c574699ac1f9f274389fc48b9fb84d7d591d92 100644 --- a/components/filter-sort-games.tsx +++ b/components/filter-sort-games.tsx @@ -2,7 +2,7 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Icons } from "./icons"; import { Button } from "./ui/button"; import { Card } from "./ui/card"; @@ -25,8 +25,8 @@ export default function Sort() { category: selectedCategory ? `category=${selectedCategory}` : '', genre: selectedGenre ? `genre=${selectedGenre}` : '', platform: selectedPlatform ? `platform=${selectedPlatform}` : '', - sortMethod: selectedSortMethod ? `sortby=${selectedSortMethod}` : '', - sortOrder: selectedSortOrder ? `order=${selectedSortOrder}` : '', + sortMethod: selectedSortMethod == 'total_rating_count' ? '' : `sortby=${selectedSortMethod}`, + sortOrder: selectedSortOrder == 'desc' ? '' : `order=${selectedSortOrder}`, }; const queryParamString = Object.values(urlParams) @@ -34,13 +34,9 @@ export default function Sort() { .map((param, index) => (index === 0 ? `?${param}` : `&${param}`)) .join(''); - const queryString = `${queryParamString ? `${queryParamString}` : ''}`; + const url = `${pathname}${queryParamString ? `${queryParamString}` : ''}`; - const url = `${pathname}${queryString ? `${queryString}` : ''}`; - - useEffect(() => { - router.push(url); - }, [router, url]); + router.replace(url); function toggleSortOrder() { const newSortOrder = selectedSortOrder === 'desc' ? 'asc' : 'desc'; @@ -53,6 +49,7 @@ export default function Sort() { setSelectedGenre(''); setSelectedPlatform(''); setSelectedSortMethod('total_rating_count'); + setSelectedSortOrder('desc'); } return ( diff --git a/components/icons.tsx b/components/icons.tsx index ca3cca6a684a943b5bedb3e5fbcd5d6d6ede68d4..e9a8c6b58bd07cfe18ac947bcb6b2c9a53d32257 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -2,6 +2,7 @@ import { AlertTriangle, ArrowDown, ArrowRight, + ArrowUpToLine, BellRing, Check, ChevronLeft, @@ -72,6 +73,7 @@ export const Icons: IconsType = { moon: Moon, // Dark Mode Toggle Nav arrowdown: ArrowDown, // Descending Sort heart: Heart, // Like Button + arrowupline: ArrowUpToLine, // Back to Top Button close: X, spinner: Loader2, chevronLeft: ChevronLeft, diff --git a/components/infinity-scroll.tsx b/components/infinity-scroll.tsx index d59b27ba67b705e043d44d96fe77a4a4fceb8840..b1651210f22eb1d933213067230fe8e34565582d 100644 --- a/components/infinity-scroll.tsx +++ b/components/infinity-scroll.tsx @@ -34,7 +34,7 @@ export function InfiniteScrollGames() { fetchNextPage, hasNextPage, } = useInfiniteQuery( - ['infiniteGames', params], + [params], async ({ pageParam = 1 }) => await fetch(`/api/games/?page=${pageParam}${params}`, ).then((result) => result.json() as Promise<IGame[]>), @@ -49,30 +49,34 @@ export function InfiniteScrollGames() { <Card className="p-6"> {status === 'error' ? - (<span>Error: {(error as Error).message}</span>) + (<span className="text-center"> + Uh oh... something went wrong + </span>) : (status === 'success' && ( <InfiniteScroll dataLength={data?.pages.length * 20} next={fetchNextPage} hasMore={hasNextPage ? true : false} - loader={<h4>Loading more...</h4>} + loader={ + <h1 className="text-center pt-6"> + <b>Trying to load more...</b> + </h1> + } endMessage={ - <p style={{ textAlign: 'center' }}> - <b>Yay! You have seen it all</b> - </p> + <h1 className="text-center pt-6"> + <b>Yay! You have seen it all!</b> + </h1> } > - <div className=""> - <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8"> - {data.pages.map((page, i) => ( - <Fragment key={i}> - {page.map((game: IGame) => ( - <Game id={game.id} name={game.name} cover={game.cover} key={game.id} /> - ))} - </Fragment> - ))} - </div> + <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8"> + {data.pages.map((page, i) => ( + <Fragment key={i}> + {page.map((game: IGame) => ( + <Game id={game.id} name={game.name} cover={game.cover} key={game.id} /> + ))} + </Fragment> + ))} </div> </InfiniteScroll> ))} diff --git a/components/scroll-to-top.tsx b/components/scroll-to-top.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bd045beee8eb8fe9a4da11f091f3be4c977c8542 --- /dev/null +++ b/components/scroll-to-top.tsx @@ -0,0 +1,37 @@ +"use client" + +import { cn } from "@/lib/utils"; +import { useEffect, useState } from "react"; +import { Icons } from "./icons"; +import { Button } from "./ui/button"; + +export default function ScrollToTop() { + const [isVisible, setIsVisible] = useState(false); + + const toggleVisibility = () => { + if (window.pageYOffset > 300) { + setIsVisible(true); + } else { + setIsVisible(false); + } + }; + + const scrollToTop = () => { + window.scrollTo({ + top: 0, + behavior: "smooth" + }); + }; + + useEffect(() => { + window.addEventListener("scroll", toggleVisibility); + return () => window.removeEventListener("scroll", toggleVisibility); + }, []); + + return ( + <Button size="lg" onClick={scrollToTop} + className={cn(isVisible ? "block" : "hidden", "")}> + <Icons.arrowupline className="h-3 w-3" aria-hidden="true" /> + </Button> + ); +} \ No newline at end of file diff --git a/lib/igdb.ts b/lib/igdb.ts index c33959864889bb0cf7714af42a5ec1f00cf5074a..d48e98edd15371363135cff7fa0ffab6a36718e7 100644 --- a/lib/igdb.ts +++ b/lib/igdb.ts @@ -28,7 +28,7 @@ async function getToken(): Promise<IAuth> { } // fetches the top 200 games with a rating of 96 or higher -export async function getGames(page = 1, search?: string, category?: number, genre?: number, platform?: number[], sortby?: string, order?: string): Promise<IGame[]> { +export async function getGames(page = 1, search?: string, category?: number, genre?: number, platform?: number[], sortby = "total_rating_count", order = "desc"): Promise<IGame[]> { const auth = await getToken(); const url = new URL(`${IGDB_BASE_URL}/games`); @@ -42,7 +42,7 @@ export async function getGames(page = 1, search?: string, category?: number, gen }, body: `fields name, cover.image_id; limit ${limit}; offset ${offset}; - sort ${sortby ? sortby : "total_rating_count"} ${order ? order : "desc"}; + sort ${sortby} ${order}; where total_rating_count > 3 & cover != null & total_rating != null & rating != null & age_ratings != null & name ~ *"${search ? search : ""}"*