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 <noreply@anthropic.com>
This commit is contained in:
parent
3a78fffb65
commit
73feb2b0d2
2 changed files with 44 additions and 10 deletions
|
|
@ -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() {
|
|||
<line x1="12" y1="9" x2="12" y2="13" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
<h1 class="text-3xl font-bold">ATM Unavailable</h1>
|
||||
<p class="max-w-md text-center text-lg text-muted-foreground">
|
||||
<h1 class="text-[3.5rem] font-bold">ATM Unavailable</h1>
|
||||
<p class="max-w-md text-center text-2xl text-muted-foreground">
|
||||
This machine is temporarily out of service. Please try again later or use another machine.
|
||||
</p>
|
||||
<p v-if="atmStore.debugMode" class="max-w-lg text-center font-mono text-sm text-destructive">
|
||||
|
|
@ -89,18 +90,16 @@ function toggleLiveServices() {
|
|||
<router-view />
|
||||
|
||||
<!-- Connection status + balance (top right) -->
|
||||
<div
|
||||
class="fixed right-2 top-2 z-50 flex flex-wrap items-center justify-end gap-1 sm:right-4 sm:top-4 sm:gap-2"
|
||||
>
|
||||
<div class="fixed right-6 top-6 z-50 flex flex-wrap items-center justify-end gap-2">
|
||||
<Badge
|
||||
v-if="formattedBtcPrice"
|
||||
class="bg-primary/10 font-mono text-primary border border-primary/30 text-xs sm:text-sm"
|
||||
class="bg-primary/10 font-mono text-primary border border-primary/30 text-base"
|
||||
>
|
||||
{{ formattedBtcPrice }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="atmStore.balanceSats !== null"
|
||||
class="bg-bitcoin/20 font-mono text-bitcoin border border-bitcoin/30 text-xs sm:text-sm"
|
||||
class="bg-bitcoin/20 font-mono text-bitcoin border border-bitcoin/30 text-base"
|
||||
>
|
||||
bal: {{ formatSats(atmStore.balanceSats) }} sats
|
||||
</Badge>
|
||||
|
|
@ -121,13 +120,25 @@ function toggleLiveServices() {
|
|||
</Badge>
|
||||
<Badge v-else class="bg-muted text-muted-foreground">Mock</Badge>
|
||||
<Badge
|
||||
v-if="network !== 'mainnet'"
|
||||
v-if="atmStore.network && atmStore.network !== 'mainnet'"
|
||||
class="bg-warning/20 text-warning border border-warning/30"
|
||||
>
|
||||
{{ network }}
|
||||
{{ atmStore.network }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<!-- Light/dark toggle (always visible) -->
|
||||
<Button
|
||||
variant="outline"
|
||||
size="kiosk"
|
||||
class="fixed bottom-6 right-6 z-50 gap-3"
|
||||
@click="colorMode = colorMode === 'dark' ? 'light' : 'dark'"
|
||||
>
|
||||
<Sun v-if="colorMode === 'dark'" :size="28" />
|
||||
<Moon v-else :size="28" />
|
||||
{{ colorMode === 'dark' ? 'Light' : 'Dark' }}
|
||||
</Button>
|
||||
|
||||
<!-- Debug overlay (dev only) -->
|
||||
<div
|
||||
v-if="atmStore.debugMode"
|
||||
|
|
|
|||
|
|
@ -151,12 +151,28 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
const balanceSats = ref<number | null>(null)
|
||||
const btcPrice = ref<number | null>(null) // local fiat per BTC (for display)
|
||||
const btcPriceUsd = ref<number | null>(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<typeof setInterval> | 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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue