diff --git a/components/frontend/src/lib/components/hackathon/ParticipantCard.svelte b/components/frontend/src/lib/components/hackathon/ParticipantCard.svelte
index d9bdd303..a704baa9 100644
--- a/components/frontend/src/lib/components/hackathon/ParticipantCard.svelte
+++ b/components/frontend/src/lib/components/hackathon/ParticipantCard.svelte
@@ -9,7 +9,7 @@
role: roleProp,
skills = [],
linkedinUrl,
- profileDetailsHref = '#',
+ profileDetailsHref,
actions,
}: {
name: string;
@@ -26,6 +26,8 @@
role?: string;
skills?: string[];
linkedinUrl?: string;
+ /** When unset, no "View" link is rendered — the caller has decided this
+ * viewer has nowhere to go (e.g. a member who can't read others). */
profileDetailsHref?: string;
/**
* Extra controls rendered beside "View" — e.g. an owner's Approve/Remove
@@ -112,8 +114,10 @@
-
- View
+ {#if profileDetailsHref}
+
+ View
+ {/if}
{#if actions}
{@render actions()}
{/if}
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 f9c5050f..cd8f2c30 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
@@ -33,6 +33,7 @@ export const load: PageServerLoad = async (event) => {
return {
participants,
ownerCount,
+ hackathonId: event.params.id,
// 0 means unlimited — the page renders the fullness gauge and the
// over-capacity warning only when a cap is set.
maxParticipants: hackathon.maxParticipants ?? 0,
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 032cdc3d..7fa6023c 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
@@ -76,6 +76,17 @@
function mayDemote(p: Participant): boolean {
return data.mayManage && p.isOwner && !p.isSelf && data.ownerCount > 1;
}
+
+ // "View" opens this person's registration answers. Your own row goes to
+ // your editable form; another person's needs organizer rights, so it is a
+ // read-only ?userId= view offered only to managers — a plain member would
+ // deterministically 403, matching how every organizer-only action here is
+ // hidden rather than left to fail.
+ function viewHref(p: Participant): string | undefined {
+ if (p.isSelf) return `/register/${data.hackathonId}`;
+ if (data.mayManage) return `/register/${data.hackathonId}?userId=${p.id}`;
+ return undefined;
+ }
+ {#if !data.alreadySubmitted}
+