mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-06-25 15:37:42 +00:00
278 lines
6.5 KiB
Text
278 lines
6.5 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">
|
||
|
|
<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">
|
||
|
|
<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">
|
||
|
|
<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;
|
||
|
|
padding: 1.5rem;
|
||
|
|
background: var(--color-surface);
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
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;
|
||
|
|
background: var(--color-surface);
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
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;
|
||
|
|
background: var(--color-surface);
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
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>
|