From 88cdb83d66d7e29ff7bf068a093373caeb28200d Mon Sep 17 00:00:00 2001 From: Sabine Maennel <5292683+sabinem@users.noreply.github.com> Date: Fri, 21 Aug 2026 18:28:06 +0200 Subject: [PATCH] feat(frontend): add a participant detail page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The participants list is a name and a role chip, which is not enough to know who you are about to team up with. Each row gets a View link onto a profile page — the link ParticipantCard's `actions` snippet was always shaped for. No new RPC for identity: the hackathon layout already loads every member with their casbin role, waitlist flag and join date, so the page is a projection of a list the viewer has already been granted. Teams need TeamService.List, which gates on the same hackathon:read the layout just passed. Email is left off. User carries one, but it is shown only on the platform admin page today, never to a peer, and this page is readable by every confirmed member. A waitlisted id 404s rather than resolving, so the route cannot become a way around the list's own privacy filter. Sparse by necessity — affiliation, bio, avatar, skills and LinkedIn have nowhere to come from until User grows the fields (TODO(backend: user-profile-fields)). Nothing is stubbed for them: an empty About card would read as "this person filled nothing in" rather than "the platform cannot ask yet". --- .../hackathon/ParticipantCard.svelte | 7 +- .../[id]/participants/+page.server.ts | 4 +- .../hackathon/[id]/participants/+page.svelte | 19 +++- .../[participantId]/+page.server.ts | 73 ++++++++++++ .../participants/[participantId]/+page.svelte | 107 ++++++++++++++++++ 5 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 components/frontend/src/routes/(app)/my/hackathon/[id]/participants/[participantId]/+page.server.ts create mode 100644 components/frontend/src/routes/(app)/my/hackathon/[id]/participants/[participantId]/+page.svelte diff --git a/components/frontend/src/lib/components/hackathon/ParticipantCard.svelte b/components/frontend/src/lib/components/hackathon/ParticipantCard.svelte index 03d6facb..425ac1d8 100644 --- a/components/frontend/src/lib/components/hackathon/ParticipantCard.svelte +++ b/components/frontend/src/lib/components/hackathon/ParticipantCard.svelte @@ -29,9 +29,10 @@ * the caller so this card stays ignorant of hackathon roles and * permissions. * - * There is no "View" link any more: participant profiles have no page of - * their own, and a button onto `#` is a control that looks live and does - * nothing. Add one back when a profile route exists. + * The participants list passes a View link onto + * `/my/hackathon/[id]/participants/[participantId]` this way; the card + * itself is not a link, so a caller stays free to render no control at + * all. */ actions?: Snippet; } = $props(); diff --git a/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/+page.server.ts b/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/+page.server.ts index 0f5d57ff..605f1f2c 100644 --- a/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/+page.server.ts +++ b/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/+page.server.ts @@ -27,5 +27,7 @@ export const load: PageServerLoad = async (event) => { roleLabel: membershipBadgeLabel(m.isWaiting, m.role), })) - return { participants } + // The route id is returned rather than read from `$page` in the component, so + // the View link's target comes from the same load that produced the rows. + return { hackathonId: event.params.id, participants } } diff --git a/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/+page.svelte b/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/+page.svelte index 3f548ff9..fa26b04a 100644 --- a/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/+page.svelte +++ b/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/+page.svelte @@ -1,4 +1,5 @@ + + +
@{data.participant.username}
++ Teams could not be loaded. Reload the page to try again. +
+ {:else if data.teams.length === 0} ++ {data.participant.name} is not on a team in this hackathon yet. +
+ {:else} + {#each data.teams as team (team.id)} + + {team.name} + {#if team.projectTitle} + {team.projectTitle} + {/if} + + {/each} + {/if} +