--- import AppLayout from '../layouts/AppLayout.astro'; import ActivityTicker from '../components/ActivityTicker.astro'; import CouncilMark from '../components/CouncilMark.astro'; import { getOpenPulse, getPulseWithCounts, castVote, recordActivity, getRecentActivity, getPulseById, getEventById, getRoadmapItem, getAllRoadmapItems, getUpcomingEvents, getAllUsersPublic, getLitQuarters, countShippedAttributions, getUserVote, } from '../lib/db'; import { tickerItem, pulseDateLabel, timeOfDay, tenureSince, redactName } from '../lib/format'; const user = Astro.locals.user; // ── POST: cast vote ──────────────────────────────────────────────── if (Astro.request.method === 'POST') { const data = await Astro.request.formData(); const action = String(data.get('action') ?? ''); if (action === 'vote') { const pulseId = Number(data.get('pulse_id')); const optionIndex = Number(data.get('option_index')); const target = getPulseById(pulseId); if (target && target.status === 'open' && Number.isInteger(optionIndex) && optionIndex >= 0 && optionIndex < target.options.length) { const existing = getUserVote(pulseId, user.id); if (existing === null) { castVote(pulseId, user.id, optionIndex); recordActivity(user.id, 'voted', 'pulse', pulseId); } } return Astro.redirect('/pulse'); } } // ── Greeting ─────────────────────────────────────────────────────── const firstName = user.name.split(' ')[0]; const greeting = `Good ${timeOfDay()}, ${firstName}.`; const dateLabel = pulseDateLabel(); const tenureAnchor = user.role === 'cab' && user.cab_joined_date ? user.cab_joined_date : user.created_at; const tenure = tenureSince(tenureAnchor); // ── Activity ticker ──────────────────────────────────────────────── const activity = getRecentActivity({ limit: 12, sinceDays: 7 }); const tickerItems = activity.map(row => { let label: string | null = null; switch (row.subject_type) { case 'pulse': { const p = getPulseById(row.subject_id); label = p?.question ?? null; break; } case 'event': { const e = getEventById(row.subject_id); label = e?.title ?? null; break; } case 'roadmap': { const r = getRoadmapItem(row.subject_id); label = r?.title ?? null; break; } } return tickerItem(row, label); }); // ── This week's Pulse ────────────────────────────────────────────── const openPulseRaw = getOpenPulse(); const totalMembers = getAllUsersPublic().filter(u => u.role === 'cab').length; const openPulse = openPulseRaw ? getPulseWithCounts(openPulseRaw.id, user.id) : null; // Time-left label: "32 seconds" / "3 hours" / "2 days" — soft countdown function timeLeftLabel(closesAt: string): string { const ms = new Date(closesAt).getTime() - Date.now(); if (ms <= 0) return 'closing now'; const d = Math.floor(ms / 86400000); if (d >= 1) return `${d} day${d === 1 ? '' : 's'}`; const h = Math.floor(ms / 3600000); if (h >= 1) return `${h} hour${h === 1 ? '' : 's'}`; const m = Math.floor(ms / 60000); if (m >= 1) return `${m} minute${m === 1 ? '' : 's'}`; const s = Math.floor(ms / 1000); return `${s} seconds`; } function closeDayLabel(closesAt: string): string { const d = new Date(closesAt); return new Intl.DateTimeFormat('en-GB', { weekday: 'long', timeZone: 'Europe/Copenhagen', }).format(d); } // ── Roadmap preview (3 most-recently-updated items) ──────────────── const roadmapPreview = getAllRoadmapItems() .sort((a, b) => (b.updated_at > a.updated_at ? 1 : -1)) .slice(0, 3); function roadmapStatusDot(status: 'shipping' | 'beta' | 'exploring'): string { return ({ shipping: 'var(--pigment-copper)', beta: 'var(--pigment-ochre)', exploring: 'var(--on-surface-muted)', })[status]; } function roadmapStatusBlurb(item: { status: 'shipping' | 'beta' | 'exploring'; target: string | null; attributed: unknown[] }): string { const target = item.target ? ` · ${item.target}` : ''; switch (item.status) { case 'shipping': return `Shipping${target}`; case 'beta': return `In beta${target}`; case 'exploring': return `Exploring${target}`; } } // ── Council mark + stat ──────────────────────────────────────────── const litCount = getLitQuarters(user.id).size; const shippedCount = countShippedAttributions(user.id); // ── Members in the room ──────────────────────────────────────────── const allMembers = getAllUsersPublic(); // SQL stores 'YYYY-MM-DD HH:MM:SS' UTC; new Date() would parse as local — coerce to UTC ISO first. function sqlToUtcDate(s: string): Date { if (/T.*[Zz]$/.test(s) || /[+-]\d{2}:?\d{2}$/.test(s)) return new Date(s); return new Date(s.replace(' ', 'T') + 'Z'); } const onlineOthers = allMembers.filter(u => u.id !== user.id && u.last_seen_at && (Date.now() - sqlToUtcDate(u.last_seen_at).getTime()) < 5 * 60_000 ); const visibleChips = onlineOthers.slice(0, 4); const overflowCount = Math.max(0, onlineOthers.length - visibleChips.length); function initials(name: string): string { const parts = name.trim().split(/\s+/); if (parts.length === 1) return (parts[0][0] ?? '').toUpperCase(); return ((parts[0][0] ?? '') + (parts[parts.length - 1][0] ?? '')).toUpperCase(); } // ── Events row ───────────────────────────────────────────────────── const upcoming = getUpcomingEvents(20); const nextExclusive = upcoming.find(e => e.kind === 'dinner' || e.kind === 'summit') ?? null; const nextOfficeHours = upcoming.find(e => e.kind === 'office_hours') ?? null; function formatEventDate(iso: string): string { return new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'long', timeZone: 'Europe/Copenhagen', }).format(new Date(iso)).toUpperCase(); } ---

{dateLabel}

{greeting}

You've been a member for {tenure}. The team is reading every note you leave.

{tickerItems.length > 0 && (
)}
{openPulse ? ( <>
This week's pulse · closes in {timeLeftLabel(openPulse.closes_at)}

{openPulse.question}

{openPulse.context &&

{openPulse.context}

}
{openPulse.options.map((opt, i) => { const chosen = openPulse.my_vote === i; const count = openPulse.votes_by_option[i] ?? 0; const pct = openPulse.votes_total > 0 ? (count / openPulse.votes_total) * 100 : 0; const locked = openPulse.my_vote !== null; const letter = String.fromCharCode(65 + i); // A/B/C/D return ( ); })}

{openPulse.votes_total} of {totalMembers} council member{totalMembers === 1 ? '' : 's'} weighed in. Closes {closeDayLabel(openPulse.closes_at)}.

) : (
This week's pulse

No pulse is open right now. The next one drops soon.

)}

From the roadmap

{roadmapPreview.length === 0 ? (

No roadmap items yet.

) : (
    {roadmapPreview.map(item => (
  • {item.title}

    {roadmapStatusBlurb(item)}

  • ))}
)} See the full roadmap →
{visibleChips.length > 0 && (
{onlineOthers.length} other{onlineOthers.length === 1 ? '' : 's'} online now
{visibleChips.map(m => ( {redactName(m.name)} {m.organisation} ))} {overflowCount > 0 && ( +{overflowCount} others )}
)} {(nextExclusive || nextOfficeHours) && (
{nextExclusive && (

Members only · {formatEventDate(nextExclusive.starts_at)}

{nextExclusive.title}

{nextExclusive.description}

{nextExclusive.capacity && (

{nextExclusive.capacity} seats · invitation by hand

)}
)} {nextOfficeHours && (

Office hours · {formatEventDate(nextOfficeHours.starts_at)}

{nextOfficeHours.title}

{nextOfficeHours.description}

)}
)}