likwid/frontend/src/pages/users/[username].astro
Marco Allegretti e57819b60f frontend: modify 1 file
Verified changes:
- modify frontend/src/pages/users/[username].astro

Diffstat:
- 1 file changed, 3 insertions(+), 10 deletions(-)
2026-01-30 10:32:36 +01:00

270 lines
6.3 KiB
Text

---
export const prerender = false;
import Layout from '../../layouts/Layout.astro';
import { API_BASE as apiBase } from '../../lib/api';
const { username } = Astro.params;
---
<Layout title={`@${username}`}>
<section class="profile-page">
<div id="profile-content">
<p class="loading">Loading profile...</p>
</div>
</section>
</Layout>
<script define:vars={{ username, apiBase }}>
async function loadProfile() {
const container = document.getElementById('profile-content');
if (!container) return;
try {
const res = await fetch(`${apiBase}/api/users/${username}`);
if (!res.ok) throw new Error('Not found');
const profile = await res.json();
container.innerHTML = `
<div class="profile-header">
<div class="avatar">${profile.username.charAt(0).toUpperCase()}</div>
<div class="info">
<h1>${profile.display_name || profile.username}</h1>
<p class="username">@${profile.username}</p>
<p class="joined">Member since ${new Date(profile.created_at).toLocaleDateString()}</p>
</div>
</div>
<div class="stats ui-card ui-card-pad-lg">
<div class="stat">
<span class="stat-value">${profile.communities.length}</span>
<span class="stat-label">Communities</span>
</div>
<div class="stat">
<span class="stat-value">${profile.proposal_count}</span>
<span class="stat-label">Proposals</span>
</div>
<div class="stat">
<span class="stat-value">${profile.comment_count}</span>
<span class="stat-label">Comments</span>
</div>
</div>
<div class="section">
<h2>Communities</h2>
${profile.communities.length === 0
? '<p class="empty">Not a member of any communities yet</p>'
: `<div class="communities-list">
${profile.communities.map(c => `
<a href="/communities/${c.slug}" class="community-item ui-card ui-card-soft">
<span class="community-name">${c.name}</span>
<span class="community-role role-${c.role}">${c.role}</span>
</a>
`).join('')}
</div>`
}
</div>
<div class="section">
<h2>Recent Votes</h2>
<div id="votes-list" class="votes-list">
<p class="loading-small">Loading...</p>
</div>
</div>
`;
loadVotes();
} catch (error) {
container.innerHTML = '<div class="error">User not found</div>';
}
}
async function loadVotes() {
const container = document.getElementById('votes-list');
if (!container) return;
try {
const res = await fetch(`${apiBase}/api/users/${username}/votes`);
const votes = await res.json();
if (votes.length === 0) {
container.innerHTML = '<p class="empty">No votes yet</p>';
return;
}
container.innerHTML = votes.map(v => `
<a href="/proposals/${v.proposal_id}" class="vote-item ui-card ui-card-soft">
<div class="vote-info">
<span class="vote-proposal">${v.proposal_title}</span>
<span class="vote-community">in ${v.community_name}</span>
</div>
<span class="vote-option">${v.option_label}</span>
</a>
`).join('');
} catch (e) {
container.innerHTML = '<p class="empty">Failed to load votes</p>';
}
}
loadProfile();
</script>
<style>
.profile-page {
padding: 2rem 0;
max-width: 600px;
}
.profile-header {
display: flex;
gap: 1.5rem;
align-items: center;
margin-bottom: 2rem;
}
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
background: var(--color-primary);
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
font-weight: 700;
color: var(--color-on-primary);
}
.info h1 {
font-size: 1.75rem;
margin-bottom: 0.25rem;
}
.username {
color: var(--color-text-muted);
font-size: 1rem;
}
.joined {
color: var(--color-text-muted);
font-size: 0.875rem;
margin-top: 0.25rem;
}
.stats {
display: flex;
gap: 2rem;
border-radius: 12px;
margin-bottom: 2rem;
}
.stat {
text-align: center;
}
.stat-value {
display: block;
font-size: 1.5rem;
font-weight: 700;
color: var(--color-primary);
}
.stat-label {
font-size: 0.75rem;
color: var(--color-text-muted);
text-transform: uppercase;
}
.section {
margin-top: 2rem;
}
.section h2 {
font-size: 1.25rem;
margin-bottom: 1rem;
}
.communities-list {
display: grid;
gap: 0.5rem;
}
.community-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
border-radius: 8px;
text-decoration: none;
color: var(--color-text);
}
.community-item:hover {
border-color: var(--color-primary);
}
.community-name {
font-weight: 500;
}
.community-role {
font-size: 0.625rem;
padding: 0.125rem 0.5rem;
border-radius: 999px;
text-transform: uppercase;
font-weight: 600;
}
.role-admin { background: var(--color-secondary); color: var(--color-on-primary); }
.role-moderator { background: var(--color-info); color: var(--color-on-primary); }
.role-member { background: var(--color-neutral-muted); color: var(--color-on-primary); }
.empty {
color: var(--color-text-muted);
text-align: center;
padding: 2rem;
}
.loading, .error {
text-align: center;
padding: 3rem;
color: var(--color-text-muted);
}
.votes-list {
display: grid;
gap: 0.5rem;
}
.vote-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
border-radius: 8px;
text-decoration: none;
color: var(--color-text);
}
.vote-item:hover {
border-color: var(--color-primary);
}
.vote-proposal {
font-weight: 500;
}
.vote-community {
display: block;
font-size: 0.75rem;
color: var(--color-text-muted);
}
.vote-option {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
background: var(--color-primary);
border-radius: 4px;
color: var(--color-on-primary);
}
</style>