Files
music-hub/apps/web/src/routes/(app)/account/+page.svelte
Robin Choice 8bf72c2482 Full MVP: workspace layout, visual refresh, PWA, production deploy
Major changes since initial commit:

Schema: version branching (parentVersionId, branchLabel), share links,
guest comments, track status enum (sketch/in_progress/final/released),
track sections, cover art for projects and tracks.

API: 29+ endpoints — auth, projects, tracks, versions, comments, share
links (public + management), uploads (cover), activity feed, onboarding
demo seed. Email templates in German with brand styling.

Web: SvelteKit 5 workspace layout with persistent sidebar, breadcrumb
top-bar, collapsible right panel. SoundCloud-style waveform player with
round play button, avatar comment markers, keyboard shortcuts (Space/JKL/C).
Full German UI. Cover art with gradient fallback. Track status pills.
Activity feed dashboard. Welcome modal with demo-seed trigger. Landing
page with 7-section scroll layout. Login on /login. Public /listen/:token
page for guest feedback.

Visual: Inter Variable font, Magenta→Orange gradient accent, warm dark
neutrals, Lucide-style inline SVG icon set, spring animations on modals,
glass-effect toasts, responsive from 360px to 2560px+.

PWA: manifest, service worker, icons, iOS/Android installable.

Production: adapter-node, server-side API proxy hook, docker-compose with
Postgres + MinIO + auto-migration + health checks. Env example included.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:47:48 +02:00

116 lines
2.7 KiB
Svelte

<script lang="ts">
import { user } from '$lib/stores/auth.js';
import { api } from '$lib/api/client.js';
import { toastSuccess } from '$lib/stores/toast.js';
import Button from '$lib/components/ui/Button.svelte';
import Input from '$lib/components/ui/Input.svelte';
import Avatar from '$lib/components/ui/Avatar.svelte';
import TopBar from '$lib/components/workspace/TopBar.svelte';
let name = $state('');
let saving = $state(false);
$effect(() => {
if ($user && !name) name = $user.name;
});
async function save() {
if (!name.trim()) return;
saving = true;
try {
const res = await api.patch<{ user: typeof $user }>('/auth/me', { name: name.trim() });
user.set(res.user);
toastSuccess('Profil gespeichert');
} finally {
saving = false;
}
}
</script>
<TopBar crumbs={[{ label: 'Konto' }]} />
<div class="page">
<header>
<h1>Konto</h1>
<p class="sub">Dein Profil — sichtbar für andere im Projekt.</p>
</header>
{#if $user}
<section class="card">
<h2>Profil</h2>
<div class="profile-row">
<Avatar name={$user.name} src={$user.avatarUrl ?? null} size="lg" />
<div class="form">
<Input label="Anzeige-Name" bind:value={name} />
<p class="email-line">E-Mail: <span>{$user.email}</span></p>
<Button onclick={save} loading={saving} disabled={!name.trim() || name === $user.name}>
Speichern
</Button>
</div>
</div>
</section>
{/if}
</div>
<style>
.page {
padding: var(--space-6);
max-width: 720px;
}
@media (max-width: 640px) {
.page {
padding: var(--space-4);
}
.profile-row {
flex-direction: column;
align-items: stretch;
}
}
header {
margin-bottom: var(--space-8);
}
h1 {
margin: 0 0 var(--space-1);
font-size: var(--text-2xl);
}
.sub {
color: var(--color-text-tertiary);
font-size: var(--text-sm);
margin: 0;
}
.card {
background: var(--color-bg-raised);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-6);
}
h2 {
margin: 0 0 var(--space-5);
font-size: var(--text-lg);
}
.profile-row {
display: flex;
align-items: flex-start;
gap: var(--space-5);
}
.form {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--space-4);
align-items: flex-start;
}
.form :global(.input-group) {
width: 100%;
}
.email-line {
color: var(--color-text-tertiary);
font-size: var(--text-sm);
margin: 0;
}
.email-line span {
color: var(--color-text-primary);
font-family: var(--font-mono);
}
</style>