From a51f921fd9f6de60ccc0b1881d0b8e5c76a2fdc8 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Jan 2026 09:42:23 +0000 Subject: [PATCH] refactor: extract TopContributor into shared component - Create shared TopContributor component in src/components/nostr/TopContributor.tsx - Support two variants: "default" (larger, shows "sats" suffix) and "compact" (smaller, no suffix) - Update user menu to use shared component with default variant - Update welcome screen to use shared component with compact variant - Remove duplicate code and consolidate formatting logic - Maintain consistent visual design across both usages --- src/components/GrimoireWelcome.tsx | 36 ++----------------- src/components/nostr/TopContributor.tsx | 48 +++++++++++++++++++++++++ src/components/nostr/user-menu.tsx | 32 +---------------- 3 files changed, 52 insertions(+), 64 deletions(-) create mode 100644 src/components/nostr/TopContributor.tsx diff --git a/src/components/GrimoireWelcome.tsx b/src/components/GrimoireWelcome.tsx index abb0493..881b095 100644 --- a/src/components/GrimoireWelcome.tsx +++ b/src/components/GrimoireWelcome.tsx @@ -1,12 +1,11 @@ -import { Terminal, Trophy } from "lucide-react"; +import { Terminal } from "lucide-react"; import { Button } from "./ui/button"; import { Kbd, KbdGroup } from "./ui/kbd"; import { Progress } from "./ui/progress"; import supportersService, { MONTHLY_GOAL_SATS } from "@/services/supporters"; import { useLiveQuery } from "dexie-react-hooks"; import db from "@/services/db"; -import { useProfile } from "@/hooks/useProfile"; -import { getDisplayName } from "@/lib/nostr-utils"; +import { TopContributor } from "./nostr/TopContributor"; interface GrimoireWelcomeProps { onLaunchCommand: () => void; @@ -30,36 +29,6 @@ const EXAMPLE_COMMANDS = [ { command: "req -k 1 -l 20", description: "Query recent notes" }, ]; -function TopContributor({ - pubkey, - amount, -}: { - pubkey: string; - amount: number; -}) { - const profile = useProfile(pubkey); - const displayName = getDisplayName(pubkey, profile); - - function formatNumber(sats: number): string { - if (sats >= 1_000_000) { - return `${(sats / 1_000_000).toFixed(1)}M`; - } else if (sats >= 1_000) { - return `${Math.floor(sats / 1_000)}k`; - } - return sats.toString(); - } - - return ( -
- - - {displayName} - - {formatNumber(amount)} -
- ); -} - export function GrimoireWelcome({ onLaunchCommand, onExecuteCommand, @@ -187,6 +156,7 @@ export function GrimoireWelcome({ )} diff --git a/src/components/nostr/TopContributor.tsx b/src/components/nostr/TopContributor.tsx new file mode 100644 index 0000000..0c154c1 --- /dev/null +++ b/src/components/nostr/TopContributor.tsx @@ -0,0 +1,48 @@ +import { Trophy } from "lucide-react"; +import { useProfile } from "@/hooks/useProfile"; +import { getDisplayName } from "@/lib/nostr-utils"; + +interface TopContributorProps { + pubkey: string; + amount: number; + variant?: "default" | "compact"; +} + +export function TopContributor({ + pubkey, + amount, + variant = "default", +}: TopContributorProps) { + const profile = useProfile(pubkey); + const displayName = getDisplayName(pubkey, profile); + + function formatNumber(sats: number): string { + if (sats >= 1_000_000) { + return `${(sats / 1_000_000).toFixed(1)}M`; + } else if (sats >= 1_000) { + return `${Math.floor(sats / 1_000)}k`; + } + return sats.toString(); + } + + const isCompact = variant === "compact"; + + return ( +
+ + + {displayName} + + + {formatNumber(amount)} + {!isCompact && " sats"} + +
+ ); +} diff --git a/src/components/nostr/user-menu.tsx b/src/components/nostr/user-menu.tsx index aadba05..7b9b30f 100644 --- a/src/components/nostr/user-menu.tsx +++ b/src/components/nostr/user-menu.tsx @@ -8,7 +8,6 @@ import { Eye, EyeOff, Zap, - Trophy, } from "lucide-react"; import accounts from "@/services/accounts"; import { useProfile } from "@/hooks/useProfile"; @@ -40,6 +39,7 @@ import { import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import Nip05 from "./nip05"; import { RelayLink } from "./RelayLink"; +import { TopContributor } from "./TopContributor"; import SettingsDialog from "@/components/SettingsDialog"; import LoginDialog from "./LoginDialog"; import ConnectWalletDialog from "@/components/ConnectWalletDialog"; @@ -83,36 +83,6 @@ function UserLabel({ pubkey }: { pubkey: string }) { ); } -function TopContributor({ - pubkey, - amount, -}: { - pubkey: string; - amount: number; -}) { - const profile = useProfile(pubkey); - const displayName = getDisplayName(pubkey, profile); - - function formatSats(sats: number): string { - if (sats >= 1_000_000) { - return `${(sats / 1_000_000).toFixed(1)}M`; - } else if (sats >= 1_000) { - return `${Math.floor(sats / 1_000)}k`; - } - return sats.toString(); - } - - return ( -
- - - {displayName} - - {formatSats(amount)} sats -
- ); -} - export default function UserMenu() { const account = use$(accounts.active$); const { state, addWindow, disconnectNWC, toggleWalletBalancesBlur } =