From b54e48571a2c067045657c547815890d7c3a3486 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Jan 2026 22:22:43 +0000 Subject: [PATCH] feat: add Support tab to Settings with top donors and quick zap Add new Support tab in Settings page to highlight contributors: - Monthly goal progress bar (210k sats target) - Top 3 donors of the month with ranked display - Quick zap button to support Grimoire development - Reactive data from supporters service and DB - Uses UserName component for consistent styling Follows patterns from user-menu and welcome page. --- src/components/SettingsViewer.tsx | 135 +++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/src/components/SettingsViewer.tsx b/src/components/SettingsViewer.tsx index cc4eb85..861a2b3 100644 --- a/src/components/SettingsViewer.tsx +++ b/src/components/SettingsViewer.tsx @@ -9,11 +9,67 @@ import { import { Switch } from "./ui/switch"; import { useSettings } from "@/hooks/useSettings"; import { useTheme } from "@/lib/themes"; -import { Palette, FileEdit } from "lucide-react"; +import { Palette, FileEdit, Heart, Zap } from "lucide-react"; +import { Button } from "./ui/button"; +import { Progress } from "./ui/progress"; +import { useLiveQuery } from "dexie-react-hooks"; +import db from "@/services/db"; +import { useGrimoire } from "@/core/state"; +import { GRIMOIRE_DONATE_PUBKEY } from "@/lib/grimoire-members"; +import { MONTHLY_GOAL_SATS } from "@/services/supporters"; +import supportersService from "@/services/supporters"; +import { UserName } from "./nostr/UserName"; export function SettingsViewer() { const { settings, updateSetting } = useSettings(); const { themeId, setTheme, availableThemes } = useTheme(); + const { addWindow } = useGrimoire(); + + // Calculate monthly donations using supporters service + const monthlyDonations = + useLiveQuery(() => supportersService.getMonthlyDonations(), []) ?? 0; + + // Get top 3 donors of the month + const topDonors = useLiveQuery(async () => { + const thirtyDaysAgo = Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60; + const donorMap = new Map(); + + await db.grimoireZaps + .where("timestamp") + .aboveOrEqual(thirtyDaysAgo) + .each((zap) => { + const current = donorMap.get(zap.senderPubkey) || 0; + donorMap.set(zap.senderPubkey, current + zap.amountSats); + }); + + return Array.from(donorMap.entries()) + .map(([pubkey, sats]) => ({ pubkey, sats })) + .sort((a, b) => b.sats - a.sats) + .slice(0, 3); + }, []); + + // Calculate monthly donation progress + const goalProgress = (monthlyDonations / MONTHLY_GOAL_SATS) * 100; + + // Format sats for display + 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.toLocaleString(); + } + + function openZapWindow() { + addWindow( + "zap", + { + recipientPubkey: GRIMOIRE_DONATE_PUBKEY, + }, + "Support Grimoire", + ); + } return (
@@ -28,6 +84,10 @@ export function SettingsViewer() { Post + + + Support +
@@ -121,6 +181,79 @@ export function SettingsViewer() { + + +
+

Support Grimoire

+

+ Help support development with Lightning zaps +

+
+ + {/* Monthly Goal Progress */} +
+
+ Monthly Goal + + {goalProgress.toFixed(0)}% + +
+ +
+ + + {formatSats(monthlyDonations)} sats + + {" raised"} + + + {formatSats(MONTHLY_GOAL_SATS)} sats + +
+
+ + {/* Top Donors This Month */} + {topDonors && topDonors.length > 0 && ( +
+

Top Donors This Month

+
+ {topDonors.map((donor, index) => ( +
+
+ + #{index + 1} + + +
+ + {formatSats(donor.sats)} sats + +
+ ))} +
+
+ )} + + {/* Quick Zap Button */} +
+

Send Lightning Zap

+

+ Support Grimoire development with Bitcoin over Lightning +

+ +
+