{#each tracks as track (track.id)}
@@ -77,7 +95,7 @@
href={resolve(`/my/hackathon/${hackathonId}/projects`)}
class="text-xs font-semibold text-accent-ink no-underline hover:underline"
>
- View all {projectLabel} →
+ View all {linkLabel} →
{/if}
diff --git a/components/frontend/src/routes/(app)/my/hackathon/[id]/+layout.svelte b/components/frontend/src/routes/(app)/my/hackathon/[id]/+layout.svelte
index 5b38b452..9e67abfe 100644
--- a/components/frontend/src/routes/(app)/my/hackathon/[id]/+layout.svelte
+++ b/components/frontend/src/routes/(app)/my/hackathon/[id]/+layout.svelte
@@ -119,6 +119,7 @@
{participantCount}
organizers={[]}
badges={heroBadges}
+ description={hackathon.description}
/>
{#if phases.length > 0}
diff --git a/components/frontend/src/routes/(app)/my/hackathon/[id]/overview/+page.svelte b/components/frontend/src/routes/(app)/my/hackathon/[id]/overview/+page.svelte
index a09f6f37..a13783a0 100644
--- a/components/frontend/src/routes/(app)/my/hackathon/[id]/overview/+page.svelte
+++ b/components/frontend/src/routes/(app)/my/hackathon/[id]/overview/+page.svelte
@@ -7,6 +7,11 @@
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
+
+ // Whether there is a Projects card at all. With no track to group by and no
+ // approved project to count it would be an empty box holding half the row —
+ // and the way in to proposing one is already a row on the state card above.
+ const showProjects = $derived(data.trackCounts.length > 0 || data.approvedCount > 0);
-
+ mostly-empty rectangles. One column when there is no Projects card, so the
+ team card takes the width rather than leaving half the row empty.
+
+ `items-start`, so the shorter card keeps its own height instead of being
+ stretched to the taller one's and floating in its own whitespace. -->
+
{#if data.myTeam}
{/if}
-
-
-
-
-
About
- {#if data.hackathon.description}
-
{data.hackathon.description}
- {:else}
-
No description provided.
+ {#if showProjects}
+
{/if}
-
+
diff --git a/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/manage/+page.server.ts b/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/manage/+page.server.ts
index 03a06e86..5040f681 100644
--- a/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/manage/+page.server.ts
+++ b/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/manage/+page.server.ts
@@ -37,7 +37,16 @@ export const load: PageServerLoad = async (event) => {
isMe: myUserId !== undefined && m.user!.id === myUserId,
}))
- return { hackathonId: hackathon.id, participants }
+ // The export drops members with no address, since a blank one is a row a
+ // mailing tool rejects (`User.email` is optional and defaults to empty). The
+ // count is surfaced so a file shorter than the roster is not a silent
+ // surprise; the addresses themselves stay out of this payload — the download
+ // endpoint reads them from its own `Get`.
+ const withoutEmail = hackathon.members.filter(
+ (m) => m.user !== undefined && m.user.email === "",
+ ).length
+
+ return { hackathonId: hackathon.id, participants, withoutEmail }
}
/** The gRPC errors both write paths can return, as SvelteKit failures. */
diff --git a/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/manage/+page.svelte b/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/manage/+page.svelte
index 034e0e13..8136f9bc 100644
--- a/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/manage/+page.svelte
+++ b/components/frontend/src/routes/(app)/my/hackathon/[id]/participants/manage/+page.svelte
@@ -1,6 +1,7 @@