Skip to content
Snippets Groups Projects
Commit 13595cf5 authored by Yusuf Akgül's avatar Yusuf Akgül :hatching_chick:
Browse files

Merge branch 'feat.Search' into 'main'

search functionality working

See merge request !7
parents 30033916 2887a678
No related branches found
No related tags found
1 merge request!7search functionality working
Pipeline #34717 passed
import { InfiniteScrollGames } from "@/components/InfiniteScroll"; import { InfiniteScrollGames } from "@/components/InfiniteScroll";
import Search from "@/components/search-games"; import SearchInput from "@/components/search-input";
import Sort from "@/components/sort-games"; import Sort from "@/components/sort-games";
// renders a list of games infinitely (presumably) // renders a list of games infinitely (presumably)
...@@ -8,7 +8,7 @@ export default async function GamesPage() { ...@@ -8,7 +8,7 @@ export default async function GamesPage() {
<main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_240px]"> <main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_240px]">
<div className="grid"> <div className="grid">
<div className="flex flex-col gap-10 items-center w-full"> <div className="flex flex-col gap-10 items-center w-full">
<Search className="p-3 lg:w-2/3 2xl:w-1/3" /> <SearchInput className="p-3 lg:w-2/3 2xl:w-1/3" />
</div> </div>
<InfiniteScrollGames /> <InfiniteScrollGames />
</div> </div>
......
...@@ -11,7 +11,7 @@ export default async function ContentLayout({ ...@@ -11,7 +11,7 @@ export default async function ContentLayout({
}: DashboardLayoutProps) { }: DashboardLayoutProps) {
return ( return (
<div className="flex min-h-screen flex-col"> <div className="flex min-h-screen flex-col">
<div className="mx-6 my-6 flex-1 md:grid md:grid-cols-[220px_1fr] md:gap-6 lg:grid-cols-[240px_1fr] lg:gap-10"> <div className="flex-1 md:grid md:grid-cols-[220px_1fr] md:gap-6 lg:grid-cols-[240px_1fr] lg:gap-10">
<aside className="hidden w-[200px] flex-col md:flex"> <aside className="hidden w-[200px] flex-col md:flex">
<div className="sticky top-0"> <div className="sticky top-0">
<DashboardNav items={dashboardConfig.sidebarNav} /> <DashboardNav items={dashboardConfig.sidebarNav} />
......
...@@ -4,7 +4,10 @@ import { NextRequest, NextResponse } from "next/server"; ...@@ -4,7 +4,10 @@ import { NextRequest, NextResponse } from "next/server";
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
const p = req.nextUrl.searchParams; const p = req.nextUrl.searchParams;
try { try {
const games = await getGames(p.get('page') ? parseInt(p.get('page') as string) : undefined); const page = parseInt(p.get('page') as string)
const search = p.get('search')
const games = await getGames(page, search ? search : '');
return NextResponse.json(games); return NextResponse.json(games);
} catch (error) { } catch (error) {
return NextResponse.json(error, { status: 500 }); return NextResponse.json(error, { status: 500 });
......
...@@ -27,7 +27,9 @@ export default function RootLayout({ ...@@ -27,7 +27,9 @@ export default function RootLayout({
<Suspense fallback={<SiteLoad />}> <Suspense fallback={<SiteLoad />}>
<ClerkProvider> <ClerkProvider>
<Providers> <Providers>
{children} <div className="mx-32 my-6">
{children}
</div>
</Providers> </Providers>
</ClerkProvider> </ClerkProvider>
</Suspense> </Suspense>
......
...@@ -4,10 +4,16 @@ import Game from "@/components/Game"; ...@@ -4,10 +4,16 @@ import Game from "@/components/Game";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { IGame } from "@/types/igdb-types"; import { IGame } from "@/types/igdb-types";
import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteQuery } from "@tanstack/react-query";
import { useSearchParams } from "next/navigation";
import { Fragment } from "react"; import { Fragment } from "react";
import InfiniteScroll from "react-infinite-scroll-component"; import InfiniteScroll from "react-infinite-scroll-component";
export function InfiniteScrollGames() { export function InfiniteScrollGames() {
const search = useSearchParams()
const searchQuery = search.get('search');
const searchURL = searchQuery ? `&search=${searchQuery}` : "";
const { const {
status, status,
data, data,
...@@ -15,10 +21,10 @@ export function InfiniteScrollGames() { ...@@ -15,10 +21,10 @@ export function InfiniteScrollGames() {
fetchNextPage, fetchNextPage,
hasNextPage, hasNextPage,
} = useInfiniteQuery( } = useInfiniteQuery(
['infiniteGames'], ['infiniteGames', searchQuery],
async ({ pageParam = 1 }) => async ({ pageParam = 1 }) =>
await fetch(`/api/games/?page=${pageParam}`, await fetch(`/api/games/?page=${pageParam}` + searchURL,
{ cache: 'force-cache' } { cache: 'no-cache' }
).then((result) => result.json() as Promise<IGame[]>), ).then((result) => result.json() as Promise<IGame[]>),
{ {
getNextPageParam: (lastPage, pages) => { getNextPageParam: (lastPage, pages) => {
...@@ -46,7 +52,7 @@ export function InfiniteScrollGames() { ...@@ -46,7 +52,7 @@ export function InfiniteScrollGames() {
} }
> >
<div className=""> <div className="">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4 lg:gap-8"> <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) => ( {data.pages.map((page, i) => (
<Fragment key={i}> <Fragment key={i}>
{page.map((game: IGame) => ( {page.map((game: IGame) => (
......
...@@ -42,7 +42,7 @@ export default function DashboardNav({ items }: DashboardNavProps) { ...@@ -42,7 +42,7 @@ export default function DashboardNav({ items }: DashboardNavProps) {
<Link key={index} href={item.disabled ? "/" : item.href} className={index == 6 ? "mt-10" : ""}> <Link key={index} href={item.disabled ? "/" : item.href} className={index == 6 ? "mt-10" : ""}>
<span <span
className={cn( className={cn(
"group flex items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground", "group flex items-center rounded-md px-3 py-2 font-medium hover:bg-accent hover:text-accent-foreground",
path === item.href ? "bg-accent" : "transparent", path === item.href ? "bg-accent" : "transparent",
item.disabled && "cursor-not-allowed opacity-80" item.disabled && "cursor-not-allowed opacity-80"
)} )}
......
"use client" "use client"
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { usePathname, useRouter } from 'next/navigation';
import { useState } from 'react'; import { useState } from 'react';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { Icons } from './ui/icons'; import { Icons } from './ui/icons';
import { Input } from './ui/input'; import { Input } from './ui/input';
import { toast } from './ui/use-toast';
interface DocsSearchProps extends React.HTMLAttributes<HTMLFormElement> { } interface DocsSearchProps extends React.HTMLAttributes<HTMLFormElement> { }
export default function Search({ className, ...props }: DocsSearchProps) { export default function SearchInput({ className, ...props }: DocsSearchProps) {
const [searchText, setSearchText] = useState(''); const [searchQuery, setSearchQuery] = useState("");
const router = useRouter();
const pathname = usePathname();
function onSubmit(event: React.SyntheticEvent) { function onSearch(event: React.FormEvent) {
event.preventDefault() event.preventDefault()
return toast({ const encoededQuery = encodeURIComponent(searchQuery)
title: "Not implemented", router.push(`${pathname}?search=${encoededQuery}`)
description: "We're still working on the search.",
})
}; };
return ( return (
<form <form
onSubmit={onSubmit} onSubmit={onSearch}
className={cn("relative w-full flex justify-end items-center", className)} className={cn("relative w-full flex justify-end items-center", className)}
{...props} {...props}
> >
...@@ -31,6 +31,8 @@ export default function Search({ className, ...props }: DocsSearchProps) { ...@@ -31,6 +31,8 @@ export default function Search({ className, ...props }: DocsSearchProps) {
type="search" type="search"
placeholder="Search..." placeholder="Search..."
className="rounded-full pr-12" className="rounded-full pr-12"
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
/> />
<Button variant="outline" size="lg" className="absolute align-middle h-8 px-2.5 mr-1"> <Button variant="outline" size="lg" className="absolute align-middle h-8 px-2.5 mr-1">
<Icons.arrowRight className="h-3 w-3" /> <Icons.arrowRight className="h-3 w-3" />
......
...@@ -28,39 +28,35 @@ async function getToken(): Promise<IAuth> { ...@@ -28,39 +28,35 @@ async function getToken(): Promise<IAuth> {
} }
// fetches the top 200 games with a rating of 96 or higher // fetches the top 200 games with a rating of 96 or higher
export async function getGames(page = 1): Promise<IGame[]> { export async function getGames(page: number, search: string): Promise<IGame[]> {
try { const auth = await getToken();
const auth = await getToken(); const url = new URL(`${IGDB_BASE_URL}/games`);
const url = new URL(`${IGDB_BASE_URL}/games`);
let offset = calculateOffset(page, limit);
let offset = calculateOffset(page, limit);
const response = await fetch(url, {
const response = await fetch(url, { method: 'POST',
method: 'POST', headers: {
headers: { 'Client-ID': CLIENT_ID,
'Client-ID': CLIENT_ID, 'Authorization': `Bearer ${auth.access_token}`
'Authorization': `Bearer ${auth.access_token}` },
}, body: `fields name, cover.*; limit ${limit}; offset ${offset};
body: `fields name, cover.*; limit ${limit}; offset ${offset};
sort total_rating desc; where total_rating_count > 2 sort total_rating desc; where total_rating_count > 2
& cover != null & total_rating != null & rating != null;` & cover != null & total_rating != null & rating != null
}); & name ~ *"${search}"*;`
});
if (!response.ok) { if (!response.ok) {
throw new Error(`Error fetching games: ${response.statusText}`); throw new Error(`Error fetching games: ${response.statusText}`);
} }
const games = await response.json() as IGame[]; const games: IGame[] = await response.json() as IGame[];
games.forEach(game => { games.forEach(game => {
game.cover.url = getImageURL(game.cover.image_id, 'cover_big'); game.cover.url = getImageURL(game.cover.image_id, 'cover_big');
}); });
return games; return games;
} catch (error) {
console.error('Error in getGames:', error);
throw error;
}
} }
// fetches a single game by id // fetches a single game by id
......
...@@ -10,7 +10,7 @@ const IGDB_IMG_BASE_URL = process.env.IGDB_IMG_BASE_URL ?? '' ...@@ -10,7 +10,7 @@ const IGDB_IMG_BASE_URL = process.env.IGDB_IMG_BASE_URL ?? ''
// changes the default size of the image to be fetched // changes the default size of the image to be fetched
export function getImageURL(hashId: string, size: string): string { export function getImageURL(hashId: string, size: string): string {
return `${IGDB_IMG_BASE_URL}/t_${size}_2x/${hashId}.jpg` return `${IGDB_IMG_BASE_URL}/t_${size}/${hashId}.jpg`
} }
// calculates the offset for the query // calculates the offset for the query
......
This diff is collapsed.
...@@ -11,19 +11,19 @@ ...@@ -11,19 +11,19 @@
"preview": "next build && next start" "preview": "next build && next start"
}, },
"dependencies": { "dependencies": {
"@clerk/nextjs": "^4.18.4", "@clerk/nextjs": "^4.18.5",
"@clerk/themes": "^1.7.3", "@clerk/themes": "^1.7.4",
"@prisma/client": "^4.14.1", "@prisma/client": "^4.14.1",
"@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-scroll-area": "^1.0.3", "@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-select": "^1.2.1", "@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-slot": "^1.0.1", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.3", "@radix-ui/react-toast": "^1.1.4",
"@tanstack/react-query": "^4.29.7", "@tanstack/react-query": "^4.29.7",
"class-variance-authority": "^0.6.0", "class-variance-authority": "^0.6.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"lucide-react": "^0.221.0", "lucide-react": "^0.221.0",
"next": "13.4.3", "next": "^13.4.3",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
...@@ -33,12 +33,12 @@ ...@@ -33,12 +33,12 @@
}, },
"devDependencies": { "devDependencies": {
"@tanstack/eslint-plugin-query": "^4.29.9", "@tanstack/eslint-plugin-query": "^4.29.9",
"@types/node": "^20.2.3", "@types/node": "^20.2.4",
"@types/react": "^18.2.6", "@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4", "@types/react-dom": "^18.2.4",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"eslint": "^8.41.0", "eslint": "^8.41.0",
"eslint-config-next": "^13.4.3", "eslint-config-next": "^13.4.4",
"postcss": "8.4.23", "postcss": "8.4.23",
"prisma": "^4.14.1", "prisma": "^4.14.1",
"tailwindcss": "3.3.2", "tailwindcss": "3.3.2",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment