From 8c942c07524dcdee778a3832cd48dacb1aa36c7a Mon Sep 17 00:00:00 2001 From: RadinMan Date: Fri, 13 Feb 2026 09:09:05 +0000 Subject: [PATCH 01/26] Tried pulling from main branch --- server/api/settings.py | 3 ++ .../0005_alter_member_profile_picture.py | 18 ------- server/game_dev/migrations/0005_committee.py | 20 -------- ...r_member_profile_picture_game_and_more.py} | 51 +++++++++++++++---- .../0006_alter_gameshowcase_game.py | 23 +++++++++ .../migrations/0006_committee_role.py | 20 -------- ...move_game_isitch_alter_game_itchembedid.py | 27 ---------- .../migrations/0007_alter_committee_id.py | 19 ------- ...pathtothumbnail_game_thumbnail_and_more.py | 23 --------- .../migrations/0008_alter_committee_role.py | 20 -------- .../migrations/0008_alter_event_date.py | 18 ------- .../migrations/0009_merge_20260129_2104.py | 14 ----- .../migrations/0009_merge_20260131_1044.py | 14 ----- .../migrations/0010_merge_20260131_1118.py | 14 ----- server/game_dev/models.py | 2 +- 15 files changed, 67 insertions(+), 219 deletions(-) delete mode 100644 server/game_dev/migrations/0005_alter_member_profile_picture.py delete mode 100644 server/game_dev/migrations/0005_committee.py rename server/game_dev/migrations/{0005_alter_event_date_game_gameshowcase_gamecontributor.py => 0005_committee_alter_member_profile_picture_game_and_more.py} (70%) create mode 100644 server/game_dev/migrations/0006_alter_gameshowcase_game.py delete mode 100644 server/game_dev/migrations/0006_committee_role.py delete mode 100644 server/game_dev/migrations/0006_remove_game_isitch_alter_game_itchembedid.py delete mode 100644 server/game_dev/migrations/0007_alter_committee_id.py delete mode 100644 server/game_dev/migrations/0007_rename_pathtothumbnail_game_thumbnail_and_more.py delete mode 100644 server/game_dev/migrations/0008_alter_committee_role.py delete mode 100644 server/game_dev/migrations/0008_alter_event_date.py delete mode 100644 server/game_dev/migrations/0009_merge_20260129_2104.py delete mode 100644 server/game_dev/migrations/0009_merge_20260131_1044.py delete mode 100644 server/game_dev/migrations/0010_merge_20260131_1118.py diff --git a/server/api/settings.py b/server/api/settings.py index 1f4df893..5c93d765 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -30,6 +30,7 @@ SECRET_KEY = os.environ.get("API_SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! +# export APP_ENV=DEVELOPMENT DEBUG = os.environ.get("APP_ENV") == "DEVELOPMENT" ALLOWED_HOSTS = ( @@ -65,6 +66,8 @@ "corsheaders.middleware.CorsMiddleware", ] + +# export FRONTEND_URL=http://localhost:3000 CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://127.0.0.1:3000", diff --git a/server/game_dev/migrations/0005_alter_member_profile_picture.py b/server/game_dev/migrations/0005_alter_member_profile_picture.py deleted file mode 100644 index 76031d25..00000000 --- a/server/game_dev/migrations/0005_alter_member_profile_picture.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.15 on 2026-01-18 15:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("game_dev", "0004_alter_event_date"), - ] - - operations = [ - migrations.AlterField( - model_name="member", - name="profile_picture", - field=models.ImageField(blank=True, null=True, upload_to="profiles/"), - ), - ] diff --git a/server/game_dev/migrations/0005_committee.py b/server/game_dev/migrations/0005_committee.py deleted file mode 100644 index 271dee18..00000000 --- a/server/game_dev/migrations/0005_committee.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.15 on 2026-01-09 09:46 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0004_alter_event_date'), - ] - - operations = [ - migrations.CreateModel( - name='Committee', - fields=[ - ('id', models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, primary_key=True, serialize=False, to='game_dev.member')), - ], - ), - ] diff --git a/server/game_dev/migrations/0005_alter_event_date_game_gameshowcase_gamecontributor.py b/server/game_dev/migrations/0005_committee_alter_member_profile_picture_game_and_more.py similarity index 70% rename from server/game_dev/migrations/0005_alter_event_date_game_gameshowcase_gamecontributor.py rename to server/game_dev/migrations/0005_committee_alter_member_profile_picture_game_and_more.py index 89ea924d..de6f2910 100644 --- a/server/game_dev/migrations/0005_alter_event_date_game_gameshowcase_gamecontributor.py +++ b/server/game_dev/migrations/0005_committee_alter_member_profile_picture_game_and_more.py @@ -1,7 +1,6 @@ -# Generated by Django 5.1.14 on 2026-01-24 06:27 +# Generated by Django 5.1.14 on 2026-02-07 07:47 import django.db.models.deletion -import django.db.models.functions.datetime from django.db import migrations, models @@ -12,12 +11,42 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name="Committee", + fields=[ + ( + "id", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + primary_key=True, + serialize=False, + to="game_dev.member", + ), + ), + ( + "role", + models.CharField( + choices=[ + ("P", "President"), + ("VP", "Vice-President"), + ("SEC", "Secretary"), + ("TRE", "Treasurer"), + ("MARK", "Marketing"), + ("EV", "Events OCM"), + ("PRO", "Projects OCM"), + ("FRE", "Fresher Rep"), + ], + default="FRE", + max_length=9, + unique=True, + ), + ), + ], + ), migrations.AlterField( - model_name="event", - name="date", - field=models.DateTimeField( - db_default=django.db.models.functions.datetime.Now() - ), + model_name="member", + name="profile_picture", + field=models.ImageField(blank=True, null=True, upload_to="profiles/"), ), migrations.CreateModel( name="Game", @@ -46,18 +75,17 @@ class Migration(migrations.Migration): ), ), ("active", models.BooleanField(default=True)), - ("hostURL", models.CharField(max_length=2083)), - ("isItch", models.BooleanField(default=True)), + ("hostURL", models.URLField(max_length=2083)), ( "itchEmbedID", models.PositiveIntegerField( blank=True, default=None, - help_text="If game is stored on itch.io, please enter the 7 digit long game id as its itchEmbedID, i.e., 1000200", + help_text="If game is stored on itch.io, please enter the itchEmbedID, i.e., 1000200", null=True, ), ), - ("pathToThumbnail", models.ImageField(null=True, upload_to="games/")), + ("thumbnail", models.ImageField(null=True, upload_to="games/")), ( "event", models.ForeignKey( @@ -88,6 +116,7 @@ class Migration(migrations.Migration): on_delete=django.db.models.deletion.CASCADE, related_name="game_showcases", to="game_dev.game", + unique=True, ), ), ], diff --git a/server/game_dev/migrations/0006_alter_gameshowcase_game.py b/server/game_dev/migrations/0006_alter_gameshowcase_game.py new file mode 100644 index 00000000..c6b8b99c --- /dev/null +++ b/server/game_dev/migrations/0006_alter_gameshowcase_game.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.14 on 2026-02-07 07:57 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0005_committee_alter_member_profile_picture_game_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="gameshowcase", + name="game", + field=models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="game_showcases", + to="game_dev.game", + ), + ), + ] diff --git a/server/game_dev/migrations/0006_committee_role.py b/server/game_dev/migrations/0006_committee_role.py deleted file mode 100644 index 457afa83..00000000 --- a/server/game_dev/migrations/0006_committee_role.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.15 on 2026-01-09 09:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0005_committee'), - ] - - operations = [ - migrations.AddField( - model_name='committee', - name='role', - field=models.CharField(choices=[('P', 'President'), ('VP', 'Vice-President'), ('SEC', 'Secretary'), - ('TRE', 'Treasurer'), ('MARK', 'Marketing'), ('EV', 'Events OCM'), - ('PRO', 'Projects OCM'), ('FRE', 'Fresher Rep')], default='FRE', max_length=9), - ), - ] diff --git a/server/game_dev/migrations/0006_remove_game_isitch_alter_game_itchembedid.py b/server/game_dev/migrations/0006_remove_game_isitch_alter_game_itchembedid.py deleted file mode 100644 index f6c45bc2..00000000 --- a/server/game_dev/migrations/0006_remove_game_isitch_alter_game_itchembedid.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 5.1.14 on 2026-01-24 07:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("game_dev", "0005_alter_event_date_game_gameshowcase_gamecontributor"), - ] - - operations = [ - migrations.RemoveField( - model_name="game", - name="isItch", - ), - migrations.AlterField( - model_name="game", - name="itchEmbedID", - field=models.PositiveIntegerField( - blank=True, - default=None, - help_text="If game is stored on itch.io, please enter the itchEmbedID, i.e., 1000200", - null=True, - ), - ), - ] diff --git a/server/game_dev/migrations/0007_alter_committee_id.py b/server/game_dev/migrations/0007_alter_committee_id.py deleted file mode 100644 index fa9a4841..00000000 --- a/server/game_dev/migrations/0007_alter_committee_id.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.15 on 2026-01-21 07:59 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0006_committee_role'), - ] - - operations = [ - migrations.AlterField( - model_name='committee', - name='id', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='game_dev.member'), - ), - ] diff --git a/server/game_dev/migrations/0007_rename_pathtothumbnail_game_thumbnail_and_more.py b/server/game_dev/migrations/0007_rename_pathtothumbnail_game_thumbnail_and_more.py deleted file mode 100644 index d2b7f58d..00000000 --- a/server/game_dev/migrations/0007_rename_pathtothumbnail_game_thumbnail_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.1.14 on 2026-01-24 07:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("game_dev", "0006_remove_game_isitch_alter_game_itchembedid"), - ] - - operations = [ - migrations.RenameField( - model_name="game", - old_name="pathToThumbnail", - new_name="thumbnail", - ), - migrations.AlterField( - model_name="game", - name="hostURL", - field=models.URLField(max_length=2083), - ), - ] diff --git a/server/game_dev/migrations/0008_alter_committee_role.py b/server/game_dev/migrations/0008_alter_committee_role.py deleted file mode 100644 index ecd84c0c..00000000 --- a/server/game_dev/migrations/0008_alter_committee_role.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.15 on 2026-01-21 08:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0007_alter_committee_id'), - ] - - operations = [ - migrations.AlterField( - model_name='committee', - name='role', - field=models.CharField(choices=[('P', 'President'), ('VP', 'Vice-President'), ('SEC', 'Secretary'), - ('TRE', 'Treasurer'), ('MARK', 'Marketing'), ('EV', 'Events OCM'), - ('PRO', 'Projects OCM'), ('FRE', 'Fresher Rep')], default='FRE', max_length=9, unique=True), - ), - ] diff --git a/server/game_dev/migrations/0008_alter_event_date.py b/server/game_dev/migrations/0008_alter_event_date.py deleted file mode 100644 index 13c0a175..00000000 --- a/server/game_dev/migrations/0008_alter_event_date.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.14 on 2026-01-24 07:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("game_dev", "0007_rename_pathtothumbnail_game_thumbnail_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="event", - name="date", - field=models.DateTimeField(), - ), - ] diff --git a/server/game_dev/migrations/0009_merge_20260129_2104.py b/server/game_dev/migrations/0009_merge_20260129_2104.py deleted file mode 100644 index c7d68a0d..00000000 --- a/server/game_dev/migrations/0009_merge_20260129_2104.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 5.1.15 on 2026-01-29 13:04 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0005_alter_member_profile_picture'), - ('game_dev', '0008_alter_committee_role'), - ] - - operations = [ - ] diff --git a/server/game_dev/migrations/0009_merge_20260131_1044.py b/server/game_dev/migrations/0009_merge_20260131_1044.py deleted file mode 100644 index 1abbcd99..00000000 --- a/server/game_dev/migrations/0009_merge_20260131_1044.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 5.1.15 on 2026-01-31 02:44 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0005_alter_member_profile_picture'), - ('game_dev', '0008_alter_event_date'), - ] - - operations = [ - ] diff --git a/server/game_dev/migrations/0010_merge_20260131_1118.py b/server/game_dev/migrations/0010_merge_20260131_1118.py deleted file mode 100644 index 35027726..00000000 --- a/server/game_dev/migrations/0010_merge_20260131_1118.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 5.1.15 on 2026-01-31 03:18 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0009_merge_20260129_2104'), - ('game_dev', '0009_merge_20260131_1044'), - ] - - operations = [ - ] diff --git a/server/game_dev/models.py b/server/game_dev/models.py index 3c39a421..b9016d04 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -69,7 +69,7 @@ def __str__(self): class GameShowcase(models.Model): - game = models.ForeignKey('Game', on_delete=models.CASCADE, related_name='game_showcases', unique=True) + game = models.OneToOneField('Game', on_delete=models.CASCADE, related_name='game_showcases') description = models.TextField() def __str__(self): From f5773162e586c9140aa51c57ead1ab72c54ddf3a Mon Sep 17 00:00:00 2001 From: RadinMan Date: Fri, 13 Feb 2026 15:30:00 +0000 Subject: [PATCH 02/26] Used eventCarousel.tsx to build a carousel for artworks inside of Gameshowcase page --- client/src/components/ui/GameArtCarousel.tsx | 127 +++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 client/src/components/ui/GameArtCarousel.tsx diff --git a/client/src/components/ui/GameArtCarousel.tsx b/client/src/components/ui/GameArtCarousel.tsx new file mode 100644 index 00000000..6b1510a8 --- /dev/null +++ b/client/src/components/ui/GameArtCarousel.tsx @@ -0,0 +1,127 @@ +// This carousel is for Artworks to be displayed in the Gameshowcase + +import { ChevronLeft, ChevronRight } from "lucide-react"; +import Image from "next/image"; +//import Link from "next/link"; +import { useEffect, useRef, useState } from "react"; + +// import { UiEvent as EventType } from "@/hooks/useEvents"; + +// Artwork teams code once done replaces this +export type MockArtwork = { + id: number; + name: string; + image: string; + sourceGameId: number; + gameName?: string; +}; + +type GameArtCarouselProps = { + items: MockArtwork[]; +}; + +const GAP = 20; + +export default function GameArtCarousel({ items }: GameArtCarouselProps) { + const firstItemRef = useRef(null); + + const [currentIndex, setCurrentIndex] = useState(0); + const [itemWidth, setItemWidth] = useState(0); + const [visibleCount, setVisibleCount] = useState(3); + + const maxIndex = Math.max(items.length - visibleCount, 0); + + const slideLeft = () => { + setCurrentIndex((prev) => Math.max(prev - 1, 0)); + }; + + const slideRight = () => { + setCurrentIndex((prev) => Math.min(prev + 1, maxIndex)); + }; + + const translateX = -(currentIndex * (itemWidth + GAP)); + + useEffect(() => { + if (!firstItemRef.current) return; + + const observer = new ResizeObserver(() => { + const width = firstItemRef.current?.clientWidth ?? 0; + setItemWidth(width); + }); + + observer.observe(firstItemRef.current); + + return () => observer.disconnect(); + }, []); + + useEffect(() => { + const updateVisibleCount = () => { + if (window.innerWidth < 768) { + setVisibleCount(1); + } else { + setVisibleCount(3); + } + }; + + updateVisibleCount(); + window.addEventListener("resize", updateVisibleCount); + + return () => window.removeEventListener("resize", updateVisibleCount); + }, []); + + return ( +
+
+

Game Art Showcase

+ +
+ + +
+
+ +
+
+ {items.map((art, index) => ( +
+
+ {art.name} +
+ +

{art.name}

+ + {art.gameName && ( +

from {art.gameName}

+ )} +
+ ))} +
+
+
+ ); +} From c35cfcad943f7ba6d928e8a92357d457e12f1fd1 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Fri, 13 Feb 2026 15:30:33 +0000 Subject: [PATCH 03/26] Added: Mock data for artwork standins --- client/src/placeholderDataArtGame.ts | 110 +++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 client/src/placeholderDataArtGame.ts diff --git a/client/src/placeholderDataArtGame.ts b/client/src/placeholderDataArtGame.ts new file mode 100644 index 00000000..434af38e --- /dev/null +++ b/client/src/placeholderDataArtGame.ts @@ -0,0 +1,110 @@ +import { MockArtwork } from "@/components/ui/GameArtCarousel"; + +export const mockGameArtworks: MockArtwork[] = [ + { + id: 1, + name: "Pixel Forest", + image: "/landing_placeholder.png", + sourceGameId: 1, + gameName: "Minecraft", + }, + { + id: 2, + name: "Cyber Arena", + image: "/landing_placeholder.png", + sourceGameId: 2, + gameName: "Crownbreakers", + }, + { + id: 3, + name: "Pixel Forest2", + image: "/landing_placeholder.png", + sourceGameId: 1, + gameName: "Minecraft", + }, + { + id: 4, + name: "Cyber Arena2", + image: "/landing_placeholder.png", + sourceGameId: 2, + gameName: "Crownbreakers", + }, + { + id: 5, + name: "Pixel Forest3", + image: "/landing_placeholder.png", + sourceGameId: 1, + gameName: "Minecraft", + }, + { + id: 6, + name: "Cyber Arena3", + image: "/landing_placeholder.png", + sourceGameId: 2, + gameName: "Crownbreakers", + }, + { + id: 7, + name: "Pixel Forest4", + image: "/landing_placeholder.png", + sourceGameId: 1, + gameName: "Minecraft", + }, + { + id: 8, + name: "Cyber Arena4", + image: "/landing_placeholder.png", + sourceGameId: 2, + gameName: "Crownbreakers", + }, + + { + id: 9, + name: "Pixel Forest5", + image: "/landing_placeholder.png", + sourceGameId: 1, + gameName: "Minecraft", + }, + { + id: 10, + name: "Cyber Arena5", + image: "/landing_placeholder.png", + sourceGameId: 2, + gameName: "Crownbreakers", + }, + { + id: 11, + name: "Pixel Forest6", + image: "/landing_placeholder.png", + sourceGameId: 1, + gameName: "Minecraft", + }, + { + id: 12, + name: "Cyber Arena6", + image: "/landing_placeholder.png", + sourceGameId: 2, + gameName: "Crownbreakers", + }, + { + id: 13, + name: "Pixel Forest7", + image: "/landing_placeholder.png", + sourceGameId: 1, + gameName: "Minecraft", + }, + { + id: 14, + name: "Pixel Forest8", + image: "/landing_placeholder.png", + sourceGameId: 1, + gameName: "Minecraft", + }, + { + id: 15, + name: "Cyber Arena9", + image: "/landing_placeholder.png", + sourceGameId: 2, + gameName: "Crownbreakers", + }, +]; From e79e278c43890a1675157b238727a89b8742cf38 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Fri, 13 Feb 2026 15:32:14 +0000 Subject: [PATCH 04/26] Added: Mock artwork, filter function to filter artwork by Game, and then GameArtCarousel UI component call for display --- client/src/pages/games/index.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client/src/pages/games/index.tsx b/client/src/pages/games/index.tsx index f89c7b42..6d998c90 100644 --- a/client/src/pages/games/index.tsx +++ b/client/src/pages/games/index.tsx @@ -3,7 +3,9 @@ import Link from "next/link"; import React from "react"; import { SocialIcon } from "react-social-icons"; +import GameArtCarousel from "@/components/ui/GameArtCarousel"; import { useGameshowcase } from "@/hooks/useGameshowcase"; +import { mockGameArtworks } from "@/placeholderDataArtGame"; // Mock data export default function HomePage() { const { data: showcases, isPending, isError, error } = useGameshowcase(); @@ -30,6 +32,17 @@ export default function HomePage() { ); } + // Create a lookup table: { gameId: artwork[] } + const artworksByGame = mockGameArtworks.reduce< + Record + >((acc, art) => { + if (!acc[art.sourceGameId]) { + acc[art.sourceGameId] = []; + } + acc[art.sourceGameId].push(art); + return acc; + }, {}); + return ( <>
+ {(artworksByGame[showcase.game_id] || []).length > 0 && ( +
+

New

+ +
+ )}
{showcase.game_description}
From d74372dfb79d06d90cbaf7acf27c812dfe4a8d65 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Fri, 13 Feb 2026 17:33:38 +0000 Subject: [PATCH 05/26] Added a third game with sample data (2 artworks) to see how the arrows will look if art is less than 4 --- client/src/placeholderDataArtGame.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/src/placeholderDataArtGame.ts b/client/src/placeholderDataArtGame.ts index 434af38e..f0829252 100644 --- a/client/src/placeholderDataArtGame.ts +++ b/client/src/placeholderDataArtGame.ts @@ -107,4 +107,18 @@ export const mockGameArtworks: MockArtwork[] = [ sourceGameId: 2, gameName: "Crownbreakers", }, + { + id: 16, + name: "Mad Lad", + image: "/landing_placeholder.png", + sourceGameId: 3, + gameName: "Celeste", + }, + { + id: 17, + name: "Mad Lad2", + image: "/landing_placeholder.png", + sourceGameId: 3, + gameName: "Celeste", + }, ]; From 5addb725c80d04a5979bb6b9b933ceda828f26f0 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Fri, 13 Feb 2026 17:37:02 +0000 Subject: [PATCH 06/26] Modified layout to match figma, Chevron arrows are larger and outside with hover effect, on desktop 4 artwork are displayed, clean coding for easy readability --- client/src/components/ui/GameArtCarousel.tsx | 104 ++++++++++--------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/client/src/components/ui/GameArtCarousel.tsx b/client/src/components/ui/GameArtCarousel.tsx index 6b1510a8..3c4c0225 100644 --- a/client/src/components/ui/GameArtCarousel.tsx +++ b/client/src/components/ui/GameArtCarousel.tsx @@ -21,13 +21,14 @@ type GameArtCarouselProps = { }; const GAP = 20; +const maxItemsPerPage = 4; export default function GameArtCarousel({ items }: GameArtCarouselProps) { const firstItemRef = useRef(null); const [currentIndex, setCurrentIndex] = useState(0); const [itemWidth, setItemWidth] = useState(0); - const [visibleCount, setVisibleCount] = useState(3); + const [visibleCount, setVisibleCount] = useState(maxItemsPerPage); const maxIndex = Math.max(items.length - visibleCount, 0); @@ -59,7 +60,7 @@ export default function GameArtCarousel({ items }: GameArtCarouselProps) { if (window.innerWidth < 768) { setVisibleCount(1); } else { - setVisibleCount(3); + setVisibleCount(maxItemsPerPage); } }; @@ -71,56 +72,61 @@ export default function GameArtCarousel({ items }: GameArtCarouselProps) { return (
-
-

Game Art Showcase

- -
- - -
-
- -
-
+ {/* LEFT ARROW */} + + + {/* VIEWPORT */} +
+
+ {items.map((art, index) => ( +
+
+ {art.name} +
+ +

{art.name}

+ + {art.gameName && ( +

from {art.gameName}

+ )}
- -

{art.name}

- - {art.gameName && ( -

from {art.gameName}

- )} -
- ))} + ))} +
+ + {/* RIGHT ARROW */} +
); From 9386aa3a0874b026367a305b04af102cecfd377a Mon Sep 17 00:00:00 2001 From: RadinMan Date: Fri, 13 Feb 2026 17:39:19 +0000 Subject: [PATCH 07/26] Modified spacing between each showcased game to 32 gap, inside each game group div the elements have a gap of 8 --- client/src/pages/games/index.tsx | 203 ++++++++++++++++--------------- 1 file changed, 104 insertions(+), 99 deletions(-) diff --git a/client/src/pages/games/index.tsx b/client/src/pages/games/index.tsx index 6d998c90..7fba86a6 100644 --- a/client/src/pages/games/index.tsx +++ b/client/src/pages/games/index.tsx @@ -33,6 +33,7 @@ export default function HomePage() { } // Create a lookup table: { gameId: artwork[] } + // O( Art + Game ) complexity const artworksByGame = mockGameArtworks.reduce< Record >((acc, art) => { @@ -64,119 +65,123 @@ export default function HomePage() { {!showcases || showcases.length === 0 ? (

No games available.

) : ( -
+
{showcases.map((showcase, idx) => ( -
- {/* Left: Cover Image */} -
- {showcase.gameCover ? ( - {showcase.game_name - ) : ( -
+
+ {/* Game CoverImage + Gameshowcase Detail */} +
+ {/* Left: Cover Image */} +
+ {showcase.gameCover ? ( Default game cover -
- )} -
- {/* Right: Details */} -
-
- {/* Title of the game */} -

- - - {showcase.game_name} - - -

- {/* Comments from committes */} -

- {/* double quotes from comments */} - - {showcase.description} - -

-

- Contributors -

-
    - {showcase.contributors.map((contributor, cidx) => ( -
  • + Default game cover +
+ )} +
+ {/* Right: Details */} +
+
+ {/* Title of the game */} +

+ - - {contributor.name} + + {showcase.game_name} - +

+ {/* Comments from committes */} +

+ {/* double quotes from comments */} + + {showcase.description} + +

+

+ Contributors +

+
    + {showcase.contributors.map((contributor, cidx) => ( +
  • - - {contributor.role} - - {/* Social icons placeholder */} - {/* TODO: Add actual links */} - - {/* Social icons using react-social-icons */} - - - - -
  • - ))} -
+ + {contributor.name} + + + - {contributor.role} + + {/* Social icons placeholder */} + {/* TODO: Add actual links */} + + {/* Social icons using react-social-icons */} + + + + + + ))} + +
-
- {(artworksByGame[showcase.game_id] || []).length > 0 && ( -
-

New

+ + {/* Game Art Carousel */} + {(artworksByGame[showcase.game_id] || []).length > 0 && ( + )} + + {/* Description */} +
+ {showcase.game_description}
- )} -
- {showcase.game_description}
))} From 1b2b15f02dfabea337d280bf78da3210214bcb58 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Fri, 13 Feb 2026 18:17:33 +0000 Subject: [PATCH 08/26] Added GameArtCarousel to the individual games pages and filter by the game id --- client/src/pages/games/[id].tsx | 39 +++++++-------------------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/client/src/pages/games/[id].tsx b/client/src/pages/games/[id].tsx index a83c5102..298e9db3 100644 --- a/client/src/pages/games/[id].tsx +++ b/client/src/pages/games/[id].tsx @@ -2,8 +2,10 @@ import Image from "next/image"; import { useRouter } from "next/router"; import React from "react"; +import GameArtCarousel from "@/components/ui/GameArtCarousel"; import { ItchEmbed } from "@/components/ui/ItchEmbed"; import { useGame } from "@/hooks/useGames"; +import { mockGameArtworks } from "@/placeholderDataArtGame"; // Mock data export default function IndividualGamePage() { const router = useRouter(); @@ -62,22 +64,11 @@ export default function IndividualGamePage() { // TODO ADD EVENT const event = "Game Jam November 2025"; - // TODO ADD ARTIMAGES - const artImages: { src: string; alt: string }[] = []; - // 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", - // }, - // ]; + + const gameId = Number(id); + const artworksForGame = mockGameArtworks.filter( + (art) => art.sourceGameId === gameId, + ); return (
@@ -164,21 +155,7 @@ export default function IndividualGamePage() {

ARTWORK

- {artImages.map((img) => ( -
- {img.alt} -
- ))} +
From 242ec0c859a229f20fd2cb1ac286f8bd4cce7657 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 14 Feb 2026 07:02:11 +0000 Subject: [PATCH 09/26] Added Art, Art Contributors, and ArtShowcase models from issue-8-merge-40 branch by artwork team --- server/game_dev/models.py | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index b9016d04..bcd0c14d 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -95,3 +95,49 @@ def get_member(self): def __str__(self): return self.id.name + + +# Copied from issue-8-merge-40 therefore is just sample to work with +class Art(models.Model): + name = models.CharField(null=False, max_length=200) + description = models.CharField(max_length=200,) + + # Talk to the artwork team to change their model to meet the follow, remove the null and blank + source_game = models.ForeignKey('Game', on_delete=models.CASCADE, related_name='game_artwork', null=True, blank=True) + media = models.ImageField(upload_to='art/', null=False) + active = models.BooleanField(default=True) + + def __str__(self): + return str(self.name) + + +class ArtContributor(models.Model): + art = models.ForeignKey('Art', on_delete=models.CASCADE, related_name='contributors') + member = models.ForeignKey('Member', on_delete=models.CASCADE, related_name='art_contributions') + role = models.CharField(max_length=100) + + class Meta: + constraints = [ + models.UniqueConstraint(fields=['art', 'member'], name='unique_art_member') + ] + verbose_name = 'Art Contributor' + verbose_name_plural = 'Art Contributors' + + def __str__(self): + return f"{self.member.name} - {self.art.name} ({self.role})" + + +class ArtShowcase(models.Model): + description = models.CharField(max_length=200) + art = models.ForeignKey(Art, on_delete=models.CASCADE, related_name='showcase') + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=['art'], + name='unique_artshowcase_per_art', + violation_error_message='Each art piece can only have one showcase.') + ] + + def __str__(self): + return f"ArtShowcase[Art={str(self.art.name)}, Description={self.description}]" From d78978047a4099092391a71604cd0578a14734ce Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 14 Feb 2026 07:04:10 +0000 Subject: [PATCH 10/26] Added GamesArtSerializer (only necessary fields from ArtSerializer by issue-8-merge-40 branch) to GameSerializer so that artworks can be grouped and sent when frontend calls for a game --- server/game_dev/serializers.py | 56 +++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index a9e5c811..67e50d44 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -1,5 +1,7 @@ from rest_framework import serializers from .models import Event, Game, Member, GameShowcase, GameContributor +# from issue-8-merge-40 temp need changes +from .models import Art, ArtContributor, ArtShowcase class EventSerializer(serializers.ModelSerializer): @@ -16,6 +18,43 @@ class Meta: ] +######################################## +# Copied from issue-8-merge-40 therefore is just sample to work with +class ArtContributorSerializer(serializers.ModelSerializer): + member_id = serializers.IntegerField(source='member.id', read_only=True) + member_name = serializers.CharField(source='member.name', read_only=True) + + class Meta: + model = ArtContributor + fields = ['id', 'member_id', 'member_name', 'role'] + + +class ArtSerializer(serializers.ModelSerializer): + art_id = serializers.IntegerField(source='id', read_only=True) + source_game_id = serializers.IntegerField(source='source_game.id', read_only=True) + source_game_name = serializers.CharField(source='source_game.name', read_only=True) + contributors = ArtContributorSerializer(many=True, read_only=True) + showcase_description = serializers.SerializerMethodField() + + class Meta: + model = Art + fields = ['art_id', 'name', 'description', 'media', 'active', 'source_game_id', 'source_game_name', 'contributors', 'showcase_description'] + + def get_showcase_description(self, obj): + showcase = obj.showcase.first() + return showcase.description if showcase else None + + +class ArtShowcaseSerializer(serializers.ModelSerializer): + art_name = serializers.CharField(source='art.name', read_only=True) + + class Meta: + model = ArtShowcase + fields = ['id', 'description', 'art', 'art_name'] + +######################################## + + # This is child serializer of GameSerializer class GameContributorSerializer(serializers.ModelSerializer): member_id = serializers.IntegerField(source="member.id") # to link contributors to their member/[id] page @@ -26,16 +65,31 @@ class Meta: fields = ("member_id", "name", "role") +# Copied ArtSerializer at kept data only needed instead of all of it +class GameArtSerializer(serializers.ModelSerializer): + art_id = serializers.IntegerField(source='id', read_only=True) + source_game_id = serializers.IntegerField(source='source_game.id', read_only=True) + + class Meta: + model = Art + fields = ['art_id', 'name', 'media', 'active', 'source_game_id'] + + class GamesSerializer(serializers.ModelSerializer): contributors = GameContributorSerializer( many=True, source="game_contributors", read_only=True ) + artworks = GameArtSerializer( + many=True, + source="game_artwork", + read_only=True + ) class Meta: model = Game - fields = ('id', 'name', 'description', 'completion', 'active', 'hostURL', 'itchEmbedID', 'thumbnail', 'event', "contributors") + fields = ('id', 'name', 'description', 'completion', 'active', 'hostURL', 'itchEmbedID', 'thumbnail', 'event', "contributors", "artworks") # Contributor serializer for name and role From 79a089b9c5a071df1d5c9354cc47a9ff9342446c Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 14 Feb 2026 07:05:04 +0000 Subject: [PATCH 11/26] Added: New Migration file to match database structure by issue-8-merge-40 branch --- .../0006_alter_gameshowcase_game.py | 2 +- .../0007_art_artcontributor_artshowcase.py | 114 ++++++++++++++++++ .../0011_alter_gameshowcase_game.py | 19 --- 3 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 server/game_dev/migrations/0007_art_artcontributor_artshowcase.py delete mode 100644 server/game_dev/migrations/0011_alter_gameshowcase_game.py diff --git a/server/game_dev/migrations/0006_alter_gameshowcase_game.py b/server/game_dev/migrations/0006_alter_gameshowcase_game.py index c6b8b99c..c4abf9ba 100644 --- a/server/game_dev/migrations/0006_alter_gameshowcase_game.py +++ b/server/game_dev/migrations/0006_alter_gameshowcase_game.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.14 on 2026-02-07 07:57 +# Generated by Django 5.1.14 on 2026-02-14 05:05 import django.db.models.deletion from django.db import migrations, models diff --git a/server/game_dev/migrations/0007_art_artcontributor_artshowcase.py b/server/game_dev/migrations/0007_art_artcontributor_artshowcase.py new file mode 100644 index 00000000..69f5b56c --- /dev/null +++ b/server/game_dev/migrations/0007_art_artcontributor_artshowcase.py @@ -0,0 +1,114 @@ +# Generated by Django 5.1.14 on 2026-02-14 06:13 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("game_dev", "0006_alter_gameshowcase_game"), + ] + + operations = [ + migrations.CreateModel( + name="Art", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200)), + ("description", models.CharField(max_length=200)), + ("media", models.ImageField(upload_to="art/")), + ("active", models.BooleanField(default=True)), + ( + "source_game", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="game_artwork", + to="game_dev.game", + ), + ), + ], + ), + migrations.CreateModel( + name="ArtContributor", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("role", models.CharField(max_length=100)), + ( + "art", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="contributors", + to="game_dev.art", + ), + ), + ( + "member", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="art_contributions", + to="game_dev.member", + ), + ), + ], + options={ + "verbose_name": "Art Contributor", + "verbose_name_plural": "Art Contributors", + "constraints": [ + models.UniqueConstraint( + fields=("art", "member"), name="unique_art_member" + ) + ], + }, + ), + migrations.CreateModel( + name="ArtShowcase", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("description", models.CharField(max_length=200)), + ( + "art", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="showcase", + to="game_dev.art", + ), + ), + ], + options={ + "constraints": [ + models.UniqueConstraint( + fields=("art",), + name="unique_artshowcase_per_art", + violation_error_message="Each art piece can only have one showcase.", + ) + ], + }, + ), + ] diff --git a/server/game_dev/migrations/0011_alter_gameshowcase_game.py b/server/game_dev/migrations/0011_alter_gameshowcase_game.py deleted file mode 100644 index 120447ce..00000000 --- a/server/game_dev/migrations/0011_alter_gameshowcase_game.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.15 on 2026-02-11 06:58 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0010_merge_20260131_1118'), - ] - - operations = [ - migrations.AlterField( - model_name='gameshowcase', - name='game', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='game_showcases', to='game_dev.game'), - ), - ] From ed9424999a2df878faff802316ddc8c308d4cb3f Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 14 Feb 2026 07:05:55 +0000 Subject: [PATCH 12/26] Added: ArtContributorInline and ArtAdmin to Djnago admin --- server/game_dev/admin.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server/game_dev/admin.py b/server/game_dev/admin.py index d07b4da7..7a21a465 100644 --- a/server/game_dev/admin.py +++ b/server/game_dev/admin.py @@ -1,6 +1,9 @@ from django.contrib import admin from .models import Member, Game, Event, GameContributor, GameShowcase, Committee +# from issue-8-merge-40 temp need changes +from .models import Art, ArtContributor, ArtShowcase + class MemberAdmin(admin.ModelAdmin): list_display = ("id", "name", "active", "profile_picture", "about", "pronouns") @@ -29,9 +32,24 @@ class CommitteeAdmin(admin.ModelAdmin): raw_id_fields = ["id"] +# from issue-8-merge-40 temp need changes +class ArtContributorInline(admin.TabularInline): + model = ArtContributor + extra = 1 + + +class ArtAdmin(admin.ModelAdmin): + inlines = [ArtContributorInline] + + admin.site.register(Member, MemberAdmin) admin.site.register(Event, EventAdmin) admin.site.register(Game, GamesAdmin) admin.site.register(GameContributor, GameContributorAdmin) admin.site.register(GameShowcase, GameShowcaseAdmin) admin.site.register(Committee, CommitteeAdmin) + +# from issue-8-merge-40 temp need changes +admin.site.register(Art, ArtAdmin) +# admin.site.register(ArtContributor) +admin.site.register(ArtShowcase) From 097cdc0ada5d56c66d70bac3a31cc82e319edb84 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 14 Feb 2026 07:09:01 +0000 Subject: [PATCH 13/26] Removed mockArtwork, instead it uses UiArtwork from the useGame Hooks (UI Shape) --- client/src/components/ui/GameArtCarousel.tsx | 17 +-- client/src/placeholderDataArtGame.ts | 124 ------------------- 2 files changed, 3 insertions(+), 138 deletions(-) delete mode 100644 client/src/placeholderDataArtGame.ts diff --git a/client/src/components/ui/GameArtCarousel.tsx b/client/src/components/ui/GameArtCarousel.tsx index 3c4c0225..99549448 100644 --- a/client/src/components/ui/GameArtCarousel.tsx +++ b/client/src/components/ui/GameArtCarousel.tsx @@ -5,19 +5,12 @@ import Image from "next/image"; //import Link from "next/link"; import { useEffect, useRef, useState } from "react"; -// import { UiEvent as EventType } from "@/hooks/useEvents"; +import type { UiArtwork } from "@/hooks/useGames"; -// Artwork teams code once done replaces this -export type MockArtwork = { - id: number; - name: string; - image: string; - sourceGameId: number; - gameName?: string; -}; +// import { UiEvent as EventType } from "@/hooks/useEvents"; type GameArtCarouselProps = { - items: MockArtwork[]; + items: UiArtwork[]; }; const GAP = 20; @@ -110,10 +103,6 @@ export default function GameArtCarousel({ items }: GameArtCarouselProps) {

{art.name}

- - {art.gameName && ( -

from {art.gameName}

- )}
))}
diff --git a/client/src/placeholderDataArtGame.ts b/client/src/placeholderDataArtGame.ts deleted file mode 100644 index f0829252..00000000 --- a/client/src/placeholderDataArtGame.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { MockArtwork } from "@/components/ui/GameArtCarousel"; - -export const mockGameArtworks: MockArtwork[] = [ - { - id: 1, - name: "Pixel Forest", - image: "/landing_placeholder.png", - sourceGameId: 1, - gameName: "Minecraft", - }, - { - id: 2, - name: "Cyber Arena", - image: "/landing_placeholder.png", - sourceGameId: 2, - gameName: "Crownbreakers", - }, - { - id: 3, - name: "Pixel Forest2", - image: "/landing_placeholder.png", - sourceGameId: 1, - gameName: "Minecraft", - }, - { - id: 4, - name: "Cyber Arena2", - image: "/landing_placeholder.png", - sourceGameId: 2, - gameName: "Crownbreakers", - }, - { - id: 5, - name: "Pixel Forest3", - image: "/landing_placeholder.png", - sourceGameId: 1, - gameName: "Minecraft", - }, - { - id: 6, - name: "Cyber Arena3", - image: "/landing_placeholder.png", - sourceGameId: 2, - gameName: "Crownbreakers", - }, - { - id: 7, - name: "Pixel Forest4", - image: "/landing_placeholder.png", - sourceGameId: 1, - gameName: "Minecraft", - }, - { - id: 8, - name: "Cyber Arena4", - image: "/landing_placeholder.png", - sourceGameId: 2, - gameName: "Crownbreakers", - }, - - { - id: 9, - name: "Pixel Forest5", - image: "/landing_placeholder.png", - sourceGameId: 1, - gameName: "Minecraft", - }, - { - id: 10, - name: "Cyber Arena5", - image: "/landing_placeholder.png", - sourceGameId: 2, - gameName: "Crownbreakers", - }, - { - id: 11, - name: "Pixel Forest6", - image: "/landing_placeholder.png", - sourceGameId: 1, - gameName: "Minecraft", - }, - { - id: 12, - name: "Cyber Arena6", - image: "/landing_placeholder.png", - sourceGameId: 2, - gameName: "Crownbreakers", - }, - { - id: 13, - name: "Pixel Forest7", - image: "/landing_placeholder.png", - sourceGameId: 1, - gameName: "Minecraft", - }, - { - id: 14, - name: "Pixel Forest8", - image: "/landing_placeholder.png", - sourceGameId: 1, - gameName: "Minecraft", - }, - { - id: 15, - name: "Cyber Arena9", - image: "/landing_placeholder.png", - sourceGameId: 2, - gameName: "Crownbreakers", - }, - { - id: 16, - name: "Mad Lad", - image: "/landing_placeholder.png", - sourceGameId: 3, - gameName: "Celeste", - }, - { - id: 17, - name: "Mad Lad2", - image: "/landing_placeholder.png", - sourceGameId: 3, - gameName: "Celeste", - }, -]; From e5583632b02bdf9f2cb40679b685ea55fa57387a Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 14 Feb 2026 07:11:30 +0000 Subject: [PATCH 14/26] Added: ApiArtworks which is the structure of backend GameArtSerializer and is converted into UiArtwork by transformApiGameToUiGame (used by GameArtCarousel) --- client/src/hooks/useGames.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/client/src/hooks/useGames.ts b/client/src/hooks/useGames.ts index 27f9bc17..c620297f 100644 --- a/client/src/hooks/useGames.ts +++ b/client/src/hooks/useGames.ts @@ -9,6 +9,21 @@ type Contributor = { role: string; }; +export type UiArtwork = { + id: number; + name: string; + image: string; + sourceGameId: number; +}; + +type ApiArtworks = { + art_id: number; + name: string; + media: string; + active: boolean; + source_game_id: number; +}; + type ApiGame = { name: string; description: string; @@ -20,10 +35,12 @@ type ApiGame = { thumbnail: string | null; event: number | null; contributors: Contributor[]; + artworks: ApiArtworks[]; }; -type UiGame = Omit & { +type UiGame = Omit & { gameCover: string; + artworks: UiArtwork[]; }; /** @@ -41,6 +58,12 @@ function transformApiGameToUiGame(data: ApiGame): UiGame { return { ...data, gameCover: data.thumbnail ?? "/game_dev_club_logo.svg", + artworks: data.artworks.map((a) => ({ + id: a.art_id, + name: a.name, + image: a.media, + sourceGameId: a.source_game_id, + })), }; } From 335f904479d743429c8b145ec96aea36f993da08 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 14 Feb 2026 07:12:46 +0000 Subject: [PATCH 15/26] Removed old artwork filter and just uses game.artworks for the GameArtCarousel --- client/src/pages/games/[id].tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/client/src/pages/games/[id].tsx b/client/src/pages/games/[id].tsx index 298e9db3..66ddd52a 100644 --- a/client/src/pages/games/[id].tsx +++ b/client/src/pages/games/[id].tsx @@ -5,7 +5,6 @@ import React from "react"; import GameArtCarousel from "@/components/ui/GameArtCarousel"; import { ItchEmbed } from "@/components/ui/ItchEmbed"; import { useGame } from "@/hooks/useGames"; -import { mockGameArtworks } from "@/placeholderDataArtGame"; // Mock data export default function IndividualGamePage() { const router = useRouter(); @@ -65,11 +64,6 @@ export default function IndividualGamePage() { // TODO ADD EVENT const event = "Game Jam November 2025"; - const gameId = Number(id); - const artworksForGame = mockGameArtworks.filter( - (art) => art.sourceGameId === gameId, - ); - return (
@@ -155,7 +149,7 @@ export default function IndividualGamePage() {

ARTWORK

- +
From 649683cdc52a6da34752ee75b2c84502316a8bde Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 14 Feb 2026 07:30:14 +0000 Subject: [PATCH 16/26] Added Link to each image that directs to /artworks/[art_id] --- client/src/components/ui/GameArtCarousel.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/client/src/components/ui/GameArtCarousel.tsx b/client/src/components/ui/GameArtCarousel.tsx index 99549448..0633accb 100644 --- a/client/src/components/ui/GameArtCarousel.tsx +++ b/client/src/components/ui/GameArtCarousel.tsx @@ -2,7 +2,7 @@ import { ChevronLeft, ChevronRight } from "lucide-react"; import Image from "next/image"; -//import Link from "next/link"; +import Link from "next/link"; import { useEffect, useRef, useState } from "react"; import type { UiArtwork } from "@/hooks/useGames"; @@ -93,14 +93,16 @@ export default function GameArtCarousel({ items }: GameArtCarouselProps) { width: `calc((100% - ${(visibleCount - 1) * GAP}px) / ${visibleCount})`, }} > -
- {art.name} -
+ +
+ {art.name} +
+

{art.name}

From b1a3ff666d72afc1bf8959b9ff916053956d2b77 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sun, 15 Feb 2026 15:29:03 +0000 Subject: [PATCH 17/26] Removed: blank and null paramaters from the Art model source_game field after discussion from Art Team --- server/game_dev/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/game_dev/models.py b/server/game_dev/models.py index bcd0c14d..9ce1450f 100644 --- a/server/game_dev/models.py +++ b/server/game_dev/models.py @@ -103,7 +103,7 @@ class Art(models.Model): description = models.CharField(max_length=200,) # Talk to the artwork team to change their model to meet the follow, remove the null and blank - source_game = models.ForeignKey('Game', on_delete=models.CASCADE, related_name='game_artwork', null=True, blank=True) + source_game = models.ForeignKey('Game', on_delete=models.CASCADE, related_name='game_artwork') media = models.ImageField(upload_to='art/', null=False) active = models.BooleanField(default=True) From 229ef1fa5457546563563b48f1448d747589f1a0 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sun, 15 Feb 2026 15:31:35 +0000 Subject: [PATCH 18/26] Added artworks field to the GameshowcaseSerializer, uses get_artwork to get all artwork of all games in gameshowcase --- server/game_dev/serializers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index 67e50d44..c9a348f9 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -111,16 +111,20 @@ class GameshowcaseSerializer(serializers.ModelSerializer): game_description = serializers.CharField(source='game.description', read_only=True) game_cover_thumbnail = serializers.ImageField(source='game.thumbnail', read_only=True) contributors = serializers.SerializerMethodField() + artworks = serializers.SerializerMethodField() class Meta: model = GameShowcase - fields = ('game_id', 'game_name', 'game_description', 'description', 'contributors', 'game_cover_thumbnail') + fields = ('game_id', 'game_name', 'game_description', 'description', 'contributors', 'game_cover_thumbnail', 'artworks') def get_contributors(self, obj): # Always fetch contributors from GameContributor for the related game contributors = GameContributor.objects.filter(game=obj.game) return ShowcaseContributorSerializer(contributors, many=True).data + def get_artworks(self, obj): + return GameArtSerializer(obj.game.game_artwork.all(), many=True).data + class MemberSerializer(serializers.ModelSerializer): class Meta: From 12984191137c93c3c39804685c716ba144cf497b Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sun, 15 Feb 2026 15:33:00 +0000 Subject: [PATCH 19/26] Made type ApiArtworks export and usable for useGameshowcase.ts, allows for single source of truth --- client/src/hooks/useGames.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/hooks/useGames.ts b/client/src/hooks/useGames.ts index c620297f..a4b13c71 100644 --- a/client/src/hooks/useGames.ts +++ b/client/src/hooks/useGames.ts @@ -16,7 +16,7 @@ export type UiArtwork = { sourceGameId: number; }; -type ApiArtworks = { +export type ApiArtworks = { art_id: number; name: string; media: string; From ce545916db104dede95aaa440ac083e7943c06b5 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sun, 15 Feb 2026 15:40:11 +0000 Subject: [PATCH 20/26] Added artworks field to ApiShowcaseGame struct, map backend artworks into UiArtworks and normalize media paths into full image urls --- client/src/hooks/useGameshowcase.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/src/hooks/useGameshowcase.ts b/client/src/hooks/useGameshowcase.ts index db04d612..8e72149c 100644 --- a/client/src/hooks/useGameshowcase.ts +++ b/client/src/hooks/useGameshowcase.ts @@ -1,6 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; +import type { ApiArtworks, UiArtwork } from "@/hooks/useGames"; import api from "@/lib/api"; type Contributor = { @@ -15,10 +16,15 @@ type ApiShowcaseGame = { game_description: string; contributors: Contributor[]; game_cover_thumbnail?: string | null; + artworks: ApiArtworks[]; }; -type UiShowcaseGame = Omit & { +type UiShowcaseGame = Omit< + ApiShowcaseGame, + "game_cover_thumbnail" | "artworks" +> & { gameCover: string; + artworks: UiArtwork[]; }; function getGameCoverUrl( @@ -36,6 +42,12 @@ function transformApiShowcaseGameToUi(data: ApiShowcaseGame): UiShowcaseGame { return { ...data, gameCover: getGameCoverUrl(data.game_cover_thumbnail), + artworks: data.artworks.map((a) => ({ + id: a.art_id, + name: a.name, + image: getGameCoverUrl(a.media), + sourceGameId: a.source_game_id, + })), }; } From 865bd0eab1a157a7a14485fa6e86f3c6f93f5041 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sun, 15 Feb 2026 15:40:55 +0000 Subject: [PATCH 21/26] Added artworks field to ApiShowcaseGame struct, map backend artworks into UiArtworks and normalize media paths into full image urls --- client/src/hooks/useGameshowcase.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/src/hooks/useGameshowcase.ts b/client/src/hooks/useGameshowcase.ts index 8e72149c..aac6a832 100644 --- a/client/src/hooks/useGameshowcase.ts +++ b/client/src/hooks/useGameshowcase.ts @@ -27,9 +27,7 @@ type UiShowcaseGame = Omit< artworks: UiArtwork[]; }; -function getGameCoverUrl( - game_cover_thumbnail: string | null | undefined, -): string { +function getMediaUrl(game_cover_thumbnail: string | null | undefined): string { if (!game_cover_thumbnail) return "/game_dev_club_logo.svg"; if (game_cover_thumbnail.startsWith("http")) return game_cover_thumbnail; // Use environment variable for Django backend base URL @@ -41,11 +39,11 @@ function getGameCoverUrl( function transformApiShowcaseGameToUi(data: ApiShowcaseGame): UiShowcaseGame { return { ...data, - gameCover: getGameCoverUrl(data.game_cover_thumbnail), + gameCover: getMediaUrl(data.game_cover_thumbnail), artworks: data.artworks.map((a) => ({ id: a.art_id, name: a.name, - image: getGameCoverUrl(a.media), + image: getMediaUrl(a.media), sourceGameId: a.source_game_id, })), }; From 755fa9d017884b39d16a822fd850b1dbfd4db158 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sun, 15 Feb 2026 15:42:15 +0000 Subject: [PATCH 22/26] Removed art mockdata and frontend art group filter, using backend serializer for art filtering by game --- client/src/pages/games/index.tsx | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/client/src/pages/games/index.tsx b/client/src/pages/games/index.tsx index 7fba86a6..5450fb35 100644 --- a/client/src/pages/games/index.tsx +++ b/client/src/pages/games/index.tsx @@ -5,7 +5,6 @@ import { SocialIcon } from "react-social-icons"; import GameArtCarousel from "@/components/ui/GameArtCarousel"; import { useGameshowcase } from "@/hooks/useGameshowcase"; -import { mockGameArtworks } from "@/placeholderDataArtGame"; // Mock data export default function HomePage() { const { data: showcases, isPending, isError, error } = useGameshowcase(); @@ -32,18 +31,6 @@ export default function HomePage() { ); } - // Create a lookup table: { gameId: artwork[] } - // O( Art + Game ) complexity - const artworksByGame = mockGameArtworks.reduce< - Record - >((acc, art) => { - if (!acc[art.sourceGameId]) { - acc[art.sourceGameId] = []; - } - acc[art.sourceGameId].push(art); - return acc; - }, {}); - return ( <>
{/* Game Art Carousel */} - {(artworksByGame[showcase.game_id] || []).length > 0 && ( - - )} + {/* Description */}
From b8c95149254b9fce49734d9256764c02bb8af89f Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 21 Feb 2026 04:45:39 +0000 Subject: [PATCH 23/26] Fixed: after merge conflict the get_artworks was in wrong line, now fixed, gameshowcase has artwork now --- server/game_dev/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py index ef71d6aa..3c64a87e 100644 --- a/server/game_dev/serializers.py +++ b/server/game_dev/serializers.py @@ -136,6 +136,9 @@ def get_contributors(self, obj): contributors = GameContributor.objects.filter(game=obj.game) return ShowcaseContributorSerializer(contributors, many=True).data + def get_artworks(self, obj): + return GameArtSerializer(obj.game.game_artwork.all(), many=True).data + class SocialMediaSerializer(serializers.ModelSerializer): class Meta: @@ -145,9 +148,6 @@ class Meta: "socialMediaUserName", ] - def get_artworks(self, obj): - return GameArtSerializer(obj.game.game_artwork.all(), many=True).data - class MemberSerializer(serializers.ModelSerializer): social_media = SocialMediaSerializer( From 29e41fed2f74c11524c165f831d10e18a8315bf8 Mon Sep 17 00:00:00 2001 From: RadinMan Date: Sat, 21 Feb 2026 04:46:35 +0000 Subject: [PATCH 24/26] Added new migration --- ...0008_alter_art_source_game_socialmedia.py} | 28 ++++++++----------- ...10_merge_20260131_1118_0010_socialmedia.py | 13 --------- ...0012_remove_socialmedia_socialmedianame.py | 17 ----------- .../migrations/0013_merge_20260214_1347.py | 14 ---------- 4 files changed, 11 insertions(+), 61 deletions(-) rename server/game_dev/migrations/{0010_socialmedia.py => 0008_alter_art_source_game_socialmedia.py} (59%) delete mode 100644 server/game_dev/migrations/0011_merge_0010_merge_20260131_1118_0010_socialmedia.py delete mode 100644 server/game_dev/migrations/0012_remove_socialmedia_socialmedianame.py delete mode 100644 server/game_dev/migrations/0013_merge_20260214_1347.py diff --git a/server/game_dev/migrations/0010_socialmedia.py b/server/game_dev/migrations/0008_alter_art_source_game_socialmedia.py similarity index 59% rename from server/game_dev/migrations/0010_socialmedia.py rename to server/game_dev/migrations/0008_alter_art_source_game_socialmedia.py index f266b880..9343b231 100644 --- a/server/game_dev/migrations/0010_socialmedia.py +++ b/server/game_dev/migrations/0008_alter_art_source_game_socialmedia.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.14 on 2026-01-31 06:18 +# Generated by Django 5.1.14 on 2026-02-21 03:15 import django.db.models.deletion from django.db import migrations, models @@ -7,10 +7,19 @@ class Migration(migrations.Migration): dependencies = [ - ("game_dev", "0009_merge_20260131_1044"), + ("game_dev", "0007_art_artcontributor_artshowcase"), ] operations = [ + migrations.AlterField( + model_name="art", + name="source_game", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="game_artwork", + to="game_dev.game", + ), + ), migrations.CreateModel( name="SocialMedia", fields=[ @@ -23,21 +32,6 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ( - "socialMediaName", - models.CharField( - choices=[ - ("TWITTER", "Twitter"), - ("FACEBOOK", "Facebook"), - ("INSTAGRAM", "Instagram"), - ("LINKEDIN", "LinkedIn"), - ("GITHUB", "GitHub"), - ("OTHER", "Other"), - ], - default="OTHER", - max_length=20, - ), - ), ("link", models.URLField(max_length=2083)), ("socialMediaUserName", models.CharField(blank=True, max_length=200)), ( diff --git a/server/game_dev/migrations/0011_merge_0010_merge_20260131_1118_0010_socialmedia.py b/server/game_dev/migrations/0011_merge_0010_merge_20260131_1118_0010_socialmedia.py deleted file mode 100644 index 2034020c..00000000 --- a/server/game_dev/migrations/0011_merge_0010_merge_20260131_1118_0010_socialmedia.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 5.1.14 on 2026-02-03 03:27 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("game_dev", "0010_merge_20260131_1118"), - ("game_dev", "0010_socialmedia"), - ] - - operations = [] diff --git a/server/game_dev/migrations/0012_remove_socialmedia_socialmedianame.py b/server/game_dev/migrations/0012_remove_socialmedia_socialmedianame.py deleted file mode 100644 index e51428a5..00000000 --- a/server/game_dev/migrations/0012_remove_socialmedia_socialmedianame.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.14 on 2026-02-07 03:31 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("game_dev", "0011_merge_0010_merge_20260131_1118_0010_socialmedia"), - ] - - operations = [ - migrations.RemoveField( - model_name="socialmedia", - name="socialMediaName", - ), - ] diff --git a/server/game_dev/migrations/0013_merge_20260214_1347.py b/server/game_dev/migrations/0013_merge_20260214_1347.py deleted file mode 100644 index 9a1c3592..00000000 --- a/server/game_dev/migrations/0013_merge_20260214_1347.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 5.1.15 on 2026-02-14 05:47 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('game_dev', '0011_alter_gameshowcase_game'), - ('game_dev', '0012_remove_socialmedia_socialmedianame'), - ] - - operations = [ - ] From e0d84ce09d7e4ab1d1043f589add5638f7cea191 Mon Sep 17 00:00:00 2001 From: Nico Buchanan Date: Sat, 21 Feb 2026 06:19:13 +0000 Subject: [PATCH 25/26] Adjusted the layout on showcase page to match Figma more, with a 4:3 ratio of image to textbox and a scalable gap between the text and contributors. The social icons now align on the right. Mobile layout still needs to be adjusted. --- client/src/pages/games/index.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/src/pages/games/index.tsx b/client/src/pages/games/index.tsx index 5450fb35..769bdf8b 100644 --- a/client/src/pages/games/index.tsx +++ b/client/src/pages/games/index.tsx @@ -61,7 +61,7 @@ export default function HomePage() { className={`flex flex-col gap-8 rounded-xl p-8 ${idx % 2 === 0 ? "lg:flex-row" : "lg:flex-row-reverse"}`} > {/* Left: Cover Image */} -
+
{showcase.gameCover ? ( {/* Right: Details */} -
+
{/* Title of the game */}

@@ -116,6 +116,9 @@ export default function HomePage() { ”

+

+
+

Contributors

@@ -128,15 +131,12 @@ export default function HomePage() { {contributor.name} - - - {contributor.role} + + {contributor.role} {/* Social icons placeholder */} {/* TODO: Add actual links */} - + {/* Social icons using react-social-icons */} Date: Sat, 21 Feb 2026 06:46:02 +0000 Subject: [PATCH 26/26] Showcase page is now responsive with some adjustments to gaps and layout to suit mobile devices. --- client/src/pages/games/index.tsx | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/client/src/pages/games/index.tsx b/client/src/pages/games/index.tsx index 769bdf8b..bab4591b 100644 --- a/client/src/pages/games/index.tsx +++ b/client/src/pages/games/index.tsx @@ -48,17 +48,17 @@ export default function HomePage() { Game Showcase
-
+
{!showcases || showcases.length === 0 ? (

No games available.

) : ( -
+
{showcases.map((showcase, idx) => ( -
+
{/* Game CoverImage + Gameshowcase Detail */}
{/* Left: Cover Image */}
@@ -83,7 +83,7 @@ export default function HomePage() { )}
{/* Right: Details */} -
+
{/* Title of the game */}

@@ -100,7 +100,7 @@ export default function HomePage() {

{/* Comments from committes */} -

+

{/* double quotes from comments */} (

  • - - {contributor.name} - - - {contributor.role} - +
    + + {contributor.name} + + + {contributor.role} + +
    {/* Social icons placeholder */} {/* TODO: Add actual links */} - + {/* Social icons using react-social-icons */}