project-bifrost-platform/src/pages/participants.astro

185 lines
4.8 KiB
Text

---
import AppLayout from '../layouts/AppLayout.astro';
import { getAllUsersPublic } from '../lib/db';
import type { UserPublic, Role } from '../lib/db';
const user = Astro.locals.user;
const allUsers = getAllUsersPublic().filter((u) => u.active);
// Group by organisation
const orgs = new Map<string, UserPublic[]>();
for (const u of allUsers) {
if (!orgs.has(u.organisation)) orgs.set(u.organisation, []);
orgs.get(u.organisation)!.push(u);
}
const roleLabels: Record<Role, string> = {
pilot: 'Pilot',
cab: 'CAB',
fenja: 'Fenja',
};
const roleColors: Record<Role, string> = {
pilot: 'var(--pigment-copper)',
cab: 'var(--pigment-indigo)',
fenja: 'var(--secondary)',
};
---
<AppLayout title="Participants" user={user}>
<div class="page">
<header class="page-header">
<p class="label-sm eyebrow">Participants</p>
<h1 class="display-md page-title">The people.</h1>
<p class="lead subtitle">
Everyone in the Bifrost pilot — who they are, where they are from,
and what they bring.
</p>
</header>
<div class="orgs">
{[...orgs.entries()].map(([orgName, members]) => (
<section class="org-section">
<h2 class="headline-sm org-name">{orgName}</h2>
<ul class="member-list">
{members.map((member) => (
<li class="member-card">
<div class="member-header">
<span class="body-md member-name">{member.name}</span>
<span
class="role-badge label-sm"
style={`color: ${roleColors[member.role]}`}
>
{roleLabels[member.role]}
</span>
</div>
{member.bio && (
<p class="body-sm member-bio">{member.bio}</p>
)}
{member.id === user.id && (
<a href="/account" class="edit-bio-link label-sm">
Edit your bio
</a>
)}
</li>
))}
</ul>
</section>
))}
</div>
</div>
</AppLayout>
<style>
.page {
padding: var(--space-12) var(--space-20) var(--space-16);
max-width: var(--content-max);
margin: 0 auto;
}
/* ── Header ──────────────────────────────────────────────────────── */
.page-header {
max-width: 44rem;
margin-bottom: var(--space-12);
}
.eyebrow {
letter-spacing: var(--tracking-wider);
text-transform: uppercase;
color: var(--on-surface-muted);
margin-bottom: var(--space-3);
}
.page-title { margin-bottom: var(--space-5); }
.subtitle {
color: var(--on-surface-variant);
max-width: var(--reading-max);
margin: 0;
}
/* ── Orgs ────────────────────────────────────────────────────────── */
.orgs {
display: flex;
flex-direction: column;
gap: var(--space-12);
max-width: 52rem;
}
.org-section {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.org-name {
font-family: var(--font-serif);
font-weight: 400;
letter-spacing: var(--tracking-snug);
margin: 0;
padding-bottom: var(--space-4);
border-bottom: var(--ghost-border);
color: var(--on-surface);
}
/* ── Member cards ────────────────────────────────────────────────── */
.member-list {
list-style: none;
padding: 0;
margin: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
gap: var(--space-4);
}
.member-card {
background: var(--surface-container-lowest);
border-radius: var(--radius-md);
padding: var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.member-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--space-3);
}
.member-name {
font-weight: 600;
color: var(--on-surface);
margin: 0;
}
.role-badge {
letter-spacing: var(--tracking-wide);
text-transform: uppercase;
font-weight: 600;
flex-shrink: 0;
}
.member-bio {
color: var(--on-surface-variant);
margin: 0;
line-height: var(--leading-relaxed);
}
.edit-bio-link {
color: var(--on-surface-muted);
letter-spacing: var(--tracking-wide);
text-transform: uppercase;
text-decoration: none;
border-bottom: none;
transition: color var(--duration-fast) var(--ease-standard);
align-self: flex-start;
}
.edit-bio-link:hover {
color: var(--on-surface-variant);
border-bottom: none;
}
</style>