Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
b24ca73
Created ItchEmbed component for widget
nicostellar Nov 29, 2025
d5c2e34
Added page structure for game pages
nicostellar Nov 29, 2025
93b80d3
Fix file structure in-line with desired URLs
nicostellar Nov 29, 2025
9e3c0a3
Enhance GameLandingPage with detailed game information and art gallery
bilibilistack Nov 29, 2025
ebccffd
rename from header to section for first image
bilibilistack Nov 29, 2025
286b245
rename function to IndividualGamePage to better reflect purpose
nicostellar Nov 29, 2025
ba6ef8d
add itch.io embed with ItchEmbed component
nicostellar Nov 29, 2025
51ecf1e
Created Games model with fields and sample Event fields for foreignkey
RadinMan Nov 29, 2025
bd8811b
Merge branch 'issue-7-Individual_game_pages' of https://github.com/co…
RadinMan Nov 29, 2025
6f225ea
Add Game Cover Image and Style Enhancements to Individual Game Page
bilibilistack Nov 29, 2025
101cb75
Refactor Individual Game Page layout and remove unused CSS styles
bilibilistack Nov 29, 2025
e5c4d1b
Fix issue of ItchEmbed inconsistent between server response and clien…
bilibilistack Nov 29, 2025
437d605
Fixed: Games class completion field to use models.IntegerChoices
RadinMan Nov 29, 2025
c400dc4
Created new migration for the new models in models.py
RadinMan Nov 29, 2025
8e7f777
Merge branch 'issue-7-Individual_game_pages' of https://github.com/co…
RadinMan Nov 29, 2025
af3483b
Added: itchEmbeddedID field to the Game Class for itch.io widget embe…
RadinMan Nov 29, 2025
35c68b4
Merge branch 'main' into issue-7-Individual_game_pages
bilibilistack Dec 3, 2025
4e034e4
Enhance Individual Game Page: Update game cover image, and include ex…
bilibilistack Dec 3, 2025
7f04583
Improved file readability
RadinMan Dec 3, 2025
efba98f
Issue Fixed: itchEmbeddedID gets default to None, for games with itch…
RadinMan Dec 3, 2025
2956ed9
Fix layout of ItchEmbed section on Individual Game Page for better al…
bilibilistack Dec 4, 2025
32a089a
Merge branch 'issue-7-Individual_game_pages' of https://github.com/co…
RadinMan Dec 6, 2025
360990a
Merge pull request #26 from codersforcauses/issue-7-Individual_game_p…
bilibilistack Dec 6, 2025
0dee0b2
Remove unnecessary link from ItchEmbed component for cleaner renderin…
bilibilistack Dec 6, 2025
91de20c
Fixed: pathToThumbnail renamed from pathhToMedia and fixed the models…
RadinMan Dec 6, 2025
aa8791c
Enhance Individual Game Page layout and content; add game description…
bilibilistack Dec 6, 2025
6d60160
Update Individual Game Page layout; enhance game description and adju…
bilibilistack Dec 10, 2025
d542650
Refactor Individual Game Page layout; streamline game description ren…
bilibilistack Dec 10, 2025
acd3dbf
Merge branch 'main' into issue-7-Individual_game_pages
bilibilistack Dec 10, 2025
b207f44
Added: GamesAdmin and a sample EventsAdmin that allows Admin to manag…
RadinMan Dec 13, 2025
a2faf83
Added: GameSerializer defines Games to JSON conversion and back
RadinMan Dec 13, 2025
cd2766b
Added: GamesView that defines the GamesAPI behaviour (CRUD) using ser…
RadinMan Dec 13, 2025
6f57197
Added: the Games_API_Router and the urls publish the GamesView endpoi…
RadinMan Dec 13, 2025
7c68353
Fixed: Games_API_Router endpoint url names to games for all the games…
RadinMan Dec 13, 2025
f3f453b
Fixed: Games in admin was displayed as Gamess which the table has bee…
RadinMan Dec 13, 2025
d5ddc40
Fixed Migration: the tables in db is renamed to Game and Event
RadinMan Dec 13, 2025
5d875f1
Fixed: Games description field to be TextField() which is for writing…
RadinMan Dec 13, 2025
103478f
Added: Fetch game data from API in IndividualGamePage and handle load…
bilibilistack Dec 13, 2025
3a30cff
Remove wiki picture configuration
bilibilistack Dec 15, 2025
9182cd8
Sync main branch next.config changes
bilibilistack Dec 15, 2025
897b85a
Merge branch 'issue-7-Individual_game_pages' into issue-7-Individual_…
bilibilistack Dec 15, 2025
0601c36
Added: Implemented itch.io embed proxy and updated IndividualGamePage…
bilibilistack Dec 15, 2025
ebb11c8
Merge branch 'main' into issue-7-Individual_game_pages
RadinMan Jan 7, 2026
4984ccb
Merge branch 'issue-7-Individual_game_pages' into issue-7-Individual_…
bilibilistack Jan 7, 2026
4e7fffc
Merge remote-tracking branch 'origin/main' into issue-7-Individual_ga…
RadinMan Jan 9, 2026
3f6cd7b
Fixed: Deleted duplicated name migrations and combined games and even…
RadinMan Jan 9, 2026
346e635
Added: the frontend receiver of the gamesAPI backend
RadinMan Jan 10, 2026
188c8e1
Fixed: migration conflict, combines both event and games, event datet…
RadinMan Jan 10, 2026
626fd0d
Fixed: import rest_framework generic line clash with itch_embed_proxy…
RadinMan Jan 10, 2026
c841d88
Added media with some boilerplate images, MUST REMOVE BEFORE MAIN MERGE
RadinMan Jan 10, 2026
94ede60
Added GameContributors model to link Game and Member with unique role…
bilibilistack Jan 10, 2026
0c90daa
Merge branch 'issue-7-Individual_game_pages_api' of https://github.co…
bilibilistack Jan 10, 2026
1ae6d0e
Fix games api fetch issue on frontend
bilibilistack Jan 10, 2026
dd90bd9
Fixed Routing Problem: missing / to the fetch game data from backend …
RadinMan Jan 10, 2026
3051da9
Merge branch 'issue-7-Individual_game_pages_api' of https://github.co…
RadinMan Jan 10, 2026
69e214b
Fixed Game Title h1 issue: where drop-shadow-lg causes the title to g…
RadinMan Jan 10, 2026
76937b8
New game image just for test
RadinMan Jan 10, 2026
9705cb0
New GameContributorAdmin Model: Allows admin users to assign member's…
RadinMan Jan 14, 2026
45257e2
New GameContributorSerializer: passes the contributor information to …
RadinMan Jan 14, 2026
377443c
New individual game pages: added contributor object from the GameCont…
RadinMan Jan 14, 2026
6032433
New member profiles and game covers
RadinMan Jan 14, 2026
acd0623
Added dynamic Contributors with their name and role displayed in a gr…
RadinMan Jan 14, 2026
73855d5
Added member_id to allow frontend to route contributors to their memb…
RadinMan Jan 14, 2026
91cd50f
Moved hero image to full-width at the top to match the Figma. Updated…
nicostellar Jan 16, 2026
459b1cf
Merge branch 'main' into issue-7-Individual_game_pages_api
bilibilistack Jan 16, 2026
ab393b2
Typography sizes and line-heights updated to match the Figma. Positio…
nicostellar Jan 16, 2026
9dcd497
Fixed the table padding and alignment issues. Updated colours to use …
nicostellar Jan 16, 2026
1885a81
ItchEmbed modified to the suggested width and aspect ratio. Toggled d…
nicostellar Jan 16, 2026
e800949
Individual games page is now responsive on mobile phones and tablets.…
nicostellar Jan 17, 2026
173b064
Added Games Hook: responsible for fetching data from backend and prep…
RadinMan Jan 17, 2026
eab2c62
Modified Simple: the id.tsx file is only responsible for rendering th…
RadinMan Jan 17, 2026
5098669
Added: API namespace for the game_dev/urls.py apis
RadinMan Jan 17, 2026
9cbc960
Added: under urlpatterns added new url paths for games api and the it…
RadinMan Jan 17, 2026
9ab41b1
Fixed GamesAPIView to GamesDetailAPIView, instead of loading list of …
RadinMan Jan 17, 2026
1af61bc
Deleted some unnecessary comments about model structure used as previ…
nicostellar Jan 17, 2026
a785218
Removed test image files for games and user profiles
RadinMan Jan 17, 2026
b6953e3
Merge branch 'main' into issue-7-Individual_game_pages_api
RadinMan Jan 17, 2026
ede34cf
Fixed merge conflict variable name typo
RadinMan Jan 17, 2026
859f5dc
Fixed spelling of game contributors(s) to contributor(s)
RadinMan Jan 17, 2026
9e321b2
Fixed the issue with hostSite being incorrect for itch games, where i…
nicostellar Jan 22, 2026
7d90c06
Merge branch 'main' into issue-7-Individual_game_pages_api
bilibilistack Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion client/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ const config = {
},
outputFileTracingRoot: import.meta.dirname,
images: {
domains: ["localhost"],
remotePatterns: [
{ protocol: 'http', hostname: '127.0.0.1' },
{ protocol: 'http', hostname: 'localhost' },
{ protocol: 'https', hostname: 'upload.wikimedia.org' },
],
},
// Turns on file change polling for the Windows Dev Container
// Doesn't work currently for turbopack, so file changes will not automatically update the client.
Expand Down
17 changes: 17 additions & 0 deletions client/src/components/ui/ItchEmbed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type ItchEmbedProps = {
embedID: string;
name: string;
};

export function ItchEmbed({ embedID, name }: ItchEmbedProps) {
return (
<div className="mb-6 w-full max-w-[552px] px-4 shadow-[0_12px_40px_-16px_hsl(var(--secondary)_/_0.45)] sm:aspect-[552/167] sm:px-0">
<iframe
className="h-full w-full border-0"
src={`https://itch.io/embed/${embedID}?dark=1`}
title={name}
allowFullScreen
/>
</div>
);
}
81 changes: 81 additions & 0 deletions client/src/hooks/useGames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useQuery } from "@tanstack/react-query";
import { AxiosError } from "axios";

import api from "@/lib/api";

type Contributor = {
member_id: number;
name: string;
role: string;
};

type ApiGame = {
name: string;
description: string;
completion: number;
active: boolean;
hostURL: string;
isItch: boolean;
// TO DO: Add support for no itchEmbedID for non-itch games
itchEmbedID: string;
pathToThumbnail: string | null;
event: number | null;
contributors: Contributor[];
};

type UiGame = Omit<ApiGame, "pathToThumbnail"> & {
gameCover: string;
};

/**
* Normalizes Next.js router query parameter to a single string ID.
* Handles both string and array formats from dynamic routes.
*/
function normalizeGameId(
gameId: string | string[] | undefined,
): string | undefined {
if (!gameId) return undefined;
return typeof gameId === "string" ? gameId : gameId[0];
}

function transformApiGameToUiGame(data: ApiGame): UiGame {
return {
...data,
gameCover: data.pathToThumbnail ?? "/game_dev_club_logo.svg",
};
}

/**
* Custom hook to fetch a single game by ID.
*
* @param gameId - game ID from Next.js router query (can be string, string[], or undefined)
* @returns React Query result with transformed UI game data
*
* @example
* ```tsx
* const { id } = router.query;
* const { data: game, isPending, error } = useGame(id);
* ```
*/
export function useGame(gameId: string | string[] | undefined) {
const id = normalizeGameId(gameId);

return useQuery<ApiGame, AxiosError, UiGame>({
queryKey: ["games", id],
queryFn: async () => {
if (!id) {
throw new Error("Game ID is required");
}
const response = await api.get<ApiGame>(`/games/${id}/`);
return response.data;
},
enabled: !!id,
select: transformApiGameToUiGame,
retry: (failureCount, error) => {
if (error!.response?.status === 404) {
return false;
}
return failureCount < 3;
},
});
}
187 changes: 187 additions & 0 deletions client/src/pages/games/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import Image from "next/image";
import { useRouter } from "next/router";
import React from "react";

import { ItchEmbed } from "@/components/ui/ItchEmbed";
import { useGame } from "@/hooks/useGames";

export default function IndividualGamePage() {
const router = useRouter();
const { id } = router.query;

const {
data: game,
isPending,
error,
isError,
} = useGame(router.isReady ? id : undefined);

if (isPending) {
return (
<main className="mx-auto min-h-dvh max-w-6xl px-6 py-16 md:px-20">
<p>Loading Game...</p>
</main>
);
}

if (isError) {
const errorMessage =
error?.response?.status === 404
? "Game not found."
: "Failed to Load Game";

return (
<main className="mx-auto min-h-screen max-w-6xl px-6 py-16 md:px-20">
<p className="text-red-500" role="alert">
{errorMessage}
</p>
</main>
);
}

if (!game) {
return (
<main className="mx-auto min-h-dvh max-w-6xl px-6 py-16 md:px-20">
<p>No Game data available.</p>
</main>
);
}

const gameTitle = game.name;
const gameCover = game.gameCover;
const gameDescription = game.description.split("\n");

const completionLabels: Record<number, string> = {
1: "WIP",
2: "Playable Dev",
3: "Beta",
4: "Completed",
};

const devStage = completionLabels[game.completion] ?? "Stage Unknown";

// TODO ADD EVENT
const event = "Game Jam November 2025";
// TODO ADD ARTIMAGES
const artImages = [
{
src: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Minecraft_Zombie.png/120px-Minecraft_Zombie.png",
alt: "Minecraft Zombie",
},
{
src: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d8/Minecraft_Enderman.png/120px-Minecraft_Enderman.png",
alt: "Minecraft Enderman",
},
{
src: "https://upload.wikimedia.org/wikipedia/en/thumb/1/17/Minecraft_explore_landscape.png/375px-Minecraft_explore_landscape.png",
alt: "Minecraft Landscape",
},
];

return (
<div className="min-h-screen bg-background font-sans text-foreground">
<main>
<section className="w-full bg-popover">
<div className="mx-auto max-w-7xl p-0 sm:p-8">
<Image
src={gameCover}
alt="Game Cover"
width={800}
height={800}
className="max-h-[60vh] w-full object-cover sm:mx-auto sm:h-auto sm:max-h-[60vh] sm:rounded-2xl sm:object-contain"
priority
/>
</div>
</section>

<section className="mx-auto max-w-7xl px-4 py-6 sm:px-8 sm:py-8 lg:px-24 lg:py-12">
<h1 className="mb-6 text-center font-jersey10 text-5xl font-bold tracking-wide text-primary sm:mb-10 sm:text-6xl">
{gameTitle}
</h1>
<div className="mb-6 w-full max-w-full sm:float-right sm:mb-4 sm:ml-8 sm:w-96">
<table className="w-full min-w-[220px] border-collapse border-spacing-0 text-sm sm:text-base">
<tbody>
<tr className="border-b-2 border-gray-300">
<td className="py-1 pr-2 text-muted-foreground sm:py-2">
Contributors
</td>
<td className="py-1 text-right sm:py-2">
<div className="grid grid-cols-[auto_auto] gap-x-1 gap-y-1">
{game.contributors.map((c) => (
<React.Fragment key={c.member_id}>
<a
href={`/member/${c.member_id}`}
className="text-primary hover:underline"
>
{c.name}
</a>
<span>{c.role}</span>
</React.Fragment>
))}
</div>
</td>
</tr>
<tr className="border-b-2 border-gray-300">
<td className="py-1 pr-2 text-muted-foreground sm:py-2">
Development Stage
</td>
<td className="py-1 text-right sm:py-2">{devStage}</td>
</tr>
<tr className="border-b-2 border-gray-300">
<td className="py-1 pr-2 text-muted-foreground sm:py-2">
Host Site
</td>
<td className="py-1 text-right sm:py-2">
<a
href={game.hostURL}
className="text-primary underline hover:underline"
>
{game.hostURL}
</a>
</td>
</tr>
<tr>
<td className="py-1 pr-2 text-muted-foreground sm:py-2">
Event
</td>
<td className="py-1 text-right sm:py-2">{event}</td>
</tr>
</tbody>
</table>
</div>
<ul className="space-y-3 text-base leading-8 sm:text-lg lg:text-xl">
{gameDescription.map((desc, i) => (
<li key={i}>{desc}</li>
))}
</ul>
</section>

<section className="mt-8 flex w-full flex-col items-center gap-6">
{game.isItch && (
<ItchEmbed embedID={game.itchEmbedID} name={gameTitle} />
)}
<h2 className="font-jersey10 text-5xl text-primary">ARTWORK</h2>

<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">
{artImages.map((img) => (
<div
key={img.src}
className="h-48 w-full overflow-hidden rounded-lg bg-popover shadow-md sm:h-60 sm:w-1/3"
>
<Image
key={img.alt}
src={img.src}
alt={img.alt}
width={240}
height={240}
className="h-full w-full object-cover"
/>
</div>
))}
</div>
</section>
</main>
{/* <Footer /> */}
</div>
);
}
Empty file.
4 changes: 2 additions & 2 deletions server/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("game_dev.urls")),
]

if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
19 changes: 15 additions & 4 deletions server/game_dev/admin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
from django.contrib import admin
from .models import Member, Event
from .models import Member, Game, Event, GameContributor


class MemberAdmin(admin.ModelAdmin):
pass


class EventAdmin(admin.ModelAdmin):
list_display = ("name", "date", "location", "publicationDate")
# Sample EventsAdmin Class made
class EventsAdmin(admin.ModelAdmin):
pass


class GameContributorAdmin(admin.ModelAdmin):
pass


class GamesAdmin(admin.ModelAdmin):
list_display = ("name", "description", "completion", "active", "hostURL", "isItch", "itchEmbedID", "pathToThumbnail", "event")


admin.site.register(Member, MemberAdmin)
admin.site.register(Event, EventAdmin)
admin.site.register(Event, EventsAdmin)
admin.site.register(Game, GamesAdmin)
admin.site.register(GameContributor, GameContributorAdmin)
18 changes: 0 additions & 18 deletions server/game_dev/migrations/0003_event_date.py

This file was deleted.

Loading