Add follow/unfollow (NIP-02) from profile view

- publishContactList (kind 3) in nostr lib — replaces full follow list on each change
- follow() and unfollow() actions in user store with optimistic UI update
- Follow/Unfollow button in ProfileView header (visible when logged in, not own profile)
- Button shows "unfollow" in muted style with danger hover when already following

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jure
2026-03-09 17:25:34 +01:00
parent b0a477177d
commit 2960e7b279
4 changed files with 60 additions and 3 deletions

View File

@@ -99,7 +99,7 @@ function EditProfileForm({ pubkey, onSaved }: { pubkey: string; onSaved: () => v
export function ProfileView() {
const { selectedPubkey, goBack } = useUIStore();
const { pubkey: ownPubkey } = useUserStore();
const { pubkey: ownPubkey, loggedIn, follows, follow, unfollow } = useUserStore();
const pubkey = selectedPubkey!;
const isOwn = pubkey === ownPubkey;
@@ -107,6 +107,22 @@ export function ProfileView() {
const [notes, setNotes] = useState<NDKEvent[]>([]);
const [loading, setLoading] = useState(true);
const [editing, setEditing] = useState(false);
const [followPending, setFollowPending] = useState(false);
const isFollowing = follows.includes(pubkey);
const handleFollowToggle = async () => {
setFollowPending(true);
try {
if (isFollowing) {
await unfollow(pubkey);
} else {
await follow(pubkey);
}
} finally {
setFollowPending(false);
}
};
const name = profile?.displayName || profile?.name || shortenPubkey(pubkey);
const avatar = profile?.picture;
@@ -144,6 +160,19 @@ export function ProfileView() {
edit profile
</button>
)}
{!isOwn && loggedIn && (
<button
onClick={handleFollowToggle}
disabled={followPending}
className={`text-[11px] px-3 py-1 border transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${
isFollowing
? "border-border text-text-muted hover:text-danger hover:border-danger/40"
: "border-accent/60 text-accent hover:bg-accent hover:text-white"
}`}
>
{followPending ? "…" : isFollowing ? "unfollow" : "follow"}
</button>
)}
</header>
<div className="flex-1 overflow-y-auto">