Skip to content

Commit fd43939

Browse files
committed
Merge branch 'main' into issue-143-allow_playable_game_width_and_height_to_be_null
2 parents 7b9b353 + bb11ebe commit fd43939

11 files changed

Lines changed: 148 additions & 39 deletions

File tree

client/src/components/ui/EventDateDisplay.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,31 @@ export function getEventDateParts(dateString: string): EventDateParts | null {
3232
}
3333
}
3434

35+
const getOrdinal = (d: number) => {
36+
if (d > 3 && d < 21) return `${d}th`;
37+
switch (d % 10) {
38+
case 1:
39+
return `${d}st`;
40+
case 2:
41+
return `${d}nd`;
42+
case 3:
43+
return `${d}rd`;
44+
default:
45+
return `${d}th`;
46+
}
47+
};
48+
3549
type EventDateDisplayProps = { date: string };
3650

3751
/** Renders event date as: weekday・ day month・ time. */
3852
export function EventDateDisplay({ date }: EventDateDisplayProps) {
3953
const parts = getEventDateParts(date);
4054
if (!parts) return null;
4155
return (
42-
<div className="flex flex-wrap items-baseline gap-x-1">
43-
<span className="whitespace-nowrap text-primary">
44-
{parts.weekday}
45-
{"・"}
46-
</span>
47-
<span className="whitespace-nowrap text-primary">
48-
{parts.day} {parts.month}
56+
<div className="flex flex-wrap items-baseline gap-x-1 font-firaCode">
57+
<span className="whitespace-nowrap text-primary">{parts.weekday},</span>
58+
<span className="whitespace-nowrap font-medium text-primary">
59+
{getOrdinal(parseInt(parts.day))} {parts.month}
4960
{"・"}
5061
</span>
5162
<span className="text-secondary">{parts.time}</span>

client/src/hooks/useEvent.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ type ApiEvent = {
1111
date: string;
1212
startTime: string | null;
1313
location: string;
14-
cover_image: string | null;
14+
coverImage: string | null;
15+
workshopLink: string;
1516
};
1617

17-
type UiEvent = Omit<ApiEvent, "cover_image"> & {
18+
type UiEvent = Omit<ApiEvent, "coverImage"> & {
1819
coverImage: string;
1920
};
2021

@@ -32,7 +33,7 @@ function normalizeEventId(
3233
function transformApiEventToUiEvent(data: ApiEvent): UiEvent {
3334
return {
3435
...data,
35-
coverImage: data.cover_image ?? "/game_dev_club_logo.svg",
36+
coverImage: data.coverImage ?? "/game_dev_club_logo.svg",
3637
};
3738
}
3839

client/src/hooks/useEvents.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ type ApiEvent = {
1010
publicationDate: string;
1111
date: string;
1212
location: string;
13-
cover_image: string | null;
13+
coverImage: string | null;
14+
workshopLink: string;
1415
};
1516

16-
export type UiEvent = Omit<ApiEvent, "cover_image"> & {
17+
export type UiEvent = Omit<ApiEvent, "coverImage"> & {
1718
coverImage: string;
19+
workshopLink: string;
1820
};
1921

2022
function transformApiEventToUiEvent(data: ApiEvent): UiEvent {
2123
return {
2224
...data,
23-
coverImage: data.cover_image ?? "/game_dev_club_logo.svg",
25+
coverImage: data.coverImage ?? "/game_dev_club_logo.svg",
2426
};
2527
}
2628

client/src/pages/events/[id].tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,25 @@ export default function EventPage() {
5858
<p className="mt-6 text-lg">
5959
<EventDateDisplay date={event.date} />
6060
</p>
61-
<div className="text-primary">{event.location} </div>
61+
<div className="font-firaCode text-sm text-primary">
62+
{event.location}{" "}
63+
</div>
6264
<p className="mt-4 max-w-lg text-base leading-relaxed">
6365
{event.description}
6466
</p>
67+
{event.workshopLink && (
68+
<p className="mt-4 font-firaCode">
69+
<span className="font-medium text-primary">Workshop link:</span>{" "}
70+
<a
71+
href={event.workshopLink}
72+
target="_blank"
73+
rel="noreferrer"
74+
className="underline"
75+
>
76+
{event.workshopLink}
77+
</a>
78+
</p>
79+
)}
6580
</div>
6681
<div className="lg:w-128 relative aspect-[4/3] w-full flex-shrink-0 overflow-hidden rounded-lg bg-gray-700 md:w-96">
6782
<Image

client/src/pages/games/[id].tsx

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import Image from "next/image";
2+
import Link from "next/link";
23
import { useRouter } from "next/router";
34
import React from "react";
45
import { SocialIcon } from "react-social-icons";
56

67
import { GameEmbed } from "@/components/ui/GameEmbed";
78
import { ItchEmbed } from "@/components/ui/ItchEmbed";
9+
import { useEvent } from "@/hooks/useEvent";
810
import { useGame } from "@/hooks/useGames";
911

1012
export default function IndividualGamePage() {
@@ -17,6 +19,9 @@ export default function IndividualGamePage() {
1719
error,
1820
isError,
1921
} = useGame(router.isReady ? id : undefined);
22+
const { data: eventData } = useEvent(
23+
game?.event ? String(game.event) : undefined,
24+
);
2025

2126
if (isPending) {
2227
return (
@@ -55,6 +60,8 @@ export default function IndividualGamePage() {
5560
const gamePlayableID = game.itchGamePlayableID;
5661
const gameWidth = game.itchGameWidth;
5762
const gameHeight = game.itchGameHeight;
63+
const eventID = game.event;
64+
const eventName = eventData?.name || "";
5865

5966
const completionLabels: Record<number, string> = {
6067
1: "WIP",
@@ -65,8 +72,6 @@ export default function IndividualGamePage() {
6572

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

68-
// TODO ADD EVENT
69-
const event = "Game Jam November 2025";
7075
// TODO ADD ARTIMAGES
7176
const artImages: { src: string; alt: string }[] = [];
7277
// const artImages = [
@@ -130,12 +135,12 @@ export default function IndividualGamePage() {
130135
key={c.member_id}
131136
className="flex items-center gap-x-2"
132137
>
133-
<a
138+
<Link
134139
href={`/members/${c.member_id}`}
135140
className="text-primary hover:underline"
136141
>
137142
{c.name}
138-
</a>
143+
</Link>
139144
{Array.isArray(c.social_media) &&
140145
c.social_media.map((sm) => (
141146
<SocialIcon
@@ -157,24 +162,39 @@ export default function IndividualGamePage() {
157162
</td>
158163
<td className="py-1 text-right sm:py-2">{devStage}</td>
159164
</tr>
160-
<tr className="border-b-2 border-gray-300">
161-
<td className="py-1 pr-2 text-muted-foreground sm:py-2">
162-
Host Site
163-
</td>
164-
<td className="py-1 text-right sm:py-2">
165-
<a
166-
href={game.hostURL}
167-
className="text-primary underline hover:underline"
168-
>
169-
{game.hostURL}
170-
</a>
171-
</td>
172-
</tr>
165+
{game.hostURL && (
166+
<tr className="border-b-2 border-gray-300">
167+
<td className="py-1 pr-2 text-muted-foreground sm:py-2">
168+
Host Site
169+
</td>
170+
<td className="py-1 text-right sm:py-2">
171+
<a
172+
href={game.hostURL}
173+
className="text-primary underline hover:underline"
174+
>
175+
{game.hostURL}
176+
</a>
177+
</td>
178+
</tr>
179+
)}
173180
<tr>
174181
<td className="py-1 pr-2 text-muted-foreground sm:py-2">
175182
Event
176183
</td>
177-
<td className="py-1 text-right sm:py-2">{event}</td>
184+
<td className="py-1 text-right sm:py-2">
185+
{eventID && eventName ? (
186+
<Link
187+
href={`/events/${eventID}`}
188+
className="text-primary hover:underline"
189+
>
190+
{eventName}
191+
</Link>
192+
) : (
193+
<span className="text-muted-foreground">
194+
No past/upcoming event
195+
</span>
196+
)}
197+
</td>
178198
</tr>
179199
</tbody>
180200
</table>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.15 on 2026-02-28 14:51
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('game_dev', '0027_remove_game_itchgameembedid_game_itchgameplayableid'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='game',
15+
name='hostURL',
16+
field=models.URLField(blank=True, max_length=2083),
17+
),
18+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.1.15 on 2026-03-01 05:30
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('game_dev', '0028_alter_game_hosturl'),
10+
]
11+
12+
operations = [
13+
migrations.RenameField(
14+
model_name='event',
15+
old_name='cover_image',
16+
new_name='coverImage',
17+
),
18+
migrations.AddField(
19+
model_name='event',
20+
name='workshopLink',
21+
field=models.URLField(blank=True, max_length=2083),
22+
),
23+
]

server/game_dev/models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ class Event(models.Model):
1818
date = models.DateTimeField()
1919
description = models.CharField(max_length=256, blank=True)
2020
publicationDate = models.DateField()
21-
cover_image = models.ImageField(upload_to="events/", null=True)
21+
coverImage = models.ImageField(upload_to="events/", null=True)
2222
location = models.CharField(max_length=256)
23+
workshopLink = models.URLField(max_length=2083, blank=True)
2324

2425
def __str__(self):
2526
return self.name
@@ -54,7 +55,7 @@ class CompletionStatus(models.IntegerChoices):
5455
null=False,
5556
)
5657
active = models.BooleanField(default=True, null=False)
57-
hostURL = models.URLField(max_length=2083)
58+
hostURL = models.URLField(max_length=2083, blank=True)
5859
itchEmbedID = models.PositiveIntegerField(
5960
default=None,
6061
null=True,

server/game_dev/serializers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ class Meta:
1111
"date",
1212
"description",
1313
"publicationDate",
14-
"cover_image",
14+
"coverImage",
1515
"location",
16+
"workshopLink",
1617
]
1718

1819

server/game_dev/tests.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ def setUp(self):
4444
date=self.event_datetime,
4545
description="Yayayyayayay!",
4646
publicationDate=self.pub_date,
47-
cover_image=image_file,
47+
coverImage=image_file,
4848
location="Ezone",
49+
workshopLink="https://example.com/workshop",
4950
)
5051

5152
def test_publication_date_is_date(self):
@@ -57,10 +58,10 @@ def test_publication_date_matches(self):
5758
self.assertEqual(event.publicationDate, self.pub_date)
5859

5960
def test_cover_image_not_empty(self):
60-
self.assertIsNotNone(self.event.cover_image)
61+
self.assertIsNotNone(self.event.coverImage)
6162

6263
def test_cover_image_is_saved_in_correct_folder(self):
63-
self.assertTrue(self.event.cover_image.name.startswith("events/"))
64+
self.assertTrue(self.event.coverImage.name.startswith("events/"))
6465

6566
def test_publication_date_not_empty(self):
6667
self.assertTrue(bool(self.event.publicationDate))
@@ -73,6 +74,10 @@ def test_event_datetime_matches(self):
7374
event = Event.objects.get(pk=self.event.pk)
7475
self.assertEqual(event.date, self.event_datetime)
7576

77+
def test_workshop_link_matches(self):
78+
event = Event.objects.get(pk=self.event.pk)
79+
self.assertEqual(event.workshopLink, "https://example.com/workshop")
80+
7681

7782
class CommitteeModelTest(TestCase):
7883
def setUp(self):

0 commit comments

Comments
 (0)