|
| 1 | +import Image from "next/image"; |
| 2 | +import { useRouter } from "next/router"; |
| 3 | +import React from "react"; |
| 4 | + |
| 5 | +import { ItchEmbed } from "@/components/ui/ItchEmbed"; |
| 6 | +import { useGame } from "@/hooks/useGames"; |
| 7 | + |
| 8 | +export default function IndividualGamePage() { |
| 9 | + const router = useRouter(); |
| 10 | + const { id } = router.query; |
| 11 | + |
| 12 | + const { |
| 13 | + data: game, |
| 14 | + isPending, |
| 15 | + error, |
| 16 | + isError, |
| 17 | + } = useGame(router.isReady ? id : undefined); |
| 18 | + |
| 19 | + if (isPending) { |
| 20 | + return ( |
| 21 | + <main className="mx-auto min-h-dvh max-w-6xl px-6 py-16 md:px-20"> |
| 22 | + <p>Loading Game...</p> |
| 23 | + </main> |
| 24 | + ); |
| 25 | + } |
| 26 | + |
| 27 | + if (isError) { |
| 28 | + const errorMessage = |
| 29 | + error?.response?.status === 404 |
| 30 | + ? "Game not found." |
| 31 | + : "Failed to Load Game"; |
| 32 | + |
| 33 | + return ( |
| 34 | + <main className="mx-auto min-h-screen max-w-6xl px-6 py-16 md:px-20"> |
| 35 | + <p className="text-red-500" role="alert"> |
| 36 | + {errorMessage} |
| 37 | + </p> |
| 38 | + </main> |
| 39 | + ); |
| 40 | + } |
| 41 | + |
| 42 | + if (!game) { |
| 43 | + return ( |
| 44 | + <main className="mx-auto min-h-dvh max-w-6xl px-6 py-16 md:px-20"> |
| 45 | + <p>No Game data available.</p> |
| 46 | + </main> |
| 47 | + ); |
| 48 | + } |
| 49 | + |
| 50 | + const gameTitle = game.name; |
| 51 | + const gameCover = game.gameCover; |
| 52 | + const gameDescription = game.description.split("\n"); |
| 53 | + |
| 54 | + const completionLabels: Record<number, string> = { |
| 55 | + 1: "WIP", |
| 56 | + 2: "Playable Dev", |
| 57 | + 3: "Beta", |
| 58 | + 4: "Completed", |
| 59 | + }; |
| 60 | + |
| 61 | + const devStage = completionLabels[game.completion] ?? "Stage Unknown"; |
| 62 | + |
| 63 | + // TODO ADD EVENT |
| 64 | + const event = "Game Jam November 2025"; |
| 65 | + // TODO ADD ARTIMAGES |
| 66 | + const artImages: { src: string; alt: string }[] = []; |
| 67 | + // const artImages = [ |
| 68 | + // { |
| 69 | + // src: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Minecraft_Zombie.png/120px-Minecraft_Zombie.png", |
| 70 | + // alt: "Minecraft Zombie", |
| 71 | + // }, |
| 72 | + // { |
| 73 | + // src: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d8/Minecraft_Enderman.png/120px-Minecraft_Enderman.png", |
| 74 | + // alt: "Minecraft Enderman", |
| 75 | + // }, |
| 76 | + // { |
| 77 | + // src: "https://upload.wikimedia.org/wikipedia/en/thumb/1/17/Minecraft_explore_landscape.png/375px-Minecraft_explore_landscape.png", |
| 78 | + // alt: "Minecraft Landscape", |
| 79 | + // }, |
| 80 | + // ]; |
| 81 | + |
| 82 | + return ( |
| 83 | + <div className="min-h-screen bg-background font-sans text-foreground"> |
| 84 | + <main> |
| 85 | + <section className="w-full bg-popover"> |
| 86 | + <div className="mx-auto max-w-7xl p-0 sm:p-8"> |
| 87 | + <Image |
| 88 | + src={gameCover} |
| 89 | + alt="Game Cover" |
| 90 | + width={800} |
| 91 | + height={800} |
| 92 | + className="max-h-[60vh] w-full object-cover sm:mx-auto sm:h-auto sm:max-h-[60vh] sm:rounded-2xl sm:object-contain" |
| 93 | + priority |
| 94 | + /> |
| 95 | + </div> |
| 96 | + </section> |
| 97 | + |
| 98 | + <section className="mx-auto max-w-7xl px-4 py-6 sm:px-8 sm:py-8 lg:px-24 lg:py-12"> |
| 99 | + <h1 className="mb-6 text-center font-jersey10 text-5xl font-bold tracking-wide text-primary sm:mb-10 sm:text-6xl"> |
| 100 | + {gameTitle} |
| 101 | + </h1> |
| 102 | + <div className="mb-6 w-full max-w-full sm:float-right sm:mb-4 sm:ml-8 sm:w-96"> |
| 103 | + <table className="w-full min-w-[220px] border-collapse border-spacing-0 text-sm sm:text-base"> |
| 104 | + <tbody> |
| 105 | + <tr className="border-b-2 border-gray-300"> |
| 106 | + <td className="py-1 pr-2 text-muted-foreground sm:py-2"> |
| 107 | + Contributors |
| 108 | + </td> |
| 109 | + <td className="py-1 text-right sm:py-2"> |
| 110 | + <div className="grid grid-cols-[auto_auto] gap-x-1 gap-y-1"> |
| 111 | + {game.contributors.map((c) => ( |
| 112 | + <React.Fragment key={c.member_id}> |
| 113 | + <a |
| 114 | + href={`/member/${c.member_id}`} |
| 115 | + className="text-primary hover:underline" |
| 116 | + > |
| 117 | + {c.name} |
| 118 | + </a> |
| 119 | + <span>{c.role}</span> |
| 120 | + </React.Fragment> |
| 121 | + ))} |
| 122 | + </div> |
| 123 | + </td> |
| 124 | + </tr> |
| 125 | + <tr className="border-b-2 border-gray-300"> |
| 126 | + <td className="py-1 pr-2 text-muted-foreground sm:py-2"> |
| 127 | + Development Stage |
| 128 | + </td> |
| 129 | + <td className="py-1 text-right sm:py-2">{devStage}</td> |
| 130 | + </tr> |
| 131 | + <tr className="border-b-2 border-gray-300"> |
| 132 | + <td className="py-1 pr-2 text-muted-foreground sm:py-2"> |
| 133 | + Host Site |
| 134 | + </td> |
| 135 | + <td className="py-1 text-right sm:py-2"> |
| 136 | + <a |
| 137 | + href={game.hostURL} |
| 138 | + className="text-primary underline hover:underline" |
| 139 | + > |
| 140 | + {game.hostURL} |
| 141 | + </a> |
| 142 | + </td> |
| 143 | + </tr> |
| 144 | + <tr> |
| 145 | + <td className="py-1 pr-2 text-muted-foreground sm:py-2"> |
| 146 | + Event |
| 147 | + </td> |
| 148 | + <td className="py-1 text-right sm:py-2">{event}</td> |
| 149 | + </tr> |
| 150 | + </tbody> |
| 151 | + </table> |
| 152 | + </div> |
| 153 | + <ul className="space-y-3 text-base leading-8 sm:text-lg lg:text-xl"> |
| 154 | + {gameDescription.map((desc, i) => ( |
| 155 | + <li key={i}>{desc}</li> |
| 156 | + ))} |
| 157 | + </ul> |
| 158 | + </section> |
| 159 | + |
| 160 | + <section className="mt-8 flex w-full flex-col items-center gap-6"> |
| 161 | + {game.itchEmbedID && ( |
| 162 | + <ItchEmbed embedID={game.itchEmbedID} name={gameTitle} /> |
| 163 | + )} |
| 164 | + <h2 className="font-jersey10 text-5xl text-primary">ARTWORK</h2> |
| 165 | + |
| 166 | + <div className="mx-auto mb-6 flex h-auto w-full max-w-4xl flex-col items-center gap-4 px-4 sm:flex-row sm:justify-center sm:gap-6 sm:px-6 md:h-60"> |
| 167 | + {artImages.map((img) => ( |
| 168 | + <div |
| 169 | + key={img.src} |
| 170 | + className="h-48 w-full overflow-hidden rounded-lg bg-popover shadow-md sm:h-60 sm:w-1/3" |
| 171 | + > |
| 172 | + <Image |
| 173 | + key={img.alt} |
| 174 | + src={img.src} |
| 175 | + alt={img.alt} |
| 176 | + width={240} |
| 177 | + height={240} |
| 178 | + className="h-full w-full object-cover" |
| 179 | + /> |
| 180 | + </div> |
| 181 | + ))} |
| 182 | + </div> |
| 183 | + </section> |
| 184 | + </main> |
| 185 | + {/* <Footer /> */} |
| 186 | + </div> |
| 187 | + ); |
| 188 | +} |
0 commit comments