Web and mobile: - Follower/following lists (tap the counts), with Follow buttons and "Remove" on your own followers. - Profile ⋯ menu: block (with confirmation) / unblock, and report with a reason and optional note. "Follows you" label, "Follow back", and a banner on profiles you've blocked. - Blocked accounts list in Settings, with unblock. Web only: /admin for the admin account: stats, open/resolved reports, resolve, suspend/unsuspend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { FollowList } from '@/components/FollowList';
|
||||
|
||||
export default function FollowersPage() {
|
||||
const { username } = useParams<{ username: string }>();
|
||||
return (
|
||||
<div className="stack">
|
||||
<p className="muted">
|
||||
<Link href={`/u/${username}`}>← @{username}</Link>
|
||||
</p>
|
||||
<h1>Followers</h1>
|
||||
<FollowList username={username} direction="followers" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { FollowList } from '@/components/FollowList';
|
||||
|
||||
export default function FollowingPage() {
|
||||
const { username } = useParams<{ username: string }>();
|
||||
return (
|
||||
<div className="stack">
|
||||
<p className="muted">
|
||||
<Link href={`/u/${username}`}>← @{username}</Link>
|
||||
</p>
|
||||
<h1>Following</h1>
|
||||
<FollowList username={username} direction="following" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import useSWR from 'swr';
|
||||
import { profileUrl, type ProfileDTO } from '@musicshare/shared';
|
||||
import { Avatar } from '@/components/Avatar';
|
||||
import { NowPlayingCard } from '@/components/NowPlayingCard';
|
||||
import { ProfileMenu } from '@/components/ProfileMenu';
|
||||
import { QrCode } from '@/components/QrCode';
|
||||
import { RecentPlays } from '@/components/RecentPlays';
|
||||
import { TrackCard } from '@/components/TrackCard';
|
||||
@@ -65,15 +66,18 @@ export default function ProfilePage() {
|
||||
<Avatar user={user} size={96} live={Boolean(profile.nowPlaying?.isPlaying)} />
|
||||
<div>
|
||||
<h1>{user.displayName ?? user.username}</h1>
|
||||
<div className="muted">@{user.username}</div>
|
||||
<div className="muted">
|
||||
@{user.username}
|
||||
{profile.followsYou && !profile.isSelf && <span className="chip-small">Follows you</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="stats">
|
||||
<span>
|
||||
<Link href={`/u/${user.username}/followers`}>
|
||||
<strong>{profile.followerCount}</strong> {profile.followerCount === 1 ? 'follower' : 'followers'}
|
||||
</span>
|
||||
<span>
|
||||
</Link>
|
||||
<Link href={`/u/${user.username}/following`}>
|
||||
<strong>{profile.followingCount}</strong> following
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
{user.bio && <p className="bio">{user.bio}</p>}
|
||||
{profile.isSelf ? (
|
||||
@@ -92,9 +96,12 @@ export default function ProfilePage() {
|
||||
{profile.isPinned ? '★ Pinned' : '☆ Pin'}
|
||||
</button>
|
||||
)}
|
||||
<button className={`btn ${profile.isFollowing ? '' : 'btn-primary'}`} onClick={toggleFollow} disabled={busy}>
|
||||
{profile.isFollowing ? 'Following' : 'Follow'}
|
||||
</button>
|
||||
{!profile.isBlocked && (
|
||||
<button className={`btn ${profile.isFollowing ? '' : 'btn-primary'}`} onClick={toggleFollow} disabled={busy}>
|
||||
{profile.isFollowing ? 'Following' : profile.followsYou ? 'Follow back' : 'Follow'}
|
||||
</button>
|
||||
)}
|
||||
<ProfileMenu profile={profile} onChanged={() => mutate()} />
|
||||
</div>
|
||||
) : (
|
||||
<Link href="/register" className="btn btn-primary">
|
||||
@@ -103,6 +110,13 @@ export default function ProfilePage() {
|
||||
)}
|
||||
</motion.section>
|
||||
|
||||
{profile.isBlocked && (
|
||||
<p className="card muted">
|
||||
You blocked @{user.username}. They can't see your profile or what you play, and you won't see theirs. Unblock
|
||||
them from the ⋯ menu.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{profile.isSelf && showQr && (
|
||||
<section className="card qr-card">
|
||||
<QrCode value={profileUrl(window.location.origin, user.username)} size={220} />
|
||||
|
||||
Reference in New Issue
Block a user