-
{name}
+
openProfile(event.pubkey)}
+ >{name}
{nip05 && (
{nip05}
)}
diff --git a/src/components/profile/ProfileView.tsx b/src/components/profile/ProfileView.tsx
new file mode 100644
index 0000000..e89d81d
--- /dev/null
+++ b/src/components/profile/ProfileView.tsx
@@ -0,0 +1,107 @@
+import { useEffect, useState } from "react";
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { useUIStore } from "../../stores/ui";
+import { useProfile } from "../../hooks/useProfile";
+import { fetchUserNotes } from "../../lib/nostr";
+import { shortenPubkey } from "../../lib/utils";
+import { NoteCard } from "../feed/NoteCard";
+
+export function ProfileView() {
+ const { selectedPubkey, setView } = useUIStore();
+ const pubkey = selectedPubkey!;
+ const profile = useProfile(pubkey);
+
+ const [notes, setNotes] = useState
([]);
+ const [loading, setLoading] = useState(true);
+
+ const name = profile?.displayName || profile?.name || shortenPubkey(pubkey);
+ const avatar = profile?.picture;
+ const about = profile?.about;
+ const nip05 = profile?.nip05;
+ const website = profile?.website;
+
+ useEffect(() => {
+ setLoading(true);
+ fetchUserNotes(pubkey).then((events) => {
+ setNotes(events);
+ setLoading(false);
+ }).catch(() => setLoading(false));
+ }, [pubkey]);
+
+ return (
+
+ {/* Header */}
+
+
+ Profile
+
+
+
+ {/* Profile card */}
+
+
+ {avatar ? (
+

{ (e.target as HTMLImageElement).style.display = "none"; }}
+ />
+ ) : (
+
+ {name.charAt(0).toUpperCase()}
+
+ )}
+
+
+
+
+
+ {/* Notes */}
+ {loading && (
+
+ Loading notes…
+
+ )}
+
+ {!loading && notes.length === 0 && (
+
+ No notes found.
+
+ )}
+
+ {notes.map((event) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/lib/nostr/client.ts b/src/lib/nostr/client.ts
index a85b49a..1b4f581 100644
--- a/src/lib/nostr/client.ts
+++ b/src/lib/nostr/client.ts
@@ -99,6 +99,19 @@ export async function publishNote(content: string): Promise {
await event.publish();
}
+export async function fetchUserNotes(pubkey: string, limit = 30): Promise {
+ const instance = getNDK();
+ const filter: NDKFilter = {
+ kinds: [NDKKind.Text],
+ authors: [pubkey],
+ limit,
+ };
+ const events = await instance.fetchEvents(filter, {
+ cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY,
+ });
+ return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
+}
+
export async function fetchProfile(pubkey: string) {
const instance = getNDK();
const user = instance.getUser({ pubkey });
diff --git a/src/lib/nostr/index.ts b/src/lib/nostr/index.ts
index cf11870..07bfd2d 100644
--- a/src/lib/nostr/index.ts
+++ b/src/lib/nostr/index.ts
@@ -1 +1 @@
-export { getNDK, connectToRelays, fetchGlobalFeed, publishNote, publishReaction, publishReply, fetchProfile } from "./client";
+export { getNDK, connectToRelays, fetchGlobalFeed, publishNote, publishReaction, publishReply, fetchUserNotes, fetchProfile } from "./client";
diff --git a/src/stores/ui.ts b/src/stores/ui.ts
index 29869fc..28a9dfe 100644
--- a/src/stores/ui.ts
+++ b/src/stores/ui.ts
@@ -1,17 +1,21 @@
import { create } from "zustand";
-type View = "feed" | "relays" | "settings";
+type View = "feed" | "relays" | "settings" | "profile";
interface UIState {
currentView: View;
sidebarCollapsed: boolean;
+ selectedPubkey: string | null;
setView: (view: View) => void;
+ openProfile: (pubkey: string) => void;
toggleSidebar: () => void;
}
export const useUIStore = create((set) => ({
currentView: "feed",
sidebarCollapsed: false,
+ selectedPubkey: null,
setView: (currentView) => set({ currentView }),
+ openProfile: (pubkey) => set({ currentView: "profile", selectedPubkey: pubkey }),
toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
}));