mirror of
https://github.com/hoornet/vega.git
synced 2026-06-08 14:11:55 -07:00
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:
@@ -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">
|
||||
|
||||
@@ -191,6 +191,17 @@ export async function fetchUserNotes(pubkey: string, limit = 30): Promise<NDKEve
|
||||
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
|
||||
}
|
||||
|
||||
export async function publishContactList(pubkeys: string[]): Promise<void> {
|
||||
const instance = getNDK();
|
||||
if (!instance.signer) throw new Error("Not logged in");
|
||||
|
||||
const event = new NDKEvent(instance);
|
||||
event.kind = 3;
|
||||
event.content = "";
|
||||
event.tags = pubkeys.map((pk) => ["p", pk]);
|
||||
await event.publish();
|
||||
}
|
||||
|
||||
export async function fetchProfile(pubkey: string) {
|
||||
const instance = getNDK();
|
||||
const user = instance.getUser({ pubkey });
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishProfile, publishReaction, publishReply, fetchUserNotes, fetchProfile } from "./client";
|
||||
export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishProfile, publishReaction, publishReply, publishContactList, fetchUserNotes, fetchProfile } from "./client";
|
||||
|
||||
+18
-1
@@ -1,6 +1,6 @@
|
||||
import { create } from "zustand";
|
||||
import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
||||
import { getNDK } from "../lib/nostr";
|
||||
import { getNDK, publishContactList } from "../lib/nostr";
|
||||
import { nip19 } from "@nostr-dev-kit/ndk";
|
||||
|
||||
interface UserState {
|
||||
@@ -16,6 +16,8 @@ interface UserState {
|
||||
logout: () => void;
|
||||
fetchOwnProfile: () => Promise<void>;
|
||||
fetchFollows: () => Promise<void>;
|
||||
follow: (pubkey: string) => Promise<void>;
|
||||
unfollow: (pubkey: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserState>((set, get) => ({
|
||||
@@ -130,4 +132,19 @@ export const useUserStore = create<UserState>((set, get) => ({
|
||||
// Non-critical
|
||||
}
|
||||
},
|
||||
|
||||
follow: async (pubkey: string) => {
|
||||
const { follows } = get();
|
||||
if (follows.includes(pubkey)) return;
|
||||
const updated = [...follows, pubkey];
|
||||
set({ follows: updated });
|
||||
await publishContactList(updated);
|
||||
},
|
||||
|
||||
unfollow: async (pubkey: string) => {
|
||||
const { follows } = get();
|
||||
const updated = follows.filter((pk) => pk !== pubkey);
|
||||
set({ follows: updated });
|
||||
await publishContactList(updated);
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user