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:
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { Suspense, useRef, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
@@ -12,12 +13,13 @@ import {
|
||||
type MeDTO,
|
||||
type NowPlayingProvider,
|
||||
type TrackSearchResultDTO,
|
||||
type UserSummaryDTO,
|
||||
} from '@musicshare/shared';
|
||||
import { Avatar } from '@/components/Avatar';
|
||||
import { ServiceName } from '@/components/ServiceName';
|
||||
import { SharingControls } from '@/components/SharingControls';
|
||||
import { SupportCard } from '@/components/SupportLink';
|
||||
import { api, fetcher, uploadFile, useMe } from '@/lib/api';
|
||||
import { api, client, fetcher, uploadFile, useMe } from '@/lib/api';
|
||||
import { timeAgo } from '@musicshare/shared';
|
||||
import { useDebounced } from '@/lib/useDebounced';
|
||||
|
||||
@@ -41,6 +43,7 @@ function Settings() {
|
||||
<SharingControls />
|
||||
<Connections />
|
||||
<FeaturedPicker />
|
||||
<BlockedAccounts />
|
||||
<SupportCard />
|
||||
<DangerZone />
|
||||
</div>
|
||||
@@ -145,6 +148,32 @@ function AvatarPicker({ me }: { me: MeDTO }) {
|
||||
);
|
||||
}
|
||||
|
||||
function BlockedAccounts() {
|
||||
const { data: blocked = [], mutate } = useSWR<UserSummaryDTO[]>('/me/blocks', fetcher);
|
||||
return (
|
||||
<section className="card">
|
||||
<h2>Blocked accounts</h2>
|
||||
{blocked.length === 0 ? (
|
||||
<p className="muted">You haven't blocked anyone. Block someone from the ⋯ menu on their profile.</p>
|
||||
) : (
|
||||
<ul className="people">
|
||||
{blocked.map((u) => (
|
||||
<li key={u.id}>
|
||||
<Link href={`/u/${u.username}`} className="user-chip">
|
||||
<Avatar user={u} size={36} />
|
||||
<span className="user-name">{u.displayName ?? u.username}</span>
|
||||
</Link>
|
||||
<button className="btn btn-small" onClick={async () => (await client.unblock(u.username), mutate())}>
|
||||
Unblock
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Connections() {
|
||||
const params = useSearchParams();
|
||||
const { data: connections = [], mutate } = useSWR<ConnectionDTO[]>('/connections', fetcher);
|
||||
|
||||
Reference in New Issue
Block a user