Files
streba/src/routes/users/[username]/+page.svelte
T

148 lines
3.7 KiB
Svelte

<script lang="ts">
import Avatar from '$lib/components/Avatar.svelte';
import ActivityItem from '$lib/components/ActivityItem.svelte';
import StatTile from '$lib/components/StatTile.svelte';
import { fmtDistance, fmtDuration, fmtElapsed, fmtElevation } from '$lib/format';
let { data } = $props();
const p = $derived(data.profile);
const memberSince = $derived(
new Date(p.created_at + 'Z').toLocaleDateString('en-GB', { month: 'long', year: 'numeric' })
);
</script>
<svelte:head>
<title>{p.username} · Streba</title>
</svelte:head>
<div class="head card">
<Avatar avatar={p.avatar} username={p.username} size={88} />
<div class="who">
<h1>{p.username}</h1>
<p class="meta">
{#if p.location}{p.location} · {/if}member since {memberSince}
</p>
{#if p.bio}<p class="bio">{p.bio}</p>{/if}
</div>
</div>
<div class="kpis">
<StatTile label="Activities" value={String(data.stats.activities)} />
<StatTile label="Distance" value={fmtDistance(data.stats.distance_m)} />
<StatTile label="Ascent" value={fmtElevation(data.stats.elev_gain_m)} />
<StatTile label="Moving time" value={fmtDuration(data.stats.moving_s)} />
</div>
{#if data.peaksReached > 0}
<div class="card peak-strip">
{data.peaksReached} peak{data.peaksReached === 1 ? '' : 's'} reached{#if data.highestAscent},
highest: <strong>{data.highestAscent.name}</strong>
({Math.round(data.highestAscent.elevation_m ?? 0).toLocaleString('en-US')} m){/if}.
</div>
{/if}
{#if data.segments.length > 0}
<h2>Segments</h2>
<div class="card seg-list">
{#each data.segments as segment (segment.id)}
<a class="seg-row" href="/segments/{segment.id}">
<span class="seg-name">
{#if segment.rank === 1}<span class="crown" title="Course record">👑</span>{/if}
{segment.name}
</span>
<span class="seg-meta">
{fmtDistance(segment.distance_m)} · {segment.attempts}
{segment.attempts === 1 ? 'attempt' : 'attempts'}
{#if segment.best_s !== null}
· best {fmtElapsed(segment.best_s)}{#if segment.best_type}&nbsp;({segment.best_type}){/if}
{#if segment.rank !== null}· #{segment.rank}{/if}
{/if}
</span>
</a>
{/each}
</div>
{/if}
<h2>Recent activities</h2>
{#if data.activities.length === 0}
<div class="card empty">No activities yet.</div>
{:else}
<div class="card list">
{#each data.activities as activity (activity.id)}
<ActivityItem {activity} />
{/each}
</div>
{/if}
<style>
.head {
display: flex;
gap: 1.25rem;
align-items: flex-start;
padding: 1.5rem;
margin-bottom: 1.25rem;
}
.who h1 {
margin: 0;
}
.meta {
margin: 0.15rem 0 0;
color: var(--text-muted);
font-size: 0.85rem;
}
.bio {
margin: 0.6rem 0 0;
color: var(--text-secondary);
font-size: 0.92rem;
white-space: pre-line;
}
.kpis {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 0.75rem;
}
.peak-strip {
margin-top: 1.25rem;
padding: 0.85rem 1.15rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
.peak-strip strong {
color: var(--text-primary);
}
.list :global(.item:not(:last-child)) {
border-bottom: 1px solid var(--border);
}
.seg-list > .seg-row:not(:last-child) {
border-bottom: 1px solid var(--border);
}
.seg-row {
display: flex;
flex-direction: column;
padding: 0.75rem 1.15rem;
text-decoration: none;
color: inherit;
transition: background 120ms;
}
.seg-row:hover {
background: var(--wash);
}
.seg-name {
font-weight: 600;
font-size: 0.92rem;
}
.crown {
font-size: 0.85rem;
}
.seg-meta {
font-size: 0.8rem;
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
}
.empty {
padding: 1.5rem;
text-align: center;
color: var(--text-secondary);
}
</style>