mirror of
https://github.com/hoornet/vega.git
synced 2026-05-07 04:39:12 -07:00
Fix React error #31 crash from malformed Nostr profiles
Malformed profiles with non-string fields (e.g. nip05: {}) crashed
React's entire render tree in production. Add typeof guards and
profileName() utility across all components that render profile data.
Add ErrorBoundary in main.tsx to show crash details instead of blank screen.
This commit is contained in:
@@ -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<HTMLTextAreaElement>(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(() => {
|
||||
|
||||
@@ -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!);
|
||||
|
||||
@@ -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 <span className="text-accent">@{name}</span>;
|
||||
}
|
||||
|
||||
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) : "";
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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(<span key={i}>{seg.value}</span>);
|
||||
elements.push(<span key={i}>{typeof seg.value === "string" ? seg.value : String(seg.value)}</span>);
|
||||
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)}
|
||||
</a>
|
||||
);
|
||||
break;
|
||||
@@ -91,8 +92,8 @@ export function renderTextSegments(
|
||||
onClick={(e) => { e.stopPropagation(); tryOpenNostrEntity(seg.value); }}
|
||||
>
|
||||
@{resolveMentions
|
||||
? <MentionName pubkey={seg.mentionPubkey} fallback={seg.display ?? seg.value.slice(0, 12) + "…"} />
|
||||
: seg.display}
|
||||
? <MentionName pubkey={seg.mentionPubkey} fallback={String(seg.display ?? seg.value).slice(0, 12) + "…"} />
|
||||
: String(seg.display ?? seg.value)}
|
||||
</span>
|
||||
);
|
||||
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)}
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user