feat(machine): add light/dark mode toggle with semantic color styling
Replace hardcoded Tailwind colors (bg-white/5, text-green-400, bg-black/30, etc.) with theme-aware semantic classes (bg-card, text-success, text-bitcoin, bg-destructive/20) across CashInView and CashOutView. Add colorMode support to useTheme composable with localStorage persistence and system preference detection. QRCode component now reacts to theme/mode changes via MutationObserver. Debug panel includes Light/Dark/System toggle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0406a21b0e
commit
f5f00108aa
5 changed files with 151 additions and 83 deletions
|
|
@ -6,7 +6,7 @@ import { Badge } from '@/components/ui/badge'
|
|||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const atmStore = useAtmStore()
|
||||
const { current: currentTheme, themes } = useTheme()
|
||||
const { current: currentTheme, themes, colorMode } = useTheme()
|
||||
const debugExpanded = ref(false)
|
||||
const network = import.meta.env.VITE_NETWORK || 'regtest'
|
||||
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined
|
||||
|
|
@ -122,6 +122,32 @@ function toggleLiveServices() {
|
|||
{{ theme.label }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
:variant="colorMode === 'light' ? 'default' : 'outline'"
|
||||
size="sm"
|
||||
class="h-6 text-xs"
|
||||
@click="colorMode = 'light'"
|
||||
>
|
||||
Light
|
||||
</Button>
|
||||
<Button
|
||||
:variant="colorMode === 'dark' ? 'default' : 'outline'"
|
||||
size="sm"
|
||||
class="h-6 text-xs"
|
||||
@click="colorMode = 'dark'"
|
||||
>
|
||||
Dark
|
||||
</Button>
|
||||
<Button
|
||||
:variant="colorMode === 'system' ? 'default' : 'outline'"
|
||||
size="sm"
|
||||
class="h-6 text-xs"
|
||||
@click="colorMode = 'system'"
|
||||
>
|
||||
System
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -13,10 +13,26 @@ const qrSize = computed(() => props.size ?? 200)
|
|||
const qrBackground = ref('#f5f0e6')
|
||||
const qrForeground = ref('#3c3836')
|
||||
|
||||
onMounted(() => {
|
||||
function readQrColors() {
|
||||
const styles = getComputedStyle(document.documentElement)
|
||||
qrBackground.value = styles.getPropertyValue('--qr').trim() || '#f5f0e6'
|
||||
qrForeground.value = styles.getPropertyValue('--qr-foreground').trim() || '#3c3836'
|
||||
}
|
||||
|
||||
// Re-read colors when theme or color mode changes (data-theme or class attributes)
|
||||
let observer: MutationObserver | null = null
|
||||
|
||||
onMounted(() => {
|
||||
readQrColors()
|
||||
observer = new MutationObserver(readQrColors)
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-theme', 'class'],
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
observer?.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { ref, watch } from 'vue'
|
||||
|
||||
export type ThemeId = 'gruvbox' | 'catppuccin' | 'cyberpunk' | 'dracula' | 'nord' | 'tokyo-night'
|
||||
export type ColorMode = 'light' | 'dark' | 'system'
|
||||
|
||||
export interface ThemeOption {
|
||||
id: ThemeId
|
||||
|
|
@ -16,13 +17,15 @@ export const themes: ThemeOption[] = [
|
|||
{ id: 'cyberpunk', label: 'Cyberpunk' },
|
||||
]
|
||||
|
||||
const STORAGE_KEY = 'lamassu-theme'
|
||||
const THEME_KEY = 'lamassu-theme'
|
||||
const MODE_KEY = 'lamassu-color-mode'
|
||||
|
||||
const current = ref<ThemeId>(loadTheme())
|
||||
const colorMode = ref<ColorMode>(loadColorMode())
|
||||
|
||||
function loadTheme(): ThemeId {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
const stored = localStorage.getItem(THEME_KEY)
|
||||
if (stored && themes.some((t) => t.id === stored)) {
|
||||
return stored as ThemeId
|
||||
}
|
||||
|
|
@ -32,6 +35,18 @@ function loadTheme(): ThemeId {
|
|||
return 'gruvbox'
|
||||
}
|
||||
|
||||
function loadColorMode(): ColorMode {
|
||||
try {
|
||||
const stored = localStorage.getItem(MODE_KEY)
|
||||
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
||||
return stored
|
||||
}
|
||||
} catch {
|
||||
// localStorage unavailable
|
||||
}
|
||||
return 'dark'
|
||||
}
|
||||
|
||||
function applyTheme(id: ThemeId) {
|
||||
const el = document.documentElement
|
||||
if (id === 'gruvbox') {
|
||||
|
|
@ -41,14 +56,42 @@ function applyTheme(id: ThemeId) {
|
|||
}
|
||||
}
|
||||
|
||||
function applyColorMode(mode: ColorMode) {
|
||||
const el = document.documentElement
|
||||
if (mode === 'system') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
el.classList.toggle('dark', prefersDark)
|
||||
} else {
|
||||
el.classList.toggle('dark', mode === 'dark')
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for system preference changes when mode is 'system'
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
mediaQuery.addEventListener('change', () => {
|
||||
if (colorMode.value === 'system') {
|
||||
applyColorMode('system')
|
||||
}
|
||||
})
|
||||
|
||||
// Apply on load
|
||||
applyTheme(current.value)
|
||||
applyColorMode(colorMode.value)
|
||||
|
||||
// Persist and apply on change
|
||||
watch(current, (id) => {
|
||||
applyTheme(id)
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, id)
|
||||
localStorage.setItem(THEME_KEY, id)
|
||||
} catch {
|
||||
// localStorage unavailable
|
||||
}
|
||||
})
|
||||
|
||||
watch(colorMode, (mode) => {
|
||||
applyColorMode(mode)
|
||||
try {
|
||||
localStorage.setItem(MODE_KEY, mode)
|
||||
} catch {
|
||||
// localStorage unavailable
|
||||
}
|
||||
|
|
@ -58,5 +101,6 @@ export function useTheme() {
|
|||
return {
|
||||
current,
|
||||
themes,
|
||||
colorMode,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
<template>
|
||||
<div class="flex min-h-0 flex-1 flex-col">
|
||||
<!-- Header -->
|
||||
<header class="flex items-center bg-black/30 px-4 pb-3 pt-12 sm:px-8 sm:py-4">
|
||||
<header class="flex items-center bg-foreground/10 px-4 pb-3 pt-12 sm:px-8 sm:py-4">
|
||||
<Button v-if="showCancelButton" variant="ghost" class="text-muted-foreground" @click="cancel">
|
||||
← Cancel
|
||||
</Button>
|
||||
|
|
@ -220,7 +220,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
<ScrollArea class="min-h-0 flex-1">
|
||||
<main class="flex min-h-0 flex-1 items-start justify-center p-4 sm:items-center sm:p-8">
|
||||
<!-- Fetching Rate -->
|
||||
<Card v-if="nestedState === 'fetchingRate'" class="w-full max-w-80 border-0 bg-white/5">
|
||||
<Card v-if="nestedState === 'fetchingRate'" class="w-full max-w-80 border-0 bg-card">
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
||||
<Skeleton class="h-10 w-10 rounded-full" />
|
||||
<p class="text-muted-foreground">Fetching exchange rate...</p>
|
||||
|
|
@ -228,10 +228,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</Card>
|
||||
|
||||
<!-- Inserting Bills -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'insertingBills'"
|
||||
class="w-full max-w-96 border-0 bg-white/5"
|
||||
>
|
||||
<Card v-else-if="nestedState === 'insertingBills'" class="w-full max-w-96 border-0 bg-card">
|
||||
<CardHeader class="text-center">
|
||||
<CardTitle>Insert Cash</CardTitle>
|
||||
<CardDescription v-if="context">
|
||||
|
|
@ -240,16 +237,16 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</CardHeader>
|
||||
<CardContent class="space-y-6">
|
||||
<!-- Bill Status -->
|
||||
<div v-if="context" class="rounded-lg bg-white/5 p-4">
|
||||
<div v-if="context" class="rounded-lg bg-card p-4">
|
||||
<div class="mb-2 flex justify-between">
|
||||
<span class="text-muted-foreground">Inserted:</span>
|
||||
<span class="text-2xl font-bold text-green-400"
|
||||
<span class="text-2xl font-bold text-success"
|
||||
>{{ atmStore.fiatSymbol }}{{ context.fiatAmount / 100 }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-muted-foreground">You'll receive:</span>
|
||||
<span class="font-semibold text-orange-400"
|
||||
<span class="font-semibold text-bitcoin"
|
||||
>{{ formatSats(context.satsAmount) }} sats</span
|
||||
>
|
||||
</div>
|
||||
|
|
@ -275,9 +272,9 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</p>
|
||||
|
||||
<!-- Dev: Simulate bill insertion -->
|
||||
<Alert v-if="atmStore.debugMode" class="border-red-500/50 bg-red-500/10">
|
||||
<Alert v-if="atmStore.debugMode" class="border-destructive/50 bg-destructive/10">
|
||||
<AlertDescription>
|
||||
<p class="mb-2 text-xs text-red-400">Dev: Simulate bills</p>
|
||||
<p class="mb-2 text-xs text-destructive">Dev: Simulate bills</p>
|
||||
<div class="flex flex-wrap justify-center gap-2">
|
||||
<Button
|
||||
v-for="denom in [1, 5, 10, 20, 50, 100]"
|
||||
|
|
@ -308,7 +305,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
<!-- Generating NDebit -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'generatingNdebit'"
|
||||
class="w-full max-w-80 border-0 bg-white/5"
|
||||
class="w-full max-w-80 border-0 bg-card"
|
||||
>
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
||||
<span class="animate-hourglass text-5xl">⏳</span>
|
||||
|
|
@ -319,13 +316,13 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
<!-- Display QR (unified lightning + clink) -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'displayingQR'"
|
||||
class="w-full max-w-[420px] border-0 bg-white/5"
|
||||
class="w-full max-w-[420px] border-0 bg-card"
|
||||
>
|
||||
<CardContent class="pt-6 space-y-5">
|
||||
<!-- Amount Display -->
|
||||
<div class="text-center">
|
||||
<p class="text-sm text-muted-foreground">Withdraw</p>
|
||||
<p class="text-3xl font-bold text-orange-400">
|
||||
<p class="text-3xl font-bold text-bitcoin">
|
||||
{{ formatSats(context?.satsAmount || 0) }} sats
|
||||
</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
|
|
@ -371,12 +368,12 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
v-if="isGeneratingLnurl && !context?.ndebitUri"
|
||||
class="h-[240px] w-[240px] rounded-xl"
|
||||
/>
|
||||
<div v-else-if="currentQrValue" class="rounded-xl overflow-hidden bg-white p-3">
|
||||
<div v-else-if="currentQrValue" class="rounded-xl overflow-hidden">
|
||||
<QRCode :value="currentQrValue" :size="216" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex h-[240px] w-[240px] items-center justify-center rounded-xl bg-white/10"
|
||||
class="flex h-[240px] w-[240px] items-center justify-center rounded-xl bg-muted"
|
||||
>
|
||||
<p class="text-sm text-muted-foreground">Generating...</p>
|
||||
</div>
|
||||
|
|
@ -423,14 +420,14 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</div>
|
||||
|
||||
<!-- Alternative: Manual Options (web-ui only, no keyboard on kiosk) -->
|
||||
<details v-if="!isElectron" class="rounded-lg bg-white/5 p-3">
|
||||
<details v-if="!isElectron" class="rounded-lg bg-card p-3">
|
||||
<summary class="cursor-pointer text-sm text-muted-foreground">
|
||||
Alternative: Manual payment options
|
||||
</summary>
|
||||
<div class="mt-4 space-y-4">
|
||||
<!-- CLINK Debit by pubkey -->
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-medium text-blue-400">Enter your npub/pubkey:</p>
|
||||
<p class="text-xs font-medium text-accent">Enter your npub/pubkey:</p>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
v-model="pubkeyInput"
|
||||
|
|
@ -451,7 +448,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
|
||||
<!-- Invoice paste -->
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-medium text-orange-400">Or paste your invoice:</p>
|
||||
<p class="text-xs font-medium text-bitcoin">Or paste your invoice:</p>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
v-model="invoiceInput"
|
||||
|
|
@ -470,7 +467,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="atmStore.paymentError" class="text-xs text-red-400">
|
||||
<p v-if="atmStore.paymentError" class="text-xs text-destructive">
|
||||
Error: {{ atmStore.paymentError }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -491,12 +488,9 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</Card>
|
||||
|
||||
<!-- Ask for Receipt -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'askForReceipt'"
|
||||
class="w-full max-w-80 border-0 bg-white/5"
|
||||
>
|
||||
<Card v-else-if="nestedState === 'askForReceipt'" class="w-full max-w-80 border-0 bg-card">
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
||||
<div class="text-6xl text-green-400">✅</div>
|
||||
<div class="text-6xl text-success">✅</div>
|
||||
<CardTitle>Payment Sent!</CardTitle>
|
||||
<p class="text-muted-foreground">Would you like a receipt?</p>
|
||||
<div class="flex h-32 w-32 items-center justify-center rounded-lg bg-white">
|
||||
|
|
@ -507,10 +501,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</Card>
|
||||
|
||||
<!-- Sending Receipt -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'sendingReceipt'"
|
||||
class="w-full max-w-80 border-0 bg-white/5"
|
||||
>
|
||||
<Card v-else-if="nestedState === 'sendingReceipt'" class="w-full max-w-80 border-0 bg-card">
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
||||
<span class="animate-hourglass text-5xl">⏳</span>
|
||||
<p class="text-muted-foreground">Sending receipt...</p>
|
||||
|
|
@ -518,13 +509,10 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</Card>
|
||||
|
||||
<!-- Complete -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'complete'"
|
||||
class="w-full max-w-80 border-0 bg-green-900/30"
|
||||
>
|
||||
<Card v-else-if="nestedState === 'complete'" class="w-full max-w-80 border-0 bg-success/20">
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8 text-center">
|
||||
<div class="text-6xl">🎉</div>
|
||||
<CardTitle class="text-green-400">Transaction Complete!</CardTitle>
|
||||
<CardTitle class="text-success">Transaction Complete!</CardTitle>
|
||||
<p v-if="context">
|
||||
You received
|
||||
<Badge variant="secondary">{{ formatSats(context.satsAmount) }} sats</Badge>
|
||||
|
|
@ -536,37 +524,40 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
<!-- Confirm Abandon Warning -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'confirmAbandon'"
|
||||
class="w-full max-w-80 border-0 bg-red-900/30"
|
||||
class="w-full max-w-80 border-0 bg-destructive/20"
|
||||
>
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8 text-center">
|
||||
<div class="text-6xl">⚠️</div>
|
||||
<CardTitle class="text-red-400">Cash Already Accepted</CardTitle>
|
||||
<CardTitle class="text-destructive">Cash Already Accepted</CardTitle>
|
||||
<p class="text-muted-foreground">
|
||||
Your {{ atmStore.fiatSymbol
|
||||
}}{{ context ? (context.fiatAmount / 100).toLocaleString() : '' }} has been accepted
|
||||
and cannot be returned. Please scan the QR code to receive your Bitcoin.
|
||||
</p>
|
||||
<Button
|
||||
class="w-full bg-gradient-to-r from-green-500 to-emerald-400 text-black"
|
||||
class="w-full bg-success text-success-foreground"
|
||||
@click="atmStore.send({ type: 'RETRY' })"
|
||||
>
|
||||
Show QR Code Again
|
||||
</Button>
|
||||
<Button variant="ghost" class="w-full text-red-400" @click="cancel">
|
||||
<Button variant="ghost" class="w-full text-destructive" @click="cancel">
|
||||
Abandon (lose cash)
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- Error -->
|
||||
<Card v-else-if="nestedState === 'error'" class="w-full max-w-80 border-0 bg-red-900/30">
|
||||
<Card
|
||||
v-else-if="nestedState === 'error'"
|
||||
class="w-full max-w-80 border-0 bg-destructive/20"
|
||||
>
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8 text-center">
|
||||
<div class="text-6xl">⚠️</div>
|
||||
<CardTitle class="text-red-400">Error</CardTitle>
|
||||
<CardTitle class="text-destructive">Error</CardTitle>
|
||||
<p class="text-muted-foreground">{{ context?.error || 'An error occurred' }}</p>
|
||||
<Button
|
||||
v-if="context && context.billsInserted.length > 0"
|
||||
class="w-full bg-gradient-to-r from-green-500 to-emerald-400 text-black"
|
||||
class="w-full bg-success text-success-foreground"
|
||||
@click="atmStore.send({ type: 'RETRY' })"
|
||||
>
|
||||
Try Again
|
||||
|
|
@ -581,7 +572,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</Card>
|
||||
|
||||
<!-- Fallback -->
|
||||
<Card v-else class="w-full max-w-80 border-0 bg-white/5">
|
||||
<Card v-else class="w-full max-w-80 border-0 bg-card">
|
||||
<CardContent class="p-8 text-center">
|
||||
<p class="text-muted-foreground">State: {{ nestedState }}</p>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ function formatFiat(cents: number): string {
|
|||
<template>
|
||||
<div class="flex min-h-0 flex-1 flex-col">
|
||||
<!-- Header -->
|
||||
<header class="flex items-center bg-black/30 px-4 pb-3 pt-12 sm:px-8 sm:py-4">
|
||||
<header class="flex items-center bg-foreground/10 px-4 pb-3 pt-12 sm:px-8 sm:py-4">
|
||||
<Button variant="ghost" class="text-muted-foreground" @click="cancel">← Cancel</Button>
|
||||
<h2 class="flex-1 text-center text-lg font-semibold sm:text-xl">Sell Bitcoin</h2>
|
||||
<div class="w-20"></div>
|
||||
|
|
@ -110,7 +110,7 @@ function formatFiat(cents: number): string {
|
|||
<ScrollArea class="min-h-0 flex-1">
|
||||
<main class="flex min-h-0 flex-1 items-start justify-center p-4 sm:items-center sm:p-8">
|
||||
<!-- Fetching Rate -->
|
||||
<Card v-if="nestedState === 'fetchingRate'" class="w-full max-w-80 border-0 bg-white/5">
|
||||
<Card v-if="nestedState === 'fetchingRate'" class="w-full max-w-80 border-0 bg-card">
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
||||
<Skeleton class="h-10 w-10 rounded-full" />
|
||||
<p class="text-muted-foreground">Fetching exchange rate...</p>
|
||||
|
|
@ -120,7 +120,7 @@ function formatFiat(cents: number): string {
|
|||
<!-- Selecting Amount - Main cash-out screen -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'selectingAmount'"
|
||||
class="w-full max-w-[520px] border-0 bg-white/5"
|
||||
class="w-full max-w-[520px] border-0 bg-card"
|
||||
>
|
||||
<CardHeader class="text-center">
|
||||
<CardTitle>Select Cash Amount</CardTitle>
|
||||
|
|
@ -137,9 +137,9 @@ function formatFiat(cents: number): string {
|
|||
<div
|
||||
v-for="denom in availableDenominations"
|
||||
:key="denom"
|
||||
class="flex items-center justify-between rounded-lg bg-white/5 px-4 py-3 sm:p-4"
|
||||
class="flex items-center justify-between rounded-lg bg-card px-4 py-3 sm:p-4"
|
||||
>
|
||||
<span class="text-xl font-bold text-green-400 sm:text-2xl"
|
||||
<span class="text-xl font-bold text-success sm:text-2xl"
|
||||
>{{ atmStore.fiatSymbol }}{{ denom }}</span
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
|
|
@ -169,9 +169,9 @@ function formatFiat(cents: number): string {
|
|||
</div>
|
||||
|
||||
<!-- Selected amount summary -->
|
||||
<div class="rounded-lg bg-white/10 p-6 text-center">
|
||||
<div class="rounded-lg bg-muted/50 p-6 text-center">
|
||||
<p class="text-sm text-muted-foreground">You will receive</p>
|
||||
<p class="text-4xl font-bold text-green-400">
|
||||
<p class="text-4xl font-bold text-success">
|
||||
{{ atmStore.fiatSymbol }}{{ context ? formatFiat(context.fiatAmount) : '0.00' }}
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
|
|
@ -202,7 +202,7 @@ function formatFiat(cents: number): string {
|
|||
</div>
|
||||
|
||||
<!-- Instructions -->
|
||||
<Alert class="border-orange-500/50 bg-orange-500/10">
|
||||
<Alert class="border-bitcoin/50 bg-bitcoin/10">
|
||||
<AlertDescription>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Select the cash amount you want to receive. You'll pay the equivalent in Bitcoin.
|
||||
|
|
@ -215,7 +215,7 @@ function formatFiat(cents: number): string {
|
|||
<!-- Generating Invoice -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'generatingInvoice'"
|
||||
class="w-full max-w-80 border-0 bg-white/5"
|
||||
class="w-full max-w-80 border-0 bg-card"
|
||||
>
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
||||
<Skeleton class="h-10 w-10 rounded-full" />
|
||||
|
|
@ -226,14 +226,14 @@ function formatFiat(cents: number): string {
|
|||
<!-- Displaying Invoice - Show QR for payment -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'displayingInvoice'"
|
||||
class="w-full max-w-[480px] border-0 bg-white/5"
|
||||
class="w-full max-w-[480px] border-0 bg-card"
|
||||
>
|
||||
<CardHeader class="text-center">
|
||||
<CardTitle>Scan to Pay</CardTitle>
|
||||
<CardDescription v-if="context">
|
||||
Pay <Badge variant="secondary">{{ formatSats(context.satsAmount) }} sats</Badge> to
|
||||
receive
|
||||
<Badge class="bg-green-600"
|
||||
<Badge class="bg-success"
|
||||
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</Badge
|
||||
>
|
||||
</CardDescription>
|
||||
|
|
@ -249,14 +249,14 @@ function formatFiat(cents: number): string {
|
|||
|
||||
<!-- Payment status -->
|
||||
<div class="flex flex-col items-center gap-4 py-4">
|
||||
<div class="h-12 w-12 animate-pulse rounded-full bg-orange-500/20">
|
||||
<div class="h-12 w-12 animate-pulse rounded-full bg-bitcoin/20">
|
||||
<div class="flex h-full items-center justify-center text-2xl">⏳</div>
|
||||
</div>
|
||||
<p class="text-center text-muted-foreground">Waiting for payment...</p>
|
||||
</div>
|
||||
|
||||
<!-- Invoice info with copy button (web-ui only) -->
|
||||
<div v-if="context?.invoice && !isElectron" class="rounded-lg bg-white/5 p-3">
|
||||
<div v-if="context?.invoice && !isElectron" class="rounded-lg bg-card p-3">
|
||||
<p class="mb-2 truncate text-center font-mono text-xs text-muted-foreground">
|
||||
{{ context.invoice.slice(0, 50) }}...
|
||||
</p>
|
||||
|
|
@ -268,9 +268,9 @@ function formatFiat(cents: number): string {
|
|||
</div>
|
||||
|
||||
<!-- Dev controls -->
|
||||
<Alert v-if="atmStore.debugMode" class="border-red-500/50 bg-red-500/10">
|
||||
<Alert v-if="atmStore.debugMode" class="border-destructive/50 bg-destructive/10">
|
||||
<AlertDescription class="text-center">
|
||||
<p class="mb-2 text-xs text-red-400">Dev: Use alice-pay or button below</p>
|
||||
<p class="mb-2 text-xs text-destructive">Dev: Use alice-pay or button below</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
|
|
@ -284,10 +284,7 @@ function formatFiat(cents: number): string {
|
|||
</Card>
|
||||
|
||||
<!-- Dispensing Cash -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'dispensingCash'"
|
||||
class="w-full max-w-80 border-0 bg-white/5"
|
||||
>
|
||||
<Card v-else-if="nestedState === 'dispensingCash'" class="w-full max-w-80 border-0 bg-card">
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
||||
<Skeleton class="h-10 w-10 rounded-full" />
|
||||
<p class="text-muted-foreground">Dispensing cash...</p>
|
||||
|
|
@ -298,14 +295,14 @@ function formatFiat(cents: number): string {
|
|||
<!-- Cash Ready -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'waitingForCashTaken'"
|
||||
class="w-full max-w-80 border-0 bg-green-900/30"
|
||||
class="w-full max-w-80 border-0 bg-success/20"
|
||||
>
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8 text-center">
|
||||
<div class="text-6xl text-green-400">✅</div>
|
||||
<CardTitle class="text-green-400">Cash Ready!</CardTitle>
|
||||
<div class="text-6xl text-success">✅</div>
|
||||
<CardTitle class="text-success">Cash Ready!</CardTitle>
|
||||
<p v-if="context">
|
||||
Please take your
|
||||
<Badge class="bg-green-600 text-lg"
|
||||
<Badge class="bg-success text-lg"
|
||||
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</Badge
|
||||
>
|
||||
</p>
|
||||
|
|
@ -320,10 +317,7 @@ function formatFiat(cents: number): string {
|
|||
</Card>
|
||||
|
||||
<!-- Ask for Receipt -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'askForReceipt'"
|
||||
class="w-full max-w-80 border-0 bg-white/5"
|
||||
>
|
||||
<Card v-else-if="nestedState === 'askForReceipt'" class="w-full max-w-80 border-0 bg-card">
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
||||
<CardTitle>Would you like a receipt?</CardTitle>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
|
|
@ -337,13 +331,10 @@ function formatFiat(cents: number): string {
|
|||
</Card>
|
||||
|
||||
<!-- Complete -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'complete'"
|
||||
class="w-full max-w-80 border-0 bg-green-900/30"
|
||||
>
|
||||
<Card v-else-if="nestedState === 'complete'" class="w-full max-w-80 border-0 bg-success/20">
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8 text-center">
|
||||
<div class="text-6xl">🎉</div>
|
||||
<CardTitle class="text-green-400">Transaction Complete!</CardTitle>
|
||||
<CardTitle class="text-success">Transaction Complete!</CardTitle>
|
||||
<p class="text-muted-foreground">Thank you!</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -351,11 +342,11 @@ function formatFiat(cents: number): string {
|
|||
<!-- Error -->
|
||||
<Card
|
||||
v-else-if="nestedState === 'error' || nestedState === 'dispenseError'"
|
||||
class="w-full max-w-80 border-0 bg-red-900/30"
|
||||
class="w-full max-w-80 border-0 bg-destructive/20"
|
||||
>
|
||||
<CardContent class="flex flex-col items-center gap-4 p-8 text-center">
|
||||
<div class="text-6xl">⚠️</div>
|
||||
<CardTitle class="text-red-400">Error</CardTitle>
|
||||
<CardTitle class="text-destructive">Error</CardTitle>
|
||||
<p class="text-muted-foreground">{{ context?.error || 'An error occurred' }}</p>
|
||||
<Button
|
||||
class="bg-gradient-to-r from-orange-500 to-yellow-400 text-black"
|
||||
|
|
@ -367,7 +358,7 @@ function formatFiat(cents: number): string {
|
|||
</Card>
|
||||
|
||||
<!-- Fallback -->
|
||||
<Card v-else class="w-full max-w-80 border-0 bg-white/5">
|
||||
<Card v-else class="w-full max-w-80 border-0 bg-card">
|
||||
<CardContent class="p-8 text-center">
|
||||
<p class="text-muted-foreground">State: {{ nestedState }}</p>
|
||||
</CardContent>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue