diff --git a/src/components/GrimoireWelcome.tsx b/src/components/GrimoireWelcome.tsx index c9cf291..4e5d28a 100644 --- a/src/components/GrimoireWelcome.tsx +++ b/src/components/GrimoireWelcome.tsx @@ -5,6 +5,7 @@ import { Progress } from "./ui/progress"; import { MONTHLY_GOAL_SATS } from "@/services/supporters"; import { useLiveQuery } from "dexie-react-hooks"; import db from "@/services/db"; +import { useSettings } from "@/hooks/useSettings"; interface GrimoireWelcomeProps { onLaunchCommand: () => void; @@ -32,6 +33,8 @@ export function GrimoireWelcome({ onLaunchCommand, onExecuteCommand, }: GrimoireWelcomeProps) { + const { settings } = useSettings(); + // Calculate monthly donations reactively from DB (last 30 days) const monthlyDonations = useLiveQuery(async () => { @@ -135,7 +138,7 @@ export function GrimoireWelcome({
{description}
- {showProgress && ( + {showProgress && settings?.appearance?.showMonthlyGoal && (
diff --git a/src/components/SettingsViewer.tsx b/src/components/SettingsViewer.tsx index e0da63a..9c95f10 100644 --- a/src/components/SettingsViewer.tsx +++ b/src/components/SettingsViewer.tsx @@ -9,7 +9,18 @@ import { import { Switch } from "./ui/switch"; import { useSettings } from "@/hooks/useSettings"; import { useTheme } from "@/lib/themes"; -import { Palette, FileEdit, Heart, Trophy } from "lucide-react"; +import { + Palette, + FileEdit, + Heart, + HeartCrack, + Trophy, + Coffee, + Pizza, + Gift, + Star, + Crown, +} from "lucide-react"; import { Button } from "./ui/button"; import { Progress } from "./ui/progress"; import { useLiveQuery } from "dexie-react-hooks"; @@ -61,14 +72,22 @@ export function SettingsViewer() { return amount.toLocaleString(); } - // Contribution tiers - const contributionTiers = [210, 2100, 21000, 42000, 210000]; + // Contribution tiers with icons + const contributionTiers = [ + { amount: 210, icon: Coffee }, + { amount: 2100, icon: Pizza }, + { amount: 21000, icon: Gift }, + { amount: 42000, icon: Heart }, + { amount: 210000, icon: Star }, + { amount: 1000000, icon: Crown }, + ]; - function openSupportWindow() { + function openSupportWindow(amount: number) { addWindow( "zap", { recipientPubkey: GRIMOIRE_DONATE_PUBKEY, + defaultAmount: amount, }, "Support Grimoire", ); @@ -88,7 +107,11 @@ export function SettingsViewer() { Post - + {settings?.appearance?.showMonthlyGoal ? ( + + ) : ( + + )} Support @@ -189,12 +212,36 @@ export function SettingsViewer() {

Support Grimoire

- Help support development + Fund grimoire development

+ {/* Show Monthly Goal Toggle */} +
+
+ +

+ Display donation progress in UI +

+
+ + updateSetting("appearance", "showMonthlyGoal", checked) + } + /> +
+ {/* Monthly Goal Progress */} -
+
Monthly Goal @@ -219,14 +266,18 @@ export function SettingsViewer() {

Contribute

- {contributionTiers.map((amount) => ( + {contributionTiers.map(({ amount, icon: Icon }) => ( ))}
diff --git a/src/components/ZapWindow.tsx b/src/components/ZapWindow.tsx index 380f12b..d803f26 100644 --- a/src/components/ZapWindow.tsx +++ b/src/components/ZapWindow.tsx @@ -70,6 +70,8 @@ export interface ZapWindowProps { customTags?: string[][]; /** Relays where the zap receipt should be published */ relays?: string[]; + /** Optional default amount in sats to pre-select */ + defaultAmount?: number; } // Default preset amounts in sats @@ -99,6 +101,7 @@ export function ZapWindow({ onClose, customTags, relays: propsRelays, + defaultAmount, }: ZapWindowProps) { // Load event if we have a pointer - supports both EventPointer and AddressPointer const event = useNostrEvent(eventPointer || addressPointer); @@ -133,7 +136,9 @@ export function ZapWindow({ // Cache LNURL data for recipient's Lightning address const { data: lnurlData } = useLnurlCache(recipientProfile?.lud16); - const [selectedAmount, setSelectedAmount] = useState(null); + const [selectedAmount, setSelectedAmount] = useState( + defaultAmount || null, + ); const [customAmount, setCustomAmount] = useState(""); const [isProcessing, setIsProcessing] = useState(false); const [isPayingWithWallet, setIsPayingWithWallet] = useState(false); diff --git a/src/components/nostr/user-menu.tsx b/src/components/nostr/user-menu.tsx index 8d6806a..423f53e 100644 --- a/src/components/nostr/user-menu.tsx +++ b/src/components/nostr/user-menu.tsx @@ -49,6 +49,7 @@ import { GRIMOIRE_LIGHTNING_ADDRESS, } from "@/lib/grimoire-members"; import { MONTHLY_GOAL_SATS } from "@/services/supporters"; +import { useSettings } from "@/hooks/useSettings"; function UserAvatar({ pubkey }: { pubkey: string }) { const profile = useProfile(pubkey); @@ -83,6 +84,7 @@ export default function UserMenu() { const account = use$(accounts.active$); const { state, addWindow, disconnectNWC, toggleWalletBalancesBlur } = useGrimoire(); + const { settings } = useSettings(); const relays = state.activeAccount?.relays; const blossomServers = state.activeAccount?.blossomServers; const nwcConnection = state.nwcConnection; @@ -494,29 +496,33 @@ export default function UserMenu() { {/* Support Grimoire */} - - -
- - Support Grimoire -
- -
- - - {formatSats(monthlyDonations)} - - {" / "} - {formatSats(MONTHLY_GOAL_SATS)} - - - {goalProgress.toFixed(0)}% - -
-
+ {settings?.appearance?.showMonthlyGoal && ( + <> + + +
+ + Support Grimoire +
+ +
+ + + {formatSats(monthlyDonations)} + + {" / "} + {formatSats(MONTHLY_GOAL_SATS)} + + + {goalProgress.toFixed(0)}% + +
+
+ + )} {/* Logout at bottom for logged in users */} {account && ( diff --git a/src/lib/zap-parser.ts b/src/lib/zap-parser.ts index 515a02d..cf53229 100644 --- a/src/lib/zap-parser.ts +++ b/src/lib/zap-parser.ts @@ -22,6 +22,8 @@ export interface ParsedZapCommand { customTags?: string[][]; /** Relays where the zap receipt should be published */ relays?: string[]; + /** Optional default amount in sats to pre-select */ + defaultAmount?: number; } /** @@ -33,6 +35,7 @@ export interface ParsedZapCommand { * - `zap ` - Zap a specific person for a specific event * * Options: + * - `-a, --amount ` - Pre-select amount in sats * - `-T, --tag [relay]` - Add custom tag (can be repeated) * - `-r, --relay ` - Add relay for zap receipt publication (can be repeated) * @@ -53,12 +56,29 @@ export async function parseZapCommand( const positionalArgs: string[] = []; const customTags: string[][] = []; const relays: string[] = []; + let defaultAmount: number | undefined; let i = 0; while (i < args.length) { const arg = args[i]; - if (arg === "-T" || arg === "--tag") { + if (arg === "-a" || arg === "--amount") { + // Parse amount: -a + const amountStr = args[i + 1]; + if (!amountStr) { + throw new Error("Amount option requires a value: -a "); + } + + const amount = parseInt(amountStr, 10); + if (isNaN(amount) || amount <= 0) { + throw new Error( + `Invalid amount: ${amountStr}. Must be a positive number.`, + ); + } + + defaultAmount = amount; + i += 2; + } else if (arg === "-T" || arg === "--tag") { // Parse tag: -T [relay-hint] // Minimum 2 values after -T (type and value), optional relay hint const tagType = args[i + 1]; @@ -122,7 +142,7 @@ export async function parseZapCommand( const firstArg = positionalArgs[0]; const secondArg = positionalArgs[1]; - // Build result with optional custom tags and relays + // Build result with optional custom tags, relays, and amount const buildResult = ( recipientPubkey: string, pointer?: EventPointer | AddressPointer, @@ -138,6 +158,7 @@ export async function parseZapCommand( } if (customTags.length > 0) result.customTags = customTags; if (relays.length > 0) result.relays = relays; + if (defaultAmount !== undefined) result.defaultAmount = defaultAmount; return result; }; diff --git a/src/services/settings.ts b/src/services/settings.ts index b0f45fa..629c30c 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -37,6 +37,8 @@ export interface AppearanceSettings { animationsEnabled: boolean; /** Accent color (hue value 0-360) */ accentHue: number; + /** Show monthly donation goal in UI (user menu, welcome page, settings) */ + showMonthlyGoal: boolean; } /** @@ -153,6 +155,7 @@ const DEFAULT_APPEARANCE_SETTINGS: AppearanceSettings = { fontSizeMultiplier: 1.0, animationsEnabled: true, accentHue: 280, // Purple + showMonthlyGoal: true, }; const DEFAULT_RELAY_SETTINGS: RelaySettings = {