Friends tab shows who you follow and who follows you

The app's Friends tab was only a search box. It now lists Following and
Followers (with Follow / Remove buttons) until you type a search. The
website gets the same Friends page and tab; search moves there from the
top of the feed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 20:43:03 -04:00
co-authored by Claude Opus 5.5
parent a4dad724ad
commit 08631589e3
5 changed files with 174 additions and 66 deletions
+29
View File
@@ -0,0 +1,29 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
import { FollowList } from '@/components/FollowList';
import { UserSearch } from '@/components/UserSearch';
import { useMe } from '@/lib/api';
/** Friends: who you follow and who follows you, plus search to find new people. */
export default function FriendsPage() {
const { me, loggedOut } = useMe();
const [tab, setTab] = useState<'following' | 'followers'>('following');
if (loggedOut) return <p className="muted"><Link href="/login">Log in</Link> to see your friends.</p>;
if (!me) return null;
return (
<div className="stack">
<h1>Friends</h1>
<UserSearch />
<div className="row">
{(['following', 'followers'] as const).map((t) => (
<button key={t} className={`btn btn-small ${tab === t ? 'btn-primary' : ''}`} aria-pressed={tab === t} onClick={() => setTab(t)}>
{t === 'following' ? 'Following' : 'Followers'}
</button>
))}
</div>
<FollowList key={tab} username={me.username} direction={tab} />
</div>
);
}
+1 -3
View File
@@ -8,7 +8,6 @@ import { Avatar } from '@/components/Avatar';
import { Equalizer } from '@/components/Equalizer';
import { NowPlayingCard } from '@/components/NowPlayingCard';
import { SharingControls } from '@/components/SharingControls';
import { UserSearch } from '@/components/UserSearch';
import { api, fetcher, useMe } from '@/lib/api';
import { useRealtime } from '@/lib/useRealtime';
@@ -60,7 +59,6 @@ function Feed() {
return (
<div className="stack">
<UserSearch />
<SharingControls compact />
{mine?.nowPlaying && (
<section className="stack">
@@ -78,7 +76,7 @@ function Feed() {
(following?.users.length ? (
<p className="muted">Nobody you follow has played anything yet. Their music will show up here live.</p>
) : (
<p className="muted">Nothing yet. Find friends above, and their music will show up here live.</p>
<p className="muted">Nothing yet. <Link href="/friends">Find friends</Link>, and their music will show up here live.</p>
))}
{/* Cards rise in on load and glide to new positions when someone pins or starts playing. */}
<AnimatePresence initial={true}>
+10 -1
View File
@@ -5,13 +5,14 @@ import { usePathname } from 'next/navigation';
import { useState } from 'react';
import { client, useMe } from '@/lib/api';
/** Top bar: logo and the three main places. On phones those move to a bottom tab bar. */
/** Top bar: logo and the four main places. On phones those move to a bottom tab bar. */
export function Nav() {
const { me } = useMe();
const path = usePathname();
const tabs = me
? [
{ href: '/', label: 'Feed', icon: <FeedIcon />, active: path === '/' },
{ href: '/friends', label: 'Friends', icon: <FriendsIcon />, active: path.startsWith('/friends') },
{ href: `/u/${me.username}`, label: 'Profile', icon: <ProfileIcon />, active: path.startsWith(`/u/${me.username}`) },
{ href: '/settings', label: 'Settings', icon: <SettingsIcon />, active: path.startsWith('/settings') || path === '/admin' },
]
@@ -89,6 +90,14 @@ const FeedIcon = () => (
<circle cx="18" cy="16" r="3" />
</svg>
);
const FriendsIcon = () => (
<svg {...icon} aria-hidden>
<circle cx="9" cy="8" r="3.5" />
<path d="M2 20c0-3.6 3.1-6 7-6s7 2.4 7 6" />
<path d="M16 4.5a3.5 3.5 0 0 1 0 7" />
<path d="M18 14c2.4.6 4 2.6 4 6" />
</svg>
);
const ProfileIcon = () => (
<svg {...icon} aria-hidden>
<circle cx="12" cy="8" r="4" />