Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4d61e54
Add MemberProfile component and MemberPage
KKatariah Jan 10, 2026
0a8d588
u
KKatariah Jan 10, 2026
06b7b19
configured routing to /members/{id}
Jan 17, 2026
3352c3d
merge main into issue-10-Member_Pages
phillipnguyenn Jan 17, 2026
a48d786
created basic profile outline
Jan 17, 2026
316a94f
Merge branch 'main' into issue-10-Member_pages
phillipnguyenn Jan 17, 2026
2cb85a3
Merge branch 'main' for PR #50
phillipnguyenn Jan 17, 2026
579dee3
added styling for profile outline
Jan 17, 2026
276f6f3
Added URL and API View for retrieval of a member
phillipnguyenn Jan 17, 2026
3aad28a
improved styling for profile outline
Jan 17, 2026
509999e
created hook to fetch member onto member profile component
phillipnguyenn Jan 17, 2026
5b6b406
Merge branch 'issue-10-Member_pages' of https://github.com/codersforc…
phillipnguyenn Jan 17, 2026
de23154
tried to get copilot to add some stuff so we can actually use the tem…
KKatariah Jan 17, 2026
c0ff440
idk bruh merge conflict
KKatariah Jan 17, 2026
83355a8
restore working state from last good snapshot, kept additional files …
phillipnguyenn Jan 18, 2026
e254ef9
add blank option to profile_picture field in Member model
phillipnguyenn Jan 18, 2026
9b8b4c9
refactor: memberpage loads data to pass -> memberprofile component. c…
phillipnguyenn Jan 18, 2026
a96e86c
updated member serializer to return active status
phillipnguyenn Jan 18, 2026
8c78c98
added active status handling in MemberProfile and update useMember hook
phillipnguyenn Jan 18, 2026
1f5f322
refactor: inactive member profile handling moved to api level, page h…
phillipnguyenn Jan 19, 2026
9c0ecfb
updated error msg for inactive member to provide better clarity
phillipnguyenn Jan 19, 2026
57120db
added frame to member profile image
Jan 19, 2026
1973bfd
added nifty improvement comment for inactive routing (issue #46)
phillipnguyenn Jan 19, 2026
c2329ff
Merge branch 'issue-10-Member_pages' of https://github.com/codersforc…
phillipnguyenn Jan 19, 2026
2559d93
fixed frame image error
Jan 19, 2026
c18be58
Merge branch 'issue-10-Member_pages' of https://github.com/codersforc…
Jan 19, 2026
7dacb5f
added rough outline of projects section to member page, slightly modi…
Jan 21, 2026
7f34a91
Merge branch 'main' into issue-10-Member_pages
Jan 22, 2026
76ba876
modified styling for projects section
Jan 24, 2026
9ee1fc5
updated styling and spacing for member page
Jan 24, 2026
b10b225
changed text colour for profile header
Jan 24, 2026
5575d80
updated backend to expose and return only active member objects
phillipnguyenn Jan 24, 2026
34a4193
changed page to sanitise router query before passing to hook
phillipnguyenn Jan 25, 2026
1de20e3
updated component to match return of member hook
phillipnguyenn Jan 25, 2026
27b6a3d
removed error handling on member hook and only enabled when receiving…
phillipnguyenn Jan 25, 2026
d0d0a93
added comments for member hook clarification
phillipnguyenn Jan 25, 2026
94c362d
changed frame image to svg
Jan 25, 2026
3dbe102
changed styling to use colours defined in globals.css
Jan 25, 2026
5825df8
improved spacing for new svg image
Jan 25, 2026
f12a206
removed placeholder text in projects section
Jan 25, 2026
b7d1188
removed unnecessary divs
Jan 25, 2026
1a94d7b
restored projects section title
Jan 25, 2026
69ff26c
Merge branch 'main' into issue-10-Member_pages
belledw Jan 26, 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
9 changes: 9 additions & 0 deletions client/public/frame.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 94 additions & 0 deletions client/src/components/main/MemberProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"use client";

import Image from "next/image";

// unused atm, as the member isnt linked a project on the backend
/* export type MemberProfileProject = {
id: string;
name: string;
description?: string;
href?: string;
}; */

export type MemberProfileData = {
name: string;
about: string;
pronouns?: string;
profile_picture?: string;
};

type MemberProfileProps = {
member: MemberProfileData;
//projects?: MemberProfileProject[];
};

function initialsFromName(name: string) {
return name
.trim()
.split(/\s+/)
.slice(0, 2)
.map((part) => part[0]?.toUpperCase())
.join("");
}

export function MemberProfile({ member }: MemberProfileProps) {
const initials = initialsFromName(member.name);

return (
<>
<div className="m-auto h-fit bg-card text-light_2">
<div className="mx-2 flex flex-wrap items-center justify-center gap-y-5 py-7 lg:mx-10">
<div className="grid grid-cols-1 grid-rows-1 items-center justify-items-center lg:mr-6">
<div className="absolute size-32 overflow-clip bg-accent text-center">
{member.profile_picture ? (
<Image
src={member.profile_picture}
alt={`${member.name} profile picture`}
fill
className="object-cover"
/>
) : (
<div className="flex h-full w-full items-center justify-center font-jersey10 text-5xl text-muted-foreground">
{initials}
</div>
)}
</div>
<Image
src="/frame.svg"
alt="golden pixel art frame around profile picture"
width={176}
height={192}
className="z-0 h-48 w-44"
/>
</div>
<div className="flex w-4/5 flex-col gap-2 rounded-md p-2.5 font-firaCode">
<div className="flex">
<p className="min-w-fit font-jersey10 text-4xl">{member.name}</p>
<hr className="ml-5 hidden w-full self-center border-light_2 lg:flex" />
</div>
<p className="text-lg">{member.pronouns}</p>
<p>{member.about}</p>
</div>
</div>
</div>
{/* Template for Projects section */}
<div className="m-auto min-h-80 w-11/12">
<h2 className="mt-7 text-center font-jersey10 text-5xl">Projects</h2>
<div className="m-auto my-5 flex flex-wrap justify-center gap-8">
{/* Div below is a single project card */}
<div className="w-fit rounded-md p-5">
<div className="mb-2 h-44 w-96 overflow-clip rounded-md p-5 text-neutral_1">
{/* Image and/or Link to Project */}
</div>
<p className="max-w-96 font-firaCode text-xl font-semibold">
{/* Project Title */}
</p>
<p className="line-clamp-1 max-w-96 font-firaCode text-[--light-3]">
{/* Project description */}
</p>
</div>
</div>
</div>
</>
);
}
22 changes: 22 additions & 0 deletions client/src/hooks/useMember.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useQuery } from "@tanstack/react-query";

import api from "@/lib/api";

type ApiMember = {
name: string;
about: string;
pronouns?: string;
profile_picture?: string;
};

// return api member, import id number from router, is not enabled if not a number type
export const useMember = (id?: number) => {
return useQuery<ApiMember>({
queryKey: ["member", id],
queryFn: async () => {
const response = await api.get(`/members/${id}/`);
return response.data;
},
enabled: Number.isFinite(id),
});
};
40 changes: 40 additions & 0 deletions client/src/pages/members/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useRouter } from "next/router";

import { useMember } from "@/hooks/useMember";

import { MemberProfile } from "../../components/main/MemberProfile";

// hook assumes correct input, page sanitises to correct type
function normaliseId(id: string | string[] | number | undefined) {
if (typeof id === "number" && Number.isFinite(id)) {
return id;
}

if (typeof id === "string") {
const parsed = Number(id);
return Number.isFinite(parsed) ? parsed : undefined;
}

return undefined;
}

export default function MemberPage() {
const router = useRouter();
const id = normaliseId(router.query.id);

const {
data: member,
isPending,
isError,
} = useMember(router.isReady ? id : undefined);

if (isPending) {
return null;
}

if (isError || !member) {
return <p>Member not found</p>;
}

return <MemberProfile member={member} />;
}
1 change: 1 addition & 0 deletions client/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

--light-1: hsl(0 0% 100%);
--light-2: hsl(236.25, 96%, 90.2%);
--light-3: hsl(235, 96%, 80%);
--light-alt: hsl(183 100% 79%);
--logo-blue-1: hsl(236 62% 95%);

Expand Down
18 changes: 18 additions & 0 deletions server/game_dev/migrations/0005_alter_member_profile_picture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 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/"),
),
]
2 changes: 1 addition & 1 deletion server/game_dev/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
class Member(models.Model):
name = models.CharField(max_length=200)
active = models.BooleanField(default=True)
profile_picture = models.ImageField(upload_to="profiles/", null=True)
profile_picture = models.ImageField(upload_to="profiles/", null=True, blank=True)
about = models.CharField(max_length=256, blank=True)
pronouns = models.CharField(max_length=20, blank=True)

Expand Down
2 changes: 1 addition & 1 deletion server/game_dev/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ class Meta:
"name",
"profile_picture",
"about",
"pronouns"
"pronouns",
]
3 changes: 2 additions & 1 deletion server/game_dev/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.urls import path
from .views import EventListAPIView, EventDetailAPIView
from .views import EventListAPIView, EventDetailAPIView, MemberAPIView

urlpatterns = [
path("events/", EventListAPIView.as_view(), name="events-list"),
path("events/<int:id>/", EventDetailAPIView.as_view()),
path('members/<int:id>/', MemberAPIView.as_view())
]
12 changes: 10 additions & 2 deletions server/game_dev/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

# Create your views here.
from rest_framework import generics
from .models import Event
from .serializers import EventSerializer
from .models import Event, Member
from .serializers import EventSerializer, MemberSerializer
from django.utils import timezone
from rest_framework.exceptions import ValidationError
from rest_framework.pagination import PageNumberPagination
Expand Down Expand Up @@ -51,3 +51,11 @@ class EventDetailAPIView(generics.RetrieveAPIView):

def get_queryset(self):
return Event.objects.filter(id=self.kwargs["id"])


class MemberAPIView(generics.RetrieveAPIView):
serializer_class = MemberSerializer
lookup_field = "id"

def get_queryset(self):
return Member.objects.filter(active=True)