Files
vega/src/components/feed/Feed.tsx
Jure b465ad03a3 Add editable own profile + navigation fixes
- ProfileView shows edit form when viewing own profile
- publishProfile (kind 0) added to nostr lib
- Sidebar name/avatar opens own profile
- Back button in edit mode cancels form; outside edit mode navigates back
- goBack safeguard: falls back to feed if previousView === currentView
- Fix ThreadView crash when selectedNote is null
- Tighten feed filter for base64 blobs and protocol messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 19:19:24 +01:00

129 lines
4.3 KiB
TypeScript

import { useEffect, useState } from "react";
import { useFeedStore } from "../../stores/feed";
import { useUserStore } from "../../stores/user";
import { fetchFollowFeed } from "../../lib/nostr";
import { NoteCard } from "./NoteCard";
import { ComposeBox } from "./ComposeBox";
import { NDKEvent } from "@nostr-dev-kit/ndk";
type FeedTab = "global" | "following";
export function Feed() {
const { notes, loading, connected, error, connect, loadFeed } = useFeedStore();
const { loggedIn, follows } = useUserStore();
const [tab, setTab] = useState<FeedTab>("global");
const [followNotes, setFollowNotes] = useState<NDKEvent[]>([]);
const [followLoading, setFollowLoading] = useState(false);
useEffect(() => {
connect().then(() => loadFeed());
}, []);
useEffect(() => {
if (tab === "following" && loggedIn && follows.length > 0) {
loadFollowFeed();
}
}, [tab, follows]);
const loadFollowFeed = async () => {
setFollowLoading(true);
try {
const events = await fetchFollowFeed(follows);
setFollowNotes(events);
} finally {
setFollowLoading(false);
}
};
const isFollowing = tab === "following";
const activeNotes = isFollowing ? followNotes : notes;
const isLoading = isFollowing ? followLoading : loading;
const filteredNotes = activeNotes.filter((event) => {
const c = event.content.trim();
if (!c || c.startsWith("{") || c.startsWith("[")) return false;
// Filter out notes that look like base64 blobs or relay protocol messages
if (c.length > 500 && /^[A-Za-z0-9+/=]{50,}$/.test(c.slice(0, 100))) return false;
if (c.startsWith("nlogpost:") || c.startsWith("T1772")) return false;
return true;
});
return (
<div className="h-full flex flex-col">
{/* Header */}
<header className="border-b border-border px-4 py-2.5 flex items-center justify-between shrink-0">
<div className="flex items-center gap-1">
<button
onClick={() => setTab("global")}
className={`px-3 py-1 text-[12px] transition-colors ${
tab === "global"
? "text-text border-b-2 border-accent"
: "text-text-muted hover:text-text"
}`}
>
Global
</button>
{loggedIn && (
<button
onClick={() => setTab("following")}
className={`px-3 py-1 text-[12px] transition-colors ${
tab === "following"
? "text-text border-b-2 border-accent"
: "text-text-muted hover:text-text"
}`}
>
Following
</button>
)}
</div>
<div className="flex items-center gap-3">
{connected && (
<span className="text-success text-[11px] flex items-center gap-1">
<span className="w-1.5 h-1.5 rounded-full bg-success inline-block" />
connected
</span>
)}
<button
onClick={isFollowing ? loadFollowFeed : loadFeed}
disabled={isLoading}
className="text-text-muted hover:text-text text-[11px] px-2 py-1 border border-border hover:border-text-dim transition-colors disabled:opacity-40"
>
{isLoading ? "loading…" : "refresh"}
</button>
</div>
</header>
{/* Compose */}
{loggedIn && <ComposeBox onPublished={loadFeed} />}
{/* Feed */}
<div className="flex-1 overflow-y-auto">
{error && !isFollowing && (
<div className="px-4 py-3 text-danger text-[12px] border-b border-border bg-danger/5">
{error}
</div>
)}
{isLoading && filteredNotes.length === 0 && (
<div className="px-4 py-8 text-text-dim text-[12px] text-center">
{isFollowing ? "Loading notes from people you follow…" : "Connecting to relays…"}
</div>
)}
{!isLoading && filteredNotes.length === 0 && (
<div className="px-4 py-8 text-text-dim text-[12px] text-center">
{isFollowing && follows.length === 0
? "You're not following anyone yet."
: "No notes yet."}
</div>
)}
{filteredNotes.map((event) => (
<NoteCard key={event.id} event={event} />
))}
</div>
</div>
);
}