From 52b448619219458a74f7016a8fb74487f915437d Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Mon, 13 Apr 2026 20:55:32 +0000 Subject: [PATCH] Generated with Hive: Add shared refreshBalance to user store and update all L402 actions to auto-refresh balance --- src/components/boost/boost-button.tsx | 4 ++- src/components/layout/node-preview-panel.tsx | 3 +++ src/components/modals/add-content-modal.tsx | 5 +++- src/components/modals/budget-modal.tsx | 28 ++++---------------- src/components/search/search-bar.tsx | 8 +++--- src/stores/user-store.ts | 14 ++++++++++ 6 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/components/boost/boost-button.tsx b/src/components/boost/boost-button.tsx index 9ca71d72..2bb15a83 100644 --- a/src/components/boost/boost-button.tsx +++ b/src/components/boost/boost-button.tsx @@ -25,6 +25,7 @@ export function BoostButton({ refId, pubkey, boostCount = 0, className }: BoostB const isAdmin = useUserStore((s) => s.isAdmin) const setBudget = useUserStore((s) => s.setBudget) + const refreshBalance = useUserStore((s) => s.refreshBalance) const handleBoost = useCallback(async () => { if (boosting) return @@ -56,13 +57,14 @@ export function BoostButton({ refId, pubkey, boostCount = 0, className }: BoostB setCount((c) => c + DEFAULT_BOOST_AMOUNT) setFlash(true) setTimeout(() => setFlash(false), 600) + refreshBalance() } catch (err) { console.error("Boost failed:", err) setError("Boost failed. Please try again.") } finally { setBoosting(false) } - }, [refId, pubkey, boosting, isAdmin, setBudget]) + }, [refId, pubkey, boosting, isAdmin, setBudget, refreshBalance]) return (
diff --git a/src/components/layout/node-preview-panel.tsx b/src/components/layout/node-preview-panel.tsx index e63d6d0e..5567a202 100644 --- a/src/components/layout/node-preview-panel.tsx +++ b/src/components/layout/node-preview-panel.tsx @@ -9,6 +9,7 @@ import { ScrollArea } from "@/components/ui/scroll-area" import { api } from "@/lib/api" import { useMocks, MOCK_FULL_NODES } from "@/lib/mock-data" import { usePlayerStore } from "@/stores/player-store" +import { useUserStore } from "@/stores/user-store" import type { GraphNode } from "@/lib/graph-api" import type { SchemaNode } from "@/app/ontology/page" @@ -206,6 +207,7 @@ function PersonCard({ props }: { props: Record }) { export function NodePreviewPanel({ node, onBack, schemas }: NodePreviewPanelProps) { const [unlockState, setUnlockState] = useState("preview") const [fullNode, setFullNode] = useState(null) + const refreshBalance = useUserStore((s) => s.refreshBalance) const nodeType = node.node_type ?? "Unknown" const schema = schemas.find((s) => s.type === nodeType) @@ -242,6 +244,7 @@ export function NodePreviewPanel({ node, onBack, schemas }: NodePreviewPanelProp setFullNode(result) } setUnlockState("unlocked") + refreshBalance() } catch { setUnlockState("error") } diff --git a/src/components/modals/add-content-modal.tsx b/src/components/modals/add-content-modal.tsx index a7bcdb95..d75f3d39 100644 --- a/src/components/modals/add-content-modal.tsx +++ b/src/components/modals/add-content-modal.tsx @@ -28,6 +28,7 @@ import { export function AddContentModal() { const { activeModal, close, open: openModal } = useModalStore() const { budget, setBudget, pubKey } = useUserStore() + const refreshBalance = useUserStore((s) => s.refreshBalance) const [sourceUrl, setSourceUrl] = useState("") const [detectedType, setDetectedType] = useState(null) const [detecting, setDetecting] = useState(false) @@ -109,6 +110,7 @@ export function AddContentModal() { try { await submitWithAuth(trimmed, detectedType) setSuccess(true) + refreshBalance() setTimeout(() => { setSourceUrl("") setDetectedType(null) @@ -125,6 +127,7 @@ export function AddContentModal() { // Retry after payment await submitWithAuth(trimmed, detectedType) setSuccess(true) + refreshBalance() setTimeout(() => { setSourceUrl("") setDetectedType(null) @@ -148,7 +151,7 @@ export function AddContentModal() { } finally { setSubmitting(false) } - }, [sourceUrl, detectedType, close, setBudget, submitWithAuth]) + }, [sourceUrl, detectedType, close, setBudget, submitWithAuth, refreshBalance]) const handleOpenChange = useCallback( (open: boolean) => { diff --git a/src/components/modals/budget-modal.tsx b/src/components/modals/budget-modal.tsx index b938a97b..c13c7898 100644 --- a/src/components/modals/budget-modal.tsx +++ b/src/components/modals/budget-modal.tsx @@ -14,8 +14,7 @@ import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { useModalStore } from "@/stores/modal-store" import { useUserStore } from "@/stores/user-store" -import { isSphinx, getL402, hasWebLN, payInvoice, payL402, topUpLsat, topUpConfirm } from "@/lib/sphinx" -import { api } from "@/lib/api" +import { isSphinx, hasWebLN, payInvoice, payL402, topUpLsat, topUpConfirm } from "@/lib/sphinx" type Step = "balance" | "amount" | "invoice" | "success" @@ -24,6 +23,7 @@ const PRESET_AMOUNTS = [50, 100, 500, 1000] export function BudgetModal() { const { activeModal, close } = useModalStore() const { budget, setBudget } = useUserStore() + const refreshBalance = useUserStore((s) => s.refreshBalance) const [loading, setLoading] = useState(false) const [error, setError] = useState("") const [step, setStep] = useState("balance") @@ -66,15 +66,7 @@ export function BudgetModal() { } }, []) - const refreshBalance = useCallback(async () => { - const l402 = await getL402() - if (l402) { - const bal = await api.get<{ balance: number }>("/balance", { - Authorization: l402, - }) - setBudget(bal.balance) - } - }, [setBudget]) + // Route "Top Up" to the right flow const handleTopUp = useCallback(async () => { @@ -198,21 +190,11 @@ export function BudgetModal() { const handleRefreshBalance = useCallback(async () => { setLoading(true) try { - const l402 = await getL402() - if (!l402) { - setBudget(0) - return - } - const balance = await api.get<{ balance: number }>("/balance", { - Authorization: l402, - }) - setBudget(balance.balance) - } catch { - // keep existing budget + await refreshBalance() } finally { setLoading(false) } - }, [setBudget]) + }, [refreshBalance]) const canTopUp = sphinxConnected || weblnAvailable || hasExistingL402 diff --git a/src/components/search/search-bar.tsx b/src/components/search/search-bar.tsx index 72b4a2bf..dfdb6df5 100644 --- a/src/components/search/search-bar.tsx +++ b/src/components/search/search-bar.tsx @@ -14,7 +14,7 @@ import { useMocks, MOCK_NODES, MOCK_EDGES } from "@/lib/mock-data" export function SearchBar() { const setSearchTerm = useAppStore((s) => s.setSearchTerm) const { setGraphData, setLoading } = useGraphStore() - const setBudget = useUserStore((s) => s.setBudget) + const refreshBalance = useUserStore((s) => s.refreshBalance) const openModal = useModalStore((s) => s.open) const [value, setValue] = useState("") const [focused, setFocused] = useState(false) @@ -46,6 +46,7 @@ export function SearchBar() { } else { const result = await searchNodes(trimmed, { limit: 100 }, controller.signal) setGraphData(result.nodes ?? [], result.edges ?? []) + refreshBalance() } } catch (err) { if (err instanceof DOMException && err.name === "AbortError") return @@ -53,10 +54,11 @@ export function SearchBar() { // Handle 402 — need payment to search if (err instanceof Response && err.status === 402) { try { - await payL402(setBudget) + await payL402(() => {}) // Retry search after payment const result = await searchNodes(trimmed, { limit: 100 }, controller.signal) setGraphData(result.nodes ?? [], result.edges ?? []) + refreshBalance() } catch { // Payment failed or cancelled — open budget modal openModal("budget") @@ -69,7 +71,7 @@ export function SearchBar() { setLoading(false) } }, - [value, setSearchTerm, setGraphData, setLoading] + [value, setSearchTerm, setGraphData, setLoading, refreshBalance] ) const handleClear = useCallback(() => { diff --git a/src/stores/user-store.ts b/src/stores/user-store.ts index 18bdf9ff..87a16b63 100644 --- a/src/stores/user-store.ts +++ b/src/stores/user-store.ts @@ -1,6 +1,8 @@ "use client" import { create } from "zustand" +import { getL402 } from "@/lib/sphinx" +import { api } from "@/lib/api" interface UserState { isAdmin: boolean @@ -17,6 +19,7 @@ interface UserActions { setBudget: (val: number | null) => void incrementNodeCount: () => void resetNodeCount: () => void + refreshBalance: () => Promise } export type UserStore = UserState & UserActions @@ -34,4 +37,15 @@ export const useUserStore = create((set) => ({ incrementNodeCount: () => set((s) => ({ nodeCount: s.nodeCount + 1 })), resetNodeCount: () => set({ nodeCount: 0 }), + refreshBalance: async () => { + const l402 = await getL402() + if (!l402) { set({ budget: 0 }); return } + try { + const bal = await api.get<{ balance: number }>("/balance", { Authorization: l402 }) + set({ budget: bal.balance }) + } catch { + localStorage.removeItem("l402") + set({ budget: 0 }) + } + }, }))