Web: redesign, logo, PWA, QR codes, Spotify guidance
- Album-art-tinted cards that crossfade on song change, equalizer bars, live avatar rings, drifting ambient colour, Space Grotesk display font, brand purple-to-blue gradient from the logo - Feed cards rise in and glide to new positions (motion); respects reduced-motion - New logo as nav mark, favicon and Apple touch icon - Installable PWA: manifest, maskable icons, small service worker (cached build assets + offline page), security headers - Show-QR-code on your own profile; centred profile header with stats - Settings explains connecting Spotify via Last.fm or ListenBrainz Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'motion/react';
|
||||
import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import type { ProfileDTO } from '@musicshare/shared';
|
||||
import { profileUrl, type ProfileDTO } from '@musicshare/shared';
|
||||
import { Avatar } from '@/components/Avatar';
|
||||
import { NowPlayingCard } from '@/components/NowPlayingCard';
|
||||
import { QrCode } from '@/components/QrCode';
|
||||
import { RecentPlays } from '@/components/RecentPlays';
|
||||
import { TrackCard } from '@/components/TrackCard';
|
||||
import { api, fetcher, useMe } from '@/lib/api';
|
||||
@@ -17,6 +19,7 @@ export default function ProfilePage() {
|
||||
const { me } = useMe();
|
||||
const { data: profile, error, mutate } = useSWR<ProfileDTO>(`/users/${username}`, fetcher);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [showQr, setShowQr] = useState(false);
|
||||
|
||||
// Live updates arrive for people you follow (and yourself)
|
||||
useRealtime(Boolean(me), (msg) => {
|
||||
@@ -53,19 +56,35 @@ export default function ProfilePage() {
|
||||
|
||||
return (
|
||||
<div className="stack">
|
||||
<section className="profile-header">
|
||||
<Avatar user={user} size={72} />
|
||||
<div className="grow">
|
||||
<motion.section
|
||||
className="profile-header"
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
|
||||
>
|
||||
<Avatar user={user} size={96} live={Boolean(profile.nowPlaying?.isPlaying)} />
|
||||
<div>
|
||||
<h1>{user.displayName ?? user.username}</h1>
|
||||
<div className="muted">
|
||||
@{user.username} · {profile.followerCount} followers · {profile.followingCount} following
|
||||
</div>
|
||||
{user.bio && <p>{user.bio}</p>}
|
||||
<div className="muted">@{user.username}</div>
|
||||
</div>
|
||||
<div className="stats">
|
||||
<span>
|
||||
<strong>{profile.followerCount}</strong> {profile.followerCount === 1 ? 'follower' : 'followers'}
|
||||
</span>
|
||||
<span>
|
||||
<strong>{profile.followingCount}</strong> following
|
||||
</span>
|
||||
</div>
|
||||
{user.bio && <p className="bio">{user.bio}</p>}
|
||||
{profile.isSelf ? (
|
||||
<Link href="/settings" className="btn">
|
||||
Edit profile
|
||||
</Link>
|
||||
<div className="row">
|
||||
<button className="btn" onClick={() => setShowQr((v) => !v)}>
|
||||
{showQr ? 'Hide QR code' : 'Show QR code'}
|
||||
</button>
|
||||
<Link href="/settings" className="btn">
|
||||
Edit profile
|
||||
</Link>
|
||||
</div>
|
||||
) : me ? (
|
||||
<div className="row">
|
||||
{profile.isFollowing && (
|
||||
@@ -82,7 +101,14 @@ export default function ProfilePage() {
|
||||
Sign up to follow
|
||||
</Link>
|
||||
)}
|
||||
</section>
|
||||
</motion.section>
|
||||
|
||||
{profile.isSelf && showQr && (
|
||||
<section className="card qr-card">
|
||||
<QrCode value={profileUrl(window.location.origin, user.username)} size={220} />
|
||||
<p className="muted">Friends scan this in the musicshare app (or their camera) to open your profile.</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{profile.featured && (
|
||||
<section className="card featured">
|
||||
|
||||
Reference in New Issue
Block a user