Home

Project #1 Day 4

Just a quick update from the first project, I only got a small amount done as I wasn't at my computer for the first couple of days of this month.

Adding Games

I wanted the experience of adding games to be slick, I knew I couldn't cater to all the potential fields for each game, so, I wanted the game selection process to be similar to something like Steam where you have a library of games that you can choose from when you start a session. I had a few hours today so I implemented a search component that allows you to search a huge catalog of games via the RAWG.io games API.

Here are some screenshots of what this looks like on both mobile and desktop.

Mobile Screenshot
Desktop Screenshot

Here is some code that handles getting the data and rendering it out.

"use client";

import { RawgGame } from "@/types";
import { useEffect, useState } from "react";
import { useQuery } from "react-query";

const fetchGames = async (searchTerm: string): Promise<RawgGame[]> => {
  if (!searchTerm) return [];

  const response = await fetch(
    `/api/search/game?query=${encodeURIComponent(searchTerm)}`
  );

  if (!response.ok) {
    throw new Error("Failed to fetch games");
  }

  const { results } = await response.json();

  return results;
};

export function useGames(searchTerm: string, debounceMs = 500) {
  const [debouncedTerm, setDebouncedTerm] = useState(searchTerm);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedTerm(searchTerm);
    }, debounceMs);

    return () => {
      clearTimeout(timer);
    };
  }, [searchTerm, debounceMs]);

  return useQuery<RawgGame[], Error>(
    ["games", debouncedTerm],
    () => fetchGames(debouncedTerm),
    {
      enabled: Boolean(debouncedTerm),
      staleTime: 5 * 60 * 1000, // 5 minutes
    }
  );
}

"use client";

import { useGames } from "@/hooks/use-games";
import { useState } from "react";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { GameResults } from "./game-results";

export const Search = () => {
  const [search, setSearch] = useState("");

  const { isLoading, data } = useGames(search);

  return (
    <>
      <h2 className="text-4xl font-semibold mb-4">Choose a game</h2>
      <form className="mb-4">
        <Input
          type="text"
          placeholder="Search"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          required
          className="w-full bg-white/10 border-white/20 text-white placeholder-white/50"
          aria-label="Search for a game"
        />
      </form>

      {isLoading && <p className="text-2xl font-semibold">Loading...</p>}

      <GameResults results={data} />

      <div className="flex justify-end">
        <Button className="bg-neutral-100 hover:bg-neutral-300 text-neutral-950">
          Continue
        </Button>
      </div>
    </>
  );
};

import { RawgGame } from "@/types";
import Image from "next/image";
import { shimmer, toBase64 } from "./functions";

interface Props {
  results: RawgGame[] | undefined;
}

export const GameResults = ({ results }: Props) => {
  if (results?.length === 0) {
    return <p className="text-2xl font-semibold">No games found</p>;
  }

  return (
    <div className="grid grid-cols-2 sm:grid-cols-[repeat(auto-fit,minmax(192px,1fr))] gap-4 mb-4 h-[calc(100vh-192px)] overflow-y-scroll hide-scrollbar">
      {results?.map((game: RawgGame) => {
        if (!game.background_image) {
          return null;
        }
        return (
          <button key={game.id} className="relative">
            <Image
              src={game.background_image}
              alt={game.name}
              className="w-48 h-64 object-cover rounded-md opacity-80 hover:opacity-100 cursor-pointer"
              width={128}
              height={128}
              placeholder={`data:image/svg+xml;base64,${toBase64(
                shimmer(700, 475)
              )}`}
              quality={100}
            />
            <p className="absolute left-2 bottom-2 text-2xl font-semibold">
              {game.name}
            </p>
          </button>
        );
      })}
    </div>
  );
};

Next Work

I'm about to get started on a few more things to tie this together with related features. I want to implement the following to have a significant flow in place

  • Auth, login, logout
  • Profile pages with the game library
  • Allow users to add and remove games from their library

I have my Next JS starter kit here which I will rip some code from to make implementing these features a little easier.

My next post should feature all of the above!

Signing Up

If you are interested in trying this project when I go live you can sign up here to get notified. I haven't decided whether I want to try to launch it earlier or towards the end of the month and work on it beyond that, no features, just fixing any bugs, unless it took off and became something that I should focus on.


More Posts