From 73feb2b0d29d799f986c6ebdfb58a4dc91d79fcf Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Sun, 1 Mar 2026 12:22:42 -0500 Subject: [PATCH] feat(machine): omnipresent light/dark toggle and dynamic network badge Move light/dark toggle with Lucide Sun/Moon icons from IdleView to App.vue so it's visible on all screens. Detect Bitcoin network (mainnet/testnet/regtest) from BOLT-11 invoice prefix and persist in localStorage. Hide network badge on mainnet (implied). Co-Authored-By: Claude Opus 4.6 --- apps/machine/src/App.vue | 31 +++++++++++++++++++++---------- apps/machine/src/stores/atm.ts | 23 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/apps/machine/src/App.vue b/apps/machine/src/App.vue index b353984..b089610 100644 --- a/apps/machine/src/App.vue +++ b/apps/machine/src/App.vue @@ -2,13 +2,14 @@ import { onMounted, onUnmounted, ref, computed } from 'vue' import { useAtmStore } from '@/stores/atm' import { useTheme } from '@/composables/useTheme' +import { Sun, Moon } from 'lucide-vue-next' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' const atmStore = useAtmStore() const { current: currentTheme, themes, colorMode } = useTheme() const debugExpanded = ref(false) -const network = import.meta.env.VITE_NETWORK || 'regtest' +// Network detected dynamically from Lightning invoice prefix const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined function formatSats(sats: number): string { @@ -76,8 +77,8 @@ function toggleLiveServices() { -

ATM Unavailable

-

+

ATM Unavailable

+

This machine is temporarily out of service. Please try again later or use another machine.

@@ -89,18 +90,16 @@ function toggleLiveServices() { -

+
{{ formattedBtcPrice }} bal: {{ formatSats(atmStore.balanceSats) }} sats @@ -121,13 +120,25 @@ function toggleLiveServices() { Mock - {{ network }} + {{ atmStore.network }}
+ + +
{ const balanceSats = ref(null) const btcPrice = ref(null) // local fiat per BTC (for display) const btcPriceUsd = ref(null) // USD per BTC (always shown) + const network = ref<'mainnet' | 'testnet' | 'regtest' | null>( + (localStorage.getItem('atm-network') as 'mainnet' | 'testnet' | 'regtest') || null + ) let stopBalanceWatch: (() => void) | null = null let pricePollingInterval: ReturnType | null = null // Store reference to ATM services for direct calls let atmServicesRef: ATMServices | null = null + /** Detect Bitcoin network from a BOLT-11 invoice prefix (called once, persisted) */ + function detectNetworkFromInvoice(invoice: string) { + if (network.value) return // already detected + const lower = invoice.toLowerCase() + if (lower.startsWith('lnbcrt')) network.value = 'regtest' + else if (lower.startsWith('lntb')) network.value = 'testnet' + else if (lower.startsWith('lnbc')) network.value = 'mainnet' + if (network.value) { + localStorage.setItem('atm-network', network.value) + console.log('[ATM] Detected network:', network.value) + } + } + // Computed const currentState = computed(() => { if (!snapshot.value) return 'uninitialized' @@ -220,6 +236,11 @@ export const useAtmStore = defineStore('atm', () => { if ('cashOut' in state) currentNested = state.cashOut as string } + // Detect network from first invoice we see + if (newSnapshot.context.invoice) { + detectNetworkFromInvoice(newSnapshot.context.invoice) + } + // Auto-advance past waitingForCashTaken when HAL is connected, // because HAL's dispenseCash already calls waitForBillsRemoved() if ( @@ -394,6 +415,7 @@ export const useAtmStore = defineStore('atm', () => { paymentError.value = null try { + detectNetworkFromInvoice(invoice) console.log( '[ATM] Paying invoice:', invoice.slice(0, 40) + '...', @@ -999,6 +1021,7 @@ export const useAtmStore = defineStore('atm', () => { balanceSats, btcPrice, btcPriceUsd, + network, halServices, isPayingInvoice, isRequestingDebit,