From 85b47a7a62fe30916f0ab2efe75b78ef2a2cceea Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 21 Mar 2026 22:39:13 -0500 Subject: [PATCH] Consolidate 4 duplicate vanish compact cards into VanishCardCompact The same stripes + glitch icon + title + npub + reason layout was duplicated across EmbeddedVanishCard, EmbeddedVanishPost, VanishEventContent compact mode, and an inline version in NoteCard's threaded branch. Extract a single VanishCardCompact component and replace all four copies with it. Net reduction of 76 lines. --- src/components/EmbeddedNote.tsx | 62 +++----------- src/components/NoteCard.tsx | 35 ++------ src/components/ReplyComposeModal.tsx | 52 +----------- src/components/VanishEventContent.tsx | 115 +++++++++++++++++--------- 4 files changed, 94 insertions(+), 170 deletions(-) diff --git a/src/components/EmbeddedNote.tsx b/src/components/EmbeddedNote.tsx index aaeeb324..8328627c 100644 --- a/src/components/EmbeddedNote.tsx +++ b/src/components/EmbeddedNote.tsx @@ -8,6 +8,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { EmojifiedText } from '@/components/CustomEmoji'; import { NoteCard } from '@/components/NoteCard'; import { ProfileHoverCard } from '@/components/ProfileHoverCard'; +import { VanishCardCompact } from '@/components/VanishEventContent'; import { useEvent } from '@/hooks/useEvent'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; @@ -105,7 +106,7 @@ export function EmbeddedNote({ eventId, relays, authorHint, className, disableHo // NIP-62 vanish events get their own dramatic inline card if (event.kind === VANISH_KIND) { - return ; + return ; } return ; @@ -356,8 +357,8 @@ function MaybeProfileHoverCard({ pubkey, disabled, children }: { pubkey: string; ); } -/** Inline embedded card for NIP-62 Request to Vanish events. */ -function EmbeddedVanishCard({ +/** Clickable wrapper around VanishCardCompact for embedded/quoted vanish events. */ +function EmbeddedVanishCardWrapper({ event, className, }: { @@ -365,22 +366,14 @@ function EmbeddedVanishCard({ className?: string; }) { const navigate = useNavigate(); - const npub = useMemo(() => nip19.npubEncode(event.pubkey), [event.pubkey]); const neventId = useMemo( () => nip19.neventEncode({ id: event.id, author: event.pubkey }), [event.id, event.pubkey], ); - const isGlobal = event.tags.some(([n, v]) => n === 'relay' && v === 'ALL_RELAYS'); - const reason = event.content || undefined; - return (
{ @@ -395,46 +388,11 @@ function EmbeddedVanishCard({ } }} > - {/* Top caution stripe */} -
- -
- {/* Header row */} -
- {/* Glitch icon */} -
-
- /// -
-
- ! -
-
- -
-

- {isGlobal ? 'Global Request to Vanish' : 'Request to Vanish'} -

-

- {npub} -

-
- - - {timeAgo(event.created_at)} - -
- - {/* Reason quote if available */} - {reason && ( -

- “{reason}” -

- )} -
- - {/* Bottom caution stripe */} -
+
); } diff --git a/src/components/NoteCard.tsx b/src/components/NoteCard.tsx index 826e5d88..d00e84f7 100644 --- a/src/components/NoteCard.tsx +++ b/src/components/NoteCard.tsx @@ -62,7 +62,7 @@ import { ReplyComposeModal } from "@/components/ReplyComposeModal"; import { ReplyContext } from "@/components/ReplyContext"; import { RepostMenu } from "@/components/RepostMenu"; import { ThemeContent } from "@/components/ThemeContent"; -import { VanishEventContent } from "@/components/VanishEventContent"; +import { VanishCardCompact } from "@/components/VanishEventContent"; import { ZapstoreAppContent } from "@/components/ZapstoreAppContent"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { getAvatarShape } from "@/lib/avatarShape"; @@ -632,7 +632,7 @@ export const NoteCard = memo(function NoteCard({ // ── Vanish layout (kind 62) — dramatic card, no author row ── if (isVanish) { - // Threaded vanish (ancestor in a reply thread — needs connector line) + // Threaded vanish (ancestor in a reply thread — needs connector line + avatar column) if (threaded || threadedLast) { return (
- {/* Glitch icon matching VanishEventContent compact style */} -
-
- /// -
-
- ! -
-
+ {avatarElement} {threaded && (
)}
- {/* Stripes are inside the bordered container so they don't escape visually */} -
-
-
-

- {event.tags.some(([n, v]) => n === 'relay' && v === 'ALL_RELAYS') ? 'Global Request to Vanish' : 'Request to Vanish'} -

-

- {nip19.npubEncode(event.pubkey)} -

- {event.content && ( -

- “{event.content}” -

- )} -
-
-
+ {!compact && ( <> {actionButtons} @@ -700,7 +675,7 @@ export const NoteCard = memo(function NoteCard({ onClick={handleCardClick} onAuxClick={handleAuxClick} > - + {!compact && ( <> {actionButtons} diff --git a/src/components/ReplyComposeModal.tsx b/src/components/ReplyComposeModal.tsx index 219d27d5..77d39809 100644 --- a/src/components/ReplyComposeModal.tsx +++ b/src/components/ReplyComposeModal.tsx @@ -14,6 +14,7 @@ import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { getAvatarShape } from '@/lib/avatarShape'; import { NoteContent } from '@/components/NoteContent'; import { ComposeBox } from '@/components/ComposeBox'; +import { VanishCardCompact } from '@/components/VanishEventContent'; import { ProfilePreview } from '@/components/ExternalContentHeader'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; @@ -160,62 +161,13 @@ function EmbeddedPost({ event }: { event: NostrEvent }) { // Kind 62 (Request to Vanish) — show a compact vanish preview if (event.kind === 62) { - return ; + return ; } return ; } -/** Compact embedded preview for NIP-62 vanish events in the reply composer. */ -function EmbeddedVanishPost({ event }: { event: NostrEvent }) { - const npub = useMemo(() => nip19.npubEncode(event.pubkey), [event.pubkey]); - const isGlobal = event.tags.some(([n, v]) => n === 'relay' && v === 'ALL_RELAYS'); - const reason = event.content || undefined; - return ( -
- {/* Top caution stripe */} -
- -
- {/* Header row */} -
-
-
- /// -
-
- ! -
-
- -
-

- {isGlobal ? 'Global Request to Vanish' : 'Request to Vanish'} -

-

- {npub} -

-
- - - {timeAgo(event.created_at)} - -
- - {/* Reason if available */} - {reason && ( -

- “{reason}” -

- )} -
- - {/* Bottom caution stripe */} -
-
- ); -} /** Compact embedded preview for regular note events. */ function EmbeddedNote({ event }: { event: NostrEvent }) { diff --git a/src/components/VanishEventContent.tsx b/src/components/VanishEventContent.tsx index 46b49d9b..bf602c4a 100644 --- a/src/components/VanishEventContent.tsx +++ b/src/components/VanishEventContent.tsx @@ -2,21 +2,97 @@ import type { NostrEvent } from '@nostrify/nostrify'; import { nip19 } from 'nostr-tools'; import { useMemo } from 'react'; +import { cn } from '@/lib/utils'; + interface VanishEventContentProps { event: NostrEvent; /** Compact mode for feed cards — shorter layout */ compact?: boolean; } +interface VanishCardCompactProps { + event: { pubkey: string; content: string; tags: string[][] }; + /** Optional className for the outer container */ + className?: string; + /** Optional timestamp text shown to the right of the title */ + timestamp?: string; +} + +/** + * Compact card for NIP-62 Request to Vanish events. + * + * Used everywhere a vanish event needs a small inline preview: feed cards, + * embedded quotes, reply composer, threaded ancestors, etc. Stripes render + * inside the bordered container so they stay visually contained. + */ +export function VanishCardCompact({ event, className, timestamp }: VanishCardCompactProps) { + const npub = useMemo(() => nip19.npubEncode(event.pubkey), [event.pubkey]); + const isGlobal = event.tags.some(([n, v]) => n === 'relay' && v === 'ALL_RELAYS'); + const reason = event.content || undefined; + + return ( +
+ {/* Top caution stripe */} +
+ +
+ {/* Header row */} +
+
+
+ /// +
+
+ ! +
+
+ +
+

+ {isGlobal ? 'Global Request to Vanish' : 'Request to Vanish'} +

+

+ {npub} +

+
+ + {timestamp && ( + + {timestamp} + + )} +
+ + {/* Reason quote if available */} + {reason && ( +

+ “{reason}” +

+ )} +
+ + {/* Bottom caution stripe */} +
+
+ ); +} + /** * Dramatic display for NIP-62 Request to Vanish (kind 62) events. * * These events represent a user permanently erasing their entire identity * from the Nostr network. The display is intentionally theatrical — red * caution stripes, glitch effects, and a "grand exit" aesthetic. + * + * This is the full detail-page view. For compact inline previews, use + * {@link VanishCardCompact} instead. */ export function VanishEventContent({ event, compact }: VanishEventContentProps) { - const npub = useMemo(() => nip19.npubEncode(event.pubkey), [event.pubkey]); + if (compact) { + return ; + } + + const npub = nip19.npubEncode(event.pubkey); const relayTags = event.tags.filter(([n]) => n === 'relay'); const isGlobal = relayTags.some(([, v]) => v === 'ALL_RELAYS'); const relayList = relayTags.map(([, v]) => v).filter((v) => v !== 'ALL_RELAYS'); @@ -31,43 +107,6 @@ export function VanishEventContent({ event, compact }: VanishEventContentProps) hour12: true, }); - if (compact) { - return ( -
- {/* Caution stripe header */} -
- -
- {/* Glitch icon */} -
-
- /// -
-
- ! -
-
- -
-

- {isGlobal ? 'Global Request to Vanish' : 'Request to Vanish'} -

-

- {npub} -

- {reason && ( -

- “{reason}” -

- )} -
-
- -
-
- ); - } - return (
{/* Top caution stripe band */}