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
This commit is contained in:
Claude
2026-01-20 09:42:23 +00:00
parent 7a7160bdac
commit a51f921fd9
3 changed files with 52 additions and 64 deletions

View File

@@ -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 (
<div className="flex items-center gap-1.5 mt-1.5 pt-1.5 border-t border-border/30">
<Trophy className="size-3 text-yellow-500" />
<span className="text-[10px] text-muted-foreground flex-1 truncate">
{displayName}
</span>
<span className="text-[10px] font-medium">{formatNumber(amount)}</span>
</div>
);
}
export function GrimoireWelcome({
onLaunchCommand,
onExecuteCommand,
@@ -187,6 +156,7 @@ export function GrimoireWelcome({
<TopContributor
pubkey={topContributor.pubkey}
amount={topContributor.totalSats}
variant="compact"
/>
)}
</div>

View File

@@ -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 (
<div
className={`flex items-center gap-1.5 mt-${isCompact ? "1.5" : "2"} pt-${isCompact ? "1.5" : "2"} border-t border-border/${isCompact ? "30" : "50"}`}
>
<Trophy
className={`${isCompact ? "size-3" : "size-3.5"} text-yellow-500`}
/>
<span
className={`${isCompact ? "text-[10px]" : "text-xs"} text-muted-foreground flex-1 truncate`}
>
{displayName}
</span>
<span className={`${isCompact ? "text-[10px]" : "text-xs"} font-medium`}>
{formatNumber(amount)}
{!isCompact && " sats"}
</span>
</div>
);
}

View File

@@ -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 (
<div className="flex items-center gap-2 mt-2 pt-2 border-t border-border/50">
<Trophy className="size-3.5 text-yellow-500" />
<span className="text-xs text-muted-foreground flex-1 truncate">
{displayName}
</span>
<span className="text-xs font-medium">{formatSats(amount)} sats</span>
</div>
);
}
export default function UserMenu() {
const account = use$(accounts.active$);
const { state, addWindow, disconnectNWC, toggleWalletBalancesBlur } =