diff --git a/src/components/article/ArticleCard.tsx b/src/components/article/ArticleCard.tsx index 9447352..9cdeaab 100644 --- a/src/components/article/ArticleCard.tsx +++ b/src/components/article/ArticleCard.tsx @@ -1,7 +1,7 @@ import { NDKEvent, nip19 } from "@nostr-dev-kit/ndk"; import { useProfile } from "../../hooks/useProfile"; import { useUIStore } from "../../stores/ui"; -import { shortenPubkey } from "../../lib/utils"; +import { shortenPubkey, profileName } from "../../lib/utils"; function getTag(event: NDKEvent, name: string): string { return event.tags.find((t) => t[0] === name)?.[1] ?? ""; @@ -32,7 +32,7 @@ export function ArticleCard({ event }: { event: NDKEvent }) { const publishedAt = parseInt(getTag(event, "published_at")) || event.created_at || null; const naddr = buildNaddr(event); - const authorName = profile?.displayName || profile?.name || shortenPubkey(event.pubkey); + const authorName = profileName(profile, shortenPubkey(event.pubkey)); const date = publishedAt ? new Date(publishedAt * 1000).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" }) : null; diff --git a/src/components/article/ArticleView.tsx b/src/components/article/ArticleView.tsx index 268a36f..a0512be 100644 --- a/src/components/article/ArticleView.tsx +++ b/src/components/article/ArticleView.tsx @@ -8,6 +8,7 @@ import { useBookmarkStore } from "../../stores/bookmark"; import { fetchArticle, publishReaction, publishRepost, publishNote } from "../../lib/nostr"; import { nip19 } from "@nostr-dev-kit/ndk"; import { useProfile } from "../../hooks/useProfile"; +import { profileName } from "../../lib/utils"; import { ZapModal } from "../zap/ZapModal"; // ── Types ──────────────────────────────────────────────────────────────────── @@ -33,7 +34,7 @@ function getTags(event: NDKEvent, name: string): string[] { function AuthorRow({ pubkey, publishedAt, readingTime }: { pubkey: string; publishedAt: number | null; readingTime?: number }) { const { openProfile } = useUIStore(); const profile = useProfile(pubkey); - const name = profile?.displayName || profile?.name || pubkey.slice(0, 12) + "…"; + const name = profileName(profile, pubkey.slice(0, 12) + "…"); const date = publishedAt ? new Date(publishedAt * 1000).toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" }) : null; @@ -122,7 +123,7 @@ export function ArticleView() { const articleTags = event ? getTags(event, "t") : []; const authorPubkey = event?.pubkey ?? ""; const authorProfile = useProfile(authorPubkey); - const authorName = authorProfile?.displayName || authorProfile?.name || authorPubkey.slice(0, 12) + "…"; + const authorName = profileName(authorProfile, authorPubkey.slice(0, 12) + "…"); const bodyHtml = event?.content ? renderMarkdown(event.content) : ""; const wordCount = event?.content?.trim().split(/\s+/).length ?? 0; diff --git a/src/components/dm/DMView.tsx b/src/components/dm/DMView.tsx index 2ac4525..3ade4ee 100644 --- a/src/components/dm/DMView.tsx +++ b/src/components/dm/DMView.tsx @@ -5,7 +5,7 @@ import { useUIStore } from "../../stores/ui"; import { useNotificationsStore } from "../../stores/notifications"; import { fetchDMConversations, fetchDMThread, sendDM, decryptDM, getNDK } from "../../lib/nostr"; import { useProfile } from "../../hooks/useProfile"; -import { timeAgo, shortenPubkey } from "../../lib/utils"; +import { timeAgo, shortenPubkey, profileName } from "../../lib/utils"; // ── Helpers ────────────────────────────────────────────────────────────────── @@ -45,7 +45,7 @@ function ConvRow({ onSelect: () => void; }) { const profile = useProfile(partnerPubkey); - const name = profile?.displayName || profile?.name || shortenPubkey(partnerPubkey); + const name = profileName(profile, shortenPubkey(partnerPubkey)); const time = lastEvent.created_at ? timeAgo(lastEvent.created_at) : ""; return ( @@ -126,7 +126,7 @@ function ThreadPanel({ }) { const { openProfile } = useUIStore(); const profile = useProfile(partnerPubkey); - const name = profile?.displayName || profile?.name || shortenPubkey(partnerPubkey); + const name = profileName(profile, shortenPubkey(partnerPubkey)); const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(true); diff --git a/src/components/feed/ComposeBox.tsx b/src/components/feed/ComposeBox.tsx index 3b2e9fd..6529dda 100644 --- a/src/components/feed/ComposeBox.tsx +++ b/src/components/feed/ComposeBox.tsx @@ -4,7 +4,7 @@ import { uploadImage, uploadBytes } from "../../lib/upload"; import { useAutoResize } from "../../hooks/useAutoResize"; import { useUserStore } from "../../stores/user"; import { useFeedStore } from "../../stores/feed"; -import { shortenPubkey } from "../../lib/utils"; +import { shortenPubkey, profileName } from "../../lib/utils"; import { open } from "@tauri-apps/plugin-dialog"; import { readFile } from "@tauri-apps/plugin-fs"; import { EmojiPicker } from "../shared/EmojiPicker"; @@ -25,8 +25,8 @@ export function ComposeBox({ onPublished, onNoteInjected }: { onPublished?: () = const textareaRef = useRef(null); const { profile, npub } = useUserStore(); - const avatar = profile?.picture; - const name = profile?.displayName || profile?.name || (npub ? shortenPubkey(npub) : ""); + const avatar = typeof profile?.picture === "string" ? profile.picture : undefined; + const name = profileName(profile, npub ? shortenPubkey(npub) : ""); // Auto-save draft with debounce useEffect(() => { diff --git a/src/components/feed/NoteActions.tsx b/src/components/feed/NoteActions.tsx index 3307e15..fb967cc 100644 --- a/src/components/feed/NoteActions.tsx +++ b/src/components/feed/NoteActions.tsx @@ -7,6 +7,7 @@ import { useZapCount } from "../../hooks/useZapCount"; import { useUserStore } from "../../stores/user"; import { useBookmarkStore } from "../../stores/bookmark"; import { publishReaction, publishRepost } from "../../lib/nostr"; +import { profileName } from "../../lib/utils"; import { ZapModal } from "../zap/ZapModal"; import { QuoteModal } from "./QuoteModal"; @@ -20,8 +21,8 @@ interface NoteActionsProps { export function NoteActions({ event, onReplyToggle, showReply }: NoteActionsProps) { const profile = useProfile(event.pubkey); - const name = profile?.displayName || profile?.name || event.pubkey.slice(0, 8) + "…"; - const avatar = profile?.picture; + const name = profileName(profile, event.pubkey.slice(0, 8) + "…"); + const avatar = typeof profile?.picture === "string" ? profile.picture : undefined; const { loggedIn } = useUserStore(); const { bookmarkedIds, addBookmark, removeBookmark } = useBookmarkStore(); const isBookmarked = bookmarkedIds.includes(event.id!); diff --git a/src/components/feed/NoteCard.tsx b/src/components/feed/NoteCard.tsx index 1b640fc..e637671 100644 --- a/src/components/feed/NoteCard.tsx +++ b/src/components/feed/NoteCard.tsx @@ -20,15 +20,17 @@ interface NoteCardProps { function ParentAuthorName({ pubkey }: { pubkey: string }) { const profile = useProfile(pubkey); - const name = profile?.displayName || profile?.name || pubkey.slice(0, 8) + "…"; + const raw = profile?.displayName || profile?.name; + const name = (typeof raw === "string" ? raw : null) || pubkey.slice(0, 8) + "…"; return @{name}; } export function NoteCard({ event, focused, onReplyInThread }: NoteCardProps) { const profile = useProfile(event.pubkey); - const name = profile?.displayName || profile?.name || shortenPubkey(event.pubkey); + const rawName = profile?.displayName || profile?.name; + const name = (typeof rawName === "string" ? rawName : null) || shortenPubkey(event.pubkey); const avatar = profile?.picture; - const nip05 = profile?.nip05; + const nip05 = typeof profile?.nip05 === "string" ? profile.nip05 : null; const verified = useNip05Verified(event.pubkey, nip05); const time = event.created_at ? timeAgo(event.created_at) : ""; diff --git a/src/components/feed/NoteContent.tsx b/src/components/feed/NoteContent.tsx index dd5e776..19b5d72 100644 --- a/src/components/feed/NoteContent.tsx +++ b/src/components/feed/NoteContent.tsx @@ -122,7 +122,8 @@ function QuotePreview({ eventId }: { eventId: string }) { if (!event) return null; - const name = profile?.displayName || profile?.name || shortenPubkey(event.pubkey); + const rawName = profile?.displayName || profile?.name; + const name = (typeof rawName === "string" ? rawName : null) || shortenPubkey(event.pubkey); const preview = event.content.slice(0, 160) + (event.content.length > 160 ? "…" : ""); return ( diff --git a/src/components/feed/TextSegments.tsx b/src/components/feed/TextSegments.tsx index 043ddc5..1044db9 100644 --- a/src/components/feed/TextSegments.tsx +++ b/src/components/feed/TextSegments.tsx @@ -45,7 +45,8 @@ export function tryOpenNostrEntity(raw: string): boolean { export function MentionName({ pubkey, fallback }: { pubkey?: string; fallback: string }) { const profile = useProfile(pubkey ?? ""); if (!pubkey) return <>{fallback}; - const name = profile?.displayName || profile?.name; + const raw = profile?.displayName || profile?.name; + const name = typeof raw === "string" ? raw : null; return <>{name || fallback}; } @@ -65,7 +66,7 @@ export function renderTextSegments( segments.forEach((seg, i) => { switch (seg.type) { case "text": - elements.push({seg.value}); + elements.push({typeof seg.value === "string" ? seg.value : String(seg.value)}); break; case "link": elements.push( @@ -79,7 +80,7 @@ export function renderTextSegments( if (tryHandleUrlInternally(seg.value)) e.preventDefault(); }} > - {seg.display} + {typeof seg.display === "string" ? seg.display : String(seg.display)} ); break; @@ -91,8 +92,8 @@ export function renderTextSegments( onClick={(e) => { e.stopPropagation(); tryOpenNostrEntity(seg.value); }} > @{resolveMentions - ? - : seg.display} + ? + : String(seg.display ?? seg.value)} ); break; @@ -103,7 +104,7 @@ export function renderTextSegments( className="text-accent/80 cursor-pointer hover:text-accent" onClick={(e) => { e.stopPropagation(); openHashtag(seg.value); }} > - {seg.display} + {String(seg.display ?? seg.value)} ); break; diff --git a/src/components/follows/FollowsView.tsx b/src/components/follows/FollowsView.tsx index b8ee16c..1cdc496 100644 --- a/src/components/follows/FollowsView.tsx +++ b/src/components/follows/FollowsView.tsx @@ -6,7 +6,7 @@ import { useProfile } from "../../hooks/useProfile"; import { useNip05Verified } from "../../hooks/useNip05Verified"; import { fetchFollowers, ensureConnected } from "../../lib/nostr"; import { dbLoadFollowers, dbSaveFollowers } from "../../lib/db"; -import { shortenPubkey } from "../../lib/utils"; +import { shortenPubkey, profileName } from "../../lib/utils"; function FollowRow({ pubkey, @@ -16,9 +16,9 @@ function FollowRow({ followsYou?: boolean; }) { const profile = useProfile(pubkey); - const name = profile?.displayName || profile?.name || shortenPubkey(pubkey); - const avatar = profile?.picture; - const nip05 = profile?.nip05; + const name = profileName(profile, shortenPubkey(pubkey)); + const avatar = typeof profile?.picture === "string" ? profile.picture : undefined; + const nip05 = typeof profile?.nip05 === "string" ? profile.nip05 : undefined; const verified = useNip05Verified(pubkey, nip05); const { follows, follow, unfollow, pubkey: ownPubkey } = useUserStore(); diff --git a/src/components/profile/EditProfileForm.tsx b/src/components/profile/EditProfileForm.tsx index e9d0ce2..89ac3c8 100644 --- a/src/components/profile/EditProfileForm.tsx +++ b/src/components/profile/EditProfileForm.tsx @@ -7,14 +7,15 @@ import { Nip05Field } from "./Nip05Field"; export function EditProfileForm({ pubkey, onSaved }: { pubkey: string; onSaved: () => void }) { const { profile, fetchOwnProfile } = useUserStore(); - const [name, setName] = useState(profile?.name || ""); - const [displayName, setDisplayName] = useState(profile?.displayName || ""); - const [about, setAbout] = useState(profile?.about || ""); - const [picture, setPicture] = useState(profile?.picture || ""); - const [banner, setBanner] = useState(profile?.banner || ""); - const [website, setWebsite] = useState(profile?.website || ""); - const [nip05, setNip05] = useState(profile?.nip05 || ""); - const [lud16, setLud16] = useState(profile?.lud16 || ""); + const safeStr = (v: unknown) => (typeof v === "string" ? v : ""); + const [name, setName] = useState(safeStr(profile?.name)); + const [displayName, setDisplayName] = useState(safeStr(profile?.displayName)); + const [about, setAbout] = useState(safeStr(profile?.about)); + const [picture, setPicture] = useState(safeStr(profile?.picture)); + const [banner, setBanner] = useState(safeStr(profile?.banner)); + const [website, setWebsite] = useState(safeStr(profile?.website)); + const [nip05, setNip05] = useState(safeStr(profile?.nip05)); + const [lud16, setLud16] = useState(safeStr(profile?.lud16)); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [saved, setSaved] = useState(false); diff --git a/src/components/profile/ProfileView.tsx b/src/components/profile/ProfileView.tsx index 603829c..778ddac 100644 --- a/src/components/profile/ProfileView.tsx +++ b/src/components/profile/ProfileView.tsx @@ -6,7 +6,7 @@ import { useMuteStore } from "../../stores/mute"; import { useProfile } from "../../hooks/useProfile"; import { useReputation } from "../../hooks/useReputation"; import { fetchUserNotesNIP65, fetchAuthorArticles, getNDK } from "../../lib/nostr"; -import { shortenPubkey } from "../../lib/utils"; +import { shortenPubkey, profileName } from "../../lib/utils"; import { NoteCard } from "../feed/NoteCard"; import { ArticleCard } from "../article/ArticleCard"; import { ZapModal } from "../zap/ZapModal"; @@ -17,7 +17,7 @@ import { ProfileMediaGallery } from "./ProfileMediaGallery"; function TopFollowerAvatar({ pubkey }: { pubkey: string }) { const profile = useProfile(pubkey); const { openProfile } = useUIStore(); - const name = profile?.displayName || profile?.name || pubkey.slice(0, 8) + "…"; + const name = profileName(profile, pubkey.slice(0, 8) + "…"); return (