🐐
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
<script>
|
||||
import { API_URL, WS_URL } from '$lib/api.js';
|
||||
import { apiFetch } from '$lib/api.js';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let profile = $state(null);
|
||||
let loading = $state(true);
|
||||
|
||||
const token = () => localStorage.getItem('token');
|
||||
|
||||
onMount(async () => {
|
||||
if (!token()) { goto('/auth'); return; }
|
||||
const res = await apiFetch(`${API_URL}/profile`);
|
||||
if (res.status === 401) { goto('/auth'); return; }
|
||||
profile = await res.json();
|
||||
loading = false;
|
||||
});
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
goto('/auth');
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
{#if loading}
|
||||
<p class="status">Loading...</p>
|
||||
{:else if profile}
|
||||
<div class="profile">
|
||||
|
||||
<div class="profile-header">
|
||||
<div class="avatar">{profile.username[0].toUpperCase()}</div>
|
||||
<div class="profile-info">
|
||||
<h1 class="username">{profile.username}</h1>
|
||||
<p class="email">{profile.email}</p>
|
||||
<p class="joined">Member since {new Date(profile.created_at).toLocaleDateString('en-GB', { year: 'numeric', month: 'long', day: 'numeric' })}</p>
|
||||
</div>
|
||||
<button class="logout-btn" onclick={logout}>Log Out</button>
|
||||
<a href="/reset-password" class="reset-link">Change password</a>
|
||||
</div>
|
||||
|
||||
<div class="section-divider"></div>
|
||||
|
||||
<h2 class="section-title">Stats</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Wins</span>
|
||||
<span class="stat-value wins">{profile.wins}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Losses</span>
|
||||
<span class="stat-value losses">{profile.losses}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Games Played</span>
|
||||
<span class="stat-value">{profile.wins + profile.losses}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Win Rate</span>
|
||||
<span class="stat-value" class:good-wr={profile.win_rate >= 50} class:bad-wr={profile.win_rate !== null && profile.win_rate < 50}>
|
||||
{profile.win_rate !== null ? `${profile.win_rate}%` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-divider"></div>
|
||||
|
||||
<h2 class="section-title">Highlights</h2>
|
||||
<div class="highlights">
|
||||
<div class="highlight-card">
|
||||
<span class="highlight-label">Most Played Deck</span>
|
||||
{#if profile.most_played_deck}
|
||||
<span class="highlight-value">{profile.most_played_deck.name}</span>
|
||||
<span class="highlight-sub">{profile.most_played_deck.times_played} games</span>
|
||||
{:else}
|
||||
<span class="no-data">No games played yet</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="highlight-card">
|
||||
<span class="highlight-label">Most Played Card</span>
|
||||
{#if profile.most_played_card}
|
||||
<div class="card-preview">
|
||||
{#if profile.most_played_card.image_link}
|
||||
<img src={profile.most_played_card.image_link} alt={profile.most_played_card.name} class="card-thumb" />
|
||||
{/if}
|
||||
<div class="card-preview-info">
|
||||
<span class="highlight-value">{profile.most_played_card.name}</span>
|
||||
<span class="highlight-sub">{profile.most_played_card.times_played} times played</span>
|
||||
<span class="highlight-sub">{profile.most_played_card.card_type} · {profile.most_played_card.card_rarity}</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="no-data">No cards played yet</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Crimson+Text:ital,wght@0,400;0,600;1,400&display=swap');
|
||||
|
||||
main {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
background: #0d0a04;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.profile {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: #3d2507;
|
||||
border: 2px solid #c8861a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #f0d080;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #f0d080;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.email {
|
||||
font-family: 'Crimson Text', serif;
|
||||
font-size: 15px;
|
||||
color: rgba(240, 180, 80, 0.5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.joined {
|
||||
font-family: 'Crimson Text', serif;
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
color: rgba(240, 180, 80, 0.35);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
background: none;
|
||||
border: 1px solid rgba(180, 60, 60, 0.4);
|
||||
border-radius: 4px;
|
||||
color: rgba(200, 80, 80, 0.7);
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
border-color: #c84040;
|
||||
color: #e05050;
|
||||
background: rgba(180, 40, 40, 0.1);
|
||||
}
|
||||
|
||||
.reset-link {
|
||||
font-family: 'Crimson Text', serif;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
color: rgba(240, 180, 80, 0.4);
|
||||
text-decoration: underline;
|
||||
align-self: flex-start;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.reset-link:hover { color: rgba(240, 180, 80, 0.7); }
|
||||
|
||||
.section-divider {
|
||||
height: 1px;
|
||||
background: rgba(107, 76, 30, 0.3);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(240, 180, 80, 0.4);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #1a1008;
|
||||
border: 1px solid rgba(107, 76, 30, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(240, 180, 80, 0.4);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #f0d080;
|
||||
}
|
||||
|
||||
.wins { color: #6aaa6a; }
|
||||
.losses { color: #c85050; }
|
||||
.good-wr { color: #6aaa6a; }
|
||||
.bad-wr { color: #c85050; }
|
||||
|
||||
.highlights {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.highlight-card {
|
||||
background: #1a1008;
|
||||
border: 1px solid rgba(107, 76, 30, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.highlight-label {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(240, 180, 80, 0.4);
|
||||
}
|
||||
|
||||
.highlight-value {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #f0d080;
|
||||
}
|
||||
|
||||
.highlight-sub {
|
||||
font-family: 'Crimson Text', serif;
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
color: rgba(240, 180, 80, 0.45);
|
||||
}
|
||||
|
||||
.card-preview {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-start;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.card-thumb {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(107, 76, 30, 0.4);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-preview-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
font-family: 'Crimson Text', serif;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
color: rgba(240, 180, 80, 0.25);
|
||||
}
|
||||
|
||||
.status {
|
||||
font-family: 'Crimson Text', serif;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
color: rgba(240, 180, 80, 0.5);
|
||||
text-align: center;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.highlights { grid-template-columns: 1fr; }
|
||||
.profile-header { flex-wrap: wrap; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user