mirror of
https://github.com/hoornet/vega.git
synced 2026-07-25 09:48:09 -07:00
Add V4V sidebar section with dashboard, settings, and history
- New V4V store with budget tracking, cap enforcement, and history - Dashboard: live streaming status, per-episode + weekly budget bars, stats - Settings: auto-enable toggle (guarded by caps), per-episode cap, weekly budget, default rate, info icons - History: expandable list of past V4V episodes with recipient breakdowns - Budget enforcement: streaming stops with toast when cap is hit - Auto-streaming: starts automatically for V4V episodes when enabled - Cap reached state: dashboard shows red card, ticker shows "(capped)" - V4VIndicator: slimmed popup, AUTO badge, "open v4v" nav link - Fix duplicate history entries on cap stop
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@ import { create } from "zustand";
|
||||
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
|
||||
type View = "feed" | "search" | "relays" | "settings" | "profile" | "thread" | "article-editor" | "article" | "articles" | "media" | "podcasts" | "about" | "zaps" | "dm" | "notifications" | "bookmarks" | "hashtag" | "follows";
|
||||
type View = "feed" | "search" | "relays" | "settings" | "profile" | "thread" | "article-editor" | "article" | "articles" | "media" | "podcasts" | "about" | "zaps" | "dm" | "notifications" | "bookmarks" | "hashtag" | "follows" | "v4v";
|
||||
type FeedTab = "global" | "following" | "trending";
|
||||
|
||||
interface ViewStackEntry {
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { create } from "zustand";
|
||||
import { useToastStore } from "./toast";
|
||||
|
||||
const STORAGE_KEY = "wrystr_v4v";
|
||||
const MAX_HISTORY = 500;
|
||||
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
export interface V4VHistoryEntry {
|
||||
episodeGuid: string;
|
||||
episodeTitle: string;
|
||||
showTitle: string;
|
||||
satsStreamed: number;
|
||||
satsBoosted: number;
|
||||
recipients: { name: string; address: string; sats: number }[];
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface V4VPersistedState {
|
||||
autoEnabled: boolean;
|
||||
perEpisodeCap: number;
|
||||
weeklyBudget: number;
|
||||
defaultRate: number;
|
||||
history: V4VHistoryEntry[];
|
||||
}
|
||||
|
||||
interface V4VState extends V4VPersistedState {
|
||||
currentEpisodeSats: number;
|
||||
capReachedReason: string | null; // Set when a cap stops streaming
|
||||
|
||||
// Computed
|
||||
weeklySpent: () => number;
|
||||
weeklyRemaining: () => number;
|
||||
isCapReached: () => boolean;
|
||||
|
||||
// Actions
|
||||
setAutoEnabled: (v: boolean) => void;
|
||||
setPerEpisodeCap: (v: number) => void;
|
||||
setWeeklyBudget: (v: number) => void;
|
||||
setDefaultRate: (v: number) => void;
|
||||
setCapReachedReason: (reason: string | null) => void;
|
||||
addHistoryEntry: (entry: V4VHistoryEntry) => void;
|
||||
addCurrentEpisodeSats: (amount: number) => void;
|
||||
resetCurrentEpisodeSats: () => void;
|
||||
}
|
||||
|
||||
function loadPersisted(): V4VPersistedState {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return { autoEnabled: false, perEpisodeCap: 0, weeklyBudget: 0, defaultRate: 10, history: [] };
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return { autoEnabled: false, perEpisodeCap: 0, weeklyBudget: 0, defaultRate: 10, history: [] };
|
||||
}
|
||||
}
|
||||
|
||||
function persist(state: V4VPersistedState) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function getPersistable(s: V4VState): V4VPersistedState {
|
||||
return {
|
||||
autoEnabled: s.autoEnabled,
|
||||
perEpisodeCap: s.perEpisodeCap,
|
||||
weeklyBudget: s.weeklyBudget,
|
||||
defaultRate: s.defaultRate,
|
||||
history: s.history,
|
||||
};
|
||||
}
|
||||
|
||||
const initial = loadPersisted();
|
||||
|
||||
export const useV4VStore = create<V4VState>((set, get) => ({
|
||||
...initial,
|
||||
currentEpisodeSats: 0,
|
||||
capReachedReason: null,
|
||||
|
||||
weeklySpent: () => {
|
||||
const cutoff = Date.now() - WEEK_MS;
|
||||
return get().history
|
||||
.filter((e) => e.timestamp > cutoff)
|
||||
.reduce((sum, e) => sum + e.satsStreamed + e.satsBoosted, 0);
|
||||
},
|
||||
|
||||
weeklyRemaining: () => {
|
||||
const { weeklyBudget } = get();
|
||||
if (weeklyBudget <= 0) return Infinity;
|
||||
return Math.max(0, weeklyBudget - get().weeklySpent());
|
||||
},
|
||||
|
||||
isCapReached: () => {
|
||||
const { perEpisodeCap, currentEpisodeSats } = get();
|
||||
if (perEpisodeCap > 0 && currentEpisodeSats >= perEpisodeCap) return true;
|
||||
if (get().weeklyRemaining() <= 0) return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
setAutoEnabled: (v) => {
|
||||
const { perEpisodeCap, weeklyBudget } = get();
|
||||
if (v && (perEpisodeCap <= 0 || weeklyBudget <= 0)) {
|
||||
useToastStore.getState().addToast(
|
||||
"Set per-episode cap and weekly budget before enabling auto-stream",
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
set({ autoEnabled: v });
|
||||
persist(getPersistable({ ...get(), autoEnabled: v }));
|
||||
},
|
||||
|
||||
setPerEpisodeCap: (v) => {
|
||||
set({ perEpisodeCap: v });
|
||||
const s = { ...get(), perEpisodeCap: v };
|
||||
if (s.autoEnabled && v <= 0) {
|
||||
set({ autoEnabled: false });
|
||||
s.autoEnabled = false;
|
||||
}
|
||||
persist(getPersistable(s));
|
||||
},
|
||||
|
||||
setWeeklyBudget: (v) => {
|
||||
set({ weeklyBudget: v });
|
||||
const s = { ...get(), weeklyBudget: v };
|
||||
if (s.autoEnabled && v <= 0) {
|
||||
set({ autoEnabled: false });
|
||||
s.autoEnabled = false;
|
||||
}
|
||||
persist(getPersistable(s));
|
||||
},
|
||||
|
||||
setDefaultRate: (v) => {
|
||||
set({ defaultRate: v });
|
||||
persist(getPersistable({ ...get(), defaultRate: v }));
|
||||
},
|
||||
|
||||
setCapReachedReason: (reason) => set({ capReachedReason: reason }),
|
||||
|
||||
addHistoryEntry: (entry) => {
|
||||
const history = [entry, ...get().history].slice(0, MAX_HISTORY);
|
||||
set({ history });
|
||||
persist(getPersistable({ ...get(), history }));
|
||||
},
|
||||
|
||||
addCurrentEpisodeSats: (amount) => {
|
||||
set((s) => ({ currentEpisodeSats: s.currentEpisodeSats + amount }));
|
||||
},
|
||||
|
||||
resetCurrentEpisodeSats: () => set({ currentEpisodeSats: 0, capReachedReason: null }),
|
||||
}));
|
||||
Reference in New Issue
Block a user