diff --git a/client/src/components/main/MemberProfile.tsx b/client/src/components/main/MemberProfile.tsx
index 13e9e434..23bb0cab 100644
--- a/client/src/components/main/MemberProfile.tsx
+++ b/client/src/components/main/MemberProfile.tsx
@@ -1,15 +1,10 @@
"use client";
+import { Palette, Sparkles } from "lucide-react";
import Image from "next/image";
import { SocialIcon } from "react-social-icons";
-// unused atm, as the member isnt linked a project on the backend
-/* export type MemberProfileProject = {
- id: string;
- name: string;
- description?: string;
- href?: string;
-}; */
+import MemberProjectSection from "../ui/MemberProjectSection";
export type MemberProfileData = {
name: string;
@@ -24,7 +19,6 @@ export type MemberProfileData = {
type MemberProfileProps = {
member: MemberProfileData;
- //projects?: MemberProfileProject[];
};
function initialsFromName(name: string) {
@@ -54,7 +48,7 @@ export function MemberProfile({ member }: MemberProfileProps) {
/>
) : (
- {initials}
+
{initials}
)}
@@ -106,23 +100,16 @@ export function MemberProfile({ member }: MemberProfileProps) {
- {/* Template for Projects section */}
-
-
Projects
-
- {/* Div below is a single project card */}
-
-
- {/* Image and/or Link to Project */}
-
-
- {/* Project Title */}
-
-
- {/* Project description */}
-
-
-
+
+
+ Games
+
+
+
+
+ Artwork
+
+
>
);
diff --git a/client/src/components/ui/MemberProjectSection.tsx b/client/src/components/ui/MemberProjectSection.tsx
new file mode 100644
index 00000000..1b5f7e9a
--- /dev/null
+++ b/client/src/components/ui/MemberProjectSection.tsx
@@ -0,0 +1,87 @@
+import { ArrowUpRight } from "lucide-react";
+import Image from "next/image";
+import Link from "next/link";
+import React from "react";
+
+import { useContributor } from "@/hooks/useContributor";
+
+type MemberProjectSectionProps = {
+ id: string;
+};
+
+// From useGamesShowcase
+function getGameCoverUrl(
+ 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
+ const apiBaseUrl =
+ process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
+ return `${apiBaseUrl}${game_cover_thumbnail}`;
+}
+
+export default function MemberProjectSection(props: MemberProjectSectionProps) {
+ const { data: games, isError, error } = useContributor(props.id);
+
+ {
+ /* Error handling from Games Showcase page */
+ }
+ if (isError) {
+ const errorMessage =
+ error?.response?.status === 404
+ ? "Games not found."
+ : "Failed to Load Games";
+ return (
+
+ );
+ }
+
+ return (
+
+ {!games || games.length === 0 ? (
+
+ No games available.
+
+ ) : (
+
+ {games.map((game) => (
+
+
+
+
+
window.open(`/games/${game.game_id}`)}
+ >
+ Visit Game
+
+
+
+ {game.game_data.name}
+
+
+ {game.game_data.description}
+
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/client/src/hooks/useContributor.ts b/client/src/hooks/useContributor.ts
new file mode 100644
index 00000000..d29e7843
--- /dev/null
+++ b/client/src/hooks/useContributor.ts
@@ -0,0 +1,27 @@
+import { useQuery } from "@tanstack/react-query";
+import { AxiosError } from "axios";
+
+import api from "@/lib/api";
+
+type ApiContributorGameData = {
+ name: string;
+ thumbnail: string;
+ description: string;
+};
+
+type ApiContributorGamesList = {
+ game_id: number;
+ role: string;
+ game_data: ApiContributorGameData;
+};
+
+export const useContributor = (member: string | string[] | undefined) => {
+ return useQuery
({
+ queryKey: ["contributor", member],
+ queryFn: async () => {
+ const response = await api.get(`/games/contributor/${member}/`);
+ return response.data;
+ },
+ enabled: !!member,
+ });
+};
diff --git a/server/game_dev/serializers.py b/server/game_dev/serializers.py
index 4d278157..90db3a20 100644
--- a/server/game_dev/serializers.py
+++ b/server/game_dev/serializers.py
@@ -82,6 +82,30 @@ def get_contributors(self, obj):
return ShowcaseContributorSerializer(contributors, many=True).data
+class ContributorGameDataSerializer(serializers.ModelSerializer):
+ # Serializes data in Game model to display on a contributor's profile.
+
+ class Meta:
+ model = Game
+ fields = ('name', 'thumbnail',
+ 'description')
+
+
+class ContributorGameSerializer(serializers.ModelSerializer):
+ # Matches games in the GameContributor model to the information about them in the Game model.
+ game_id = serializers.IntegerField(source='game.id', read_only=True)
+ role = serializers.CharField(read_only=True)
+ game_data = serializers.SerializerMethodField()
+
+ class Meta:
+ model = GameContributor
+ fields = ['game_id', 'role', 'game_data']
+
+ def get_game_data(self, obj):
+ game_data = Game.objects.get(id=obj.game_id)
+ return ContributorGameDataSerializer(game_data).data
+
+
class SocialMediaSerializer(serializers.ModelSerializer):
class Meta:
model = SocialMedia
diff --git a/server/game_dev/urls.py b/server/game_dev/urls.py
index 45a1e362..7e103bf9 100644
--- a/server/game_dev/urls.py
+++ b/server/game_dev/urls.py
@@ -1,11 +1,16 @@
from django.urls import path
-from .views import EventListAPIView, EventDetailAPIView, GamesDetailAPIView, GameshowcaseAPIView, MemberAPIView, CommitteeAPIView
+from .views import ContributorGamesListAPIView, EventListAPIView, EventDetailAPIView
+from .views import GamesDetailAPIView, GameshowcaseAPIView, MemberAPIView, CommitteeAPIView
urlpatterns = [
path("events/", EventListAPIView.as_view(), name="events-list"),
path("events//", EventDetailAPIView.as_view()),
path("games//", GamesDetailAPIView.as_view()),
- path("gameshowcase/", GameshowcaseAPIView.as_view(), name="gameshowcase-api"), # Updated line for GameShowcase endpoint
+ path("games/contributor//",
+ ContributorGamesListAPIView.as_view()),
+ # Updated line for GameShowcase endpoint
+ path("gameshowcase/", GameshowcaseAPIView.as_view(), name="gameshowcase-api"),
path('members//', MemberAPIView.as_view()),
- path("about/", CommitteeAPIView.as_view())
+ path("about/", CommitteeAPIView.as_view()),
+ path('members//', MemberAPIView.as_view())
]
diff --git a/server/game_dev/views.py b/server/game_dev/views.py
index 40ea5457..d1f7c42f 100644
--- a/server/game_dev/views.py
+++ b/server/game_dev/views.py
@@ -1,6 +1,6 @@
from rest_framework import generics
-from .serializers import GamesSerializer, GameshowcaseSerializer, EventSerializer, MemberSerializer
-from .models import Game, GameShowcase, Event, Member, Committee
+from .serializers import ContributorGameSerializer, GamesSerializer, GameshowcaseSerializer, EventSerializer, MemberSerializer
+from .models import Game, GameContributor, GameShowcase, Event, Member, Committee
from django.utils import timezone
from rest_framework.views import APIView
from rest_framework.response import Response
@@ -70,6 +70,20 @@ def get(self, request):
return Response(serializer.data)
+class ContributorGamesListAPIView(APIView):
+ """
+ GET /api/games/contributor//
+ Returns the games a particular member has contributed to.
+ """
+ lookup_url_kwarg = "member"
+
+ def get(self, request, member):
+ contributions = GameContributor.objects.filter(
+ member=self.kwargs["member"])
+ serializer = ContributorGameSerializer(contributions, many=True)
+ return Response(serializer.data)
+
+
class MemberAPIView(generics.RetrieveAPIView):
serializer_class = MemberSerializer
lookup_field = "id"