fix(machine): UI polish — fiat rate display, 15s receipt with QR, remove CLINK
- Show exchange rate as fiat/BTC (e.g. Q615,000/BTC) instead of sats/fiat - Show USD/BTC rate when currency != USD - Complete screen stays 15s (was 3s) with txid QR code for receipt photo - Add "Done" button for manual dismiss on complete screen - Remove CLINK ndebit UI (mode selector, pubkey entry) pending k1 fix (#23) - Remove askForReceipt/sendingReceipt screens (npub scan not active, #36) - Remove negative sign on commission display - Set timezone to America/Guatemala Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
31c5376317
commit
254fbd26f2
4 changed files with 163 additions and 248 deletions
|
|
@ -45,17 +45,12 @@ const showCancelButton = computed(() => {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
// CLINK Debit: customer's npub/pubkey
|
|
||||||
const pubkeyInput = ref('')
|
|
||||||
|
|
||||||
// Invoice input for manual payment
|
// Invoice input for manual payment
|
||||||
const invoiceInput = ref('')
|
const invoiceInput = ref('')
|
||||||
|
|
||||||
// Copy state for ndebit URI
|
// Copy state for ndebit URI
|
||||||
const copied = ref(false)
|
const copied = ref(false)
|
||||||
|
|
||||||
// QR display mode: unified (default), lnurl-only, or clink-only
|
|
||||||
const qrMode = ref<'unified' | 'lnurl' | 'clink'>('unified')
|
|
||||||
const lnurlWithdraw = ref<string | null>(null)
|
const lnurlWithdraw = ref<string | null>(null)
|
||||||
const isGeneratingLnurl = ref(false)
|
const isGeneratingLnurl = ref(false)
|
||||||
|
|
||||||
|
|
@ -77,32 +72,9 @@ watch(
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compute the unified QR value (lightning + clink on separate lines)
|
// QR value — LNURL-withdraw only (CLINK ndebit disabled until k1 single-use fix)
|
||||||
const unifiedQrValue = computed(() => {
|
|
||||||
const ndebit = atmStore.context?.ndebitUri
|
|
||||||
const lnurl = lnurlWithdraw.value
|
|
||||||
|
|
||||||
if (lnurl && ndebit) {
|
|
||||||
// Unified: lightning scheme first (more universal), then clink
|
|
||||||
return `lightning:${lnurl}\n${ndebit}`
|
|
||||||
} else if (lnurl) {
|
|
||||||
return `lightning:${lnurl}`
|
|
||||||
} else if (ndebit) {
|
|
||||||
return ndebit
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get the appropriate QR value based on mode
|
|
||||||
const currentQrValue = computed(() => {
|
const currentQrValue = computed(() => {
|
||||||
switch (qrMode.value) {
|
|
||||||
case 'unified':
|
|
||||||
return unifiedQrValue.value
|
|
||||||
case 'lnurl':
|
|
||||||
return lnurlWithdraw.value ? `lightning:${lnurlWithdraw.value}` : null
|
return lnurlWithdraw.value ? `lightning:${lnurlWithdraw.value}` : null
|
||||||
case 'clink':
|
|
||||||
return atmStore.context?.ndebitUri || null
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Copy current QR value to clipboard
|
// Copy current QR value to clipboard
|
||||||
|
|
@ -142,10 +114,6 @@ function finishInserting() {
|
||||||
atmStore.finishInserting()
|
atmStore.finishInserting()
|
||||||
}
|
}
|
||||||
|
|
||||||
function skipReceipt() {
|
|
||||||
atmStore.skipReceipt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a bill denomination can still be accepted without exceeding available balance
|
// Check if a bill denomination can still be accepted without exceeding available balance
|
||||||
function canAcceptBill(denomination: number): boolean {
|
function canAcceptBill(denomination: number): boolean {
|
||||||
const ctx = context.value
|
const ctx = context.value
|
||||||
|
|
@ -168,29 +136,6 @@ function simulateBill(denomination: number) {
|
||||||
atmStore.insertBill(denomination)
|
atmStore.insertBill(denomination)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert npub to hex pubkey if needed
|
|
||||||
function normalizePubkey(input: string): string {
|
|
||||||
const trimmed = input.trim()
|
|
||||||
if (trimmed.startsWith('npub1')) {
|
|
||||||
console.log('[CashIn] npub detected, would decode in production')
|
|
||||||
}
|
|
||||||
return trimmed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request CLINK Debit (Nostr-native payment)
|
|
||||||
async function submitDebitRequest() {
|
|
||||||
const pubkey = normalizePubkey(pubkeyInput.value)
|
|
||||||
if (!pubkey) return
|
|
||||||
|
|
||||||
// Basic validation - should be 64 hex chars or npub
|
|
||||||
if (!pubkey.startsWith('npub1') && !/^[0-9a-fA-F]{64}$/.test(pubkey)) {
|
|
||||||
alert('Please enter a valid Nostr pubkey (64 hex chars) or npub')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await atmStore.requestDebit(pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pay invoice (manual BOLT-11 payment)
|
// Pay invoice (manual BOLT-11 payment)
|
||||||
async function submitInvoice() {
|
async function submitInvoice() {
|
||||||
const invoice = invoiceInput.value.trim()
|
const invoice = invoiceInput.value.trim()
|
||||||
|
|
@ -209,7 +154,14 @@ function formatSats(sats: number): string {
|
||||||
return sats.toLocaleString()
|
return sats.toLocaleString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isRequestingDebit)
|
/** Convert sats-per-fiat-unit exchange rate to fiat-per-BTC display string */
|
||||||
|
function formatFiatPrice(rateOrPrice: number): string {
|
||||||
|
// If it looks like a sats/fiat rate, convert to fiat/BTC
|
||||||
|
const price = rateOrPrice > 10000 ? rateOrPrice : Math.round(100_000_000 / rateOrPrice)
|
||||||
|
return price.toLocaleString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -265,7 +217,13 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
||||||
|
|
||||||
<!-- Rate -->
|
<!-- Rate -->
|
||||||
<p v-if="context" class="text-xl text-muted-foreground">
|
<p v-if="context" class="text-xl text-muted-foreground">
|
||||||
Rate: {{ formatSats(context.exchangeRate) }} sats/{{ atmStore.fiatSymbol }}
|
{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
v-if="context && atmStore.fiatCode !== 'USD' && atmStore.btcPriceUsd"
|
||||||
|
class="text-lg text-muted-foreground"
|
||||||
|
>
|
||||||
|
${{ formatFiatPrice(atmStore.btcPriceUsd) }} USD/BTC
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Available balance -->
|
<!-- Available balance -->
|
||||||
|
|
@ -355,51 +313,14 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
||||||
<p class="text-xl text-muted-foreground">Waiting for wallet scan...</p>
|
<p class="text-xl text-muted-foreground">Waiting for wallet scan...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mode selector (web-ui only) -->
|
<!-- LNURL URI (web-ui only) -->
|
||||||
<div v-if="!isElectron" class="pt-4 text-center">
|
<div v-if="!isElectron && currentQrValue" class="pt-4 text-center space-y-1">
|
||||||
<p class="text-xs text-muted-foreground mb-2">Scan with</p>
|
|
||||||
<div class="flex flex-wrap gap-2 justify-center">
|
|
||||||
<Button
|
|
||||||
:variant="qrMode === 'unified' ? 'default' : 'outline'"
|
|
||||||
size="sm"
|
|
||||||
class="rounded-full px-4"
|
|
||||||
@click="qrMode = 'unified'"
|
|
||||||
>
|
|
||||||
Universal
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
:variant="qrMode === 'lnurl' ? 'default' : 'outline'"
|
|
||||||
size="sm"
|
|
||||||
class="rounded-full px-4"
|
|
||||||
@click="qrMode = 'lnurl'"
|
|
||||||
>
|
|
||||||
LNURL
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
:variant="qrMode === 'clink' ? 'default' : 'outline'"
|
|
||||||
size="sm"
|
|
||||||
class="rounded-full px-4"
|
|
||||||
@click="qrMode = 'clink'"
|
|
||||||
>
|
|
||||||
CLINK
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mode label + truncated URI (web-ui only) -->
|
|
||||||
<div v-if="!isElectron" class="text-center space-y-1">
|
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||||
{{
|
LNURL-withdraw
|
||||||
qrMode === 'unified'
|
|
||||||
? 'Universal'
|
|
||||||
: qrMode === 'lnurl'
|
|
||||||
? 'LNURL-withdraw'
|
|
||||||
: 'CLINK ndebit'
|
|
||||||
}}
|
|
||||||
</p>
|
</p>
|
||||||
<div v-if="currentQrValue" class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<code class="font-mono-code text-xs text-muted-foreground truncate max-w-[280px]">
|
<code class="font-mono-code text-xs text-muted-foreground truncate max-w-[280px]">
|
||||||
{{ currentQrValue.split('\n')[0]?.slice(0, 32) }}...
|
{{ currentQrValue.slice(0, 32) }}...
|
||||||
</code>
|
</code>
|
||||||
<Button variant="ghost" size="sm" class="h-6 w-6 p-0" @click="copyQrValue">
|
<Button variant="ghost" size="sm" class="h-6 w-6 p-0" @click="copyQrValue">
|
||||||
<span v-if="copied">✓</span>
|
<span v-if="copied">✓</span>
|
||||||
|
|
@ -467,38 +388,15 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Ask for Receipt (centered) -->
|
|
||||||
<div
|
|
||||||
v-else-if="nestedState === 'askForReceipt'"
|
|
||||||
key="askForReceipt"
|
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
|
||||||
>
|
|
||||||
<div class="text-[8vh] text-success">✅</div>
|
|
||||||
<h3 class="text-[3rem] font-bold">Sats Sent!</h3>
|
|
||||||
<p class="text-2xl text-muted-foreground">Would you like a receipt?</p>
|
|
||||||
<div class="flex h-32 w-32 items-center justify-center rounded-lg bg-qr">
|
|
||||||
<p class="text-qr-foreground">📱 Scan npub</p>
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="kiosk" @click="skipReceipt">No Thanks</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sending Receipt (centered) -->
|
|
||||||
<div
|
|
||||||
v-else-if="nestedState === 'sendingReceipt'"
|
|
||||||
key="sendingReceipt"
|
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6"
|
|
||||||
>
|
|
||||||
<BounceDots class="scale-150" />
|
|
||||||
<p class="text-2xl text-muted-foreground">Sending receipt...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Complete (centered, success accent) -->
|
<!-- Complete (centered, success accent) -->
|
||||||
<div
|
<div
|
||||||
v-else-if="nestedState === 'complete'"
|
v-else-if="nestedState === 'complete'"
|
||||||
key="complete"
|
key="complete"
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
class="flex flex-1 items-center justify-center gap-10 p-8"
|
||||||
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
||||||
>
|
>
|
||||||
|
<!-- Left side — details -->
|
||||||
|
<div class="flex flex-col items-center gap-5">
|
||||||
<div class="text-[8vh]">🎉</div>
|
<div class="text-[8vh]">🎉</div>
|
||||||
<h3 class="text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
<h3 class="text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
||||||
|
|
||||||
|
|
@ -515,14 +413,16 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-2xl">
|
||||||
<span class="text-muted-foreground">Rate</span>
|
<span class="text-muted-foreground">Rate</span>
|
||||||
<span>{{ formatSats(context.exchangeRate) }} sats/{{ atmStore.fiatSymbol }}</span>
|
<span
|
||||||
|
>{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-2xl">
|
||||||
<span class="text-muted-foreground"
|
<span class="text-muted-foreground"
|
||||||
>Commission ({{ (context.feePercent * 100).toFixed(2) }}%)</span
|
>Commission ({{ (context.feePercent * 100).toFixed(1) }}%)</span
|
||||||
>
|
>
|
||||||
<span class="text-destructive"
|
<span
|
||||||
>-{{
|
>{{
|
||||||
formatSats(
|
formatSats(
|
||||||
Math.floor(
|
Math.floor(
|
||||||
Math.floor((context.fiatAmount / 100) * context.exchangeRate) *
|
Math.floor((context.fiatAmount / 100) * context.exchangeRate) *
|
||||||
|
|
@ -540,7 +440,22 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-2xl text-muted-foreground">Thank you!</p>
|
<p class="text-xl text-muted-foreground">
|
||||||
|
Take a photo of this screen for your receipt
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button variant="outline" size="kiosk" @click="cancel"> Done </Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right side — txid QR -->
|
||||||
|
<div v-if="context?.txid" class="flex flex-col items-center gap-4">
|
||||||
|
<QRCode :value="context.txid" :size="280" />
|
||||||
|
<p
|
||||||
|
class="font-mono-code text-sm text-muted-foreground max-w-[300px] text-center break-all"
|
||||||
|
>
|
||||||
|
{{ context.txid }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error (centered, destructive accent) -->
|
<!-- Error (centered, destructive accent) -->
|
||||||
|
|
@ -586,30 +501,9 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
||||||
Alternative: Manual withdraw options
|
Alternative: Manual withdraw options
|
||||||
</summary>
|
</summary>
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
<!-- CLINK Debit by pubkey -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<p class="text-xs font-medium text-accent">Enter your npub/pubkey:</p>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<Input
|
|
||||||
v-model="pubkeyInput"
|
|
||||||
placeholder="npub1... or hex pubkey"
|
|
||||||
class="flex-1 font-mono-code text-xs"
|
|
||||||
:disabled="isProcessing"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
:disabled="!pubkeyInput.trim() || isProcessing"
|
|
||||||
@click="submitDebitRequest"
|
|
||||||
>
|
|
||||||
{{ atmStore.isRequestingDebit ? '...' : 'Send' }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Invoice paste -->
|
<!-- Invoice paste -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<p class="text-xs font-medium text-bitcoin">Or paste your invoice:</p>
|
<p class="text-xs font-medium text-bitcoin">Paste your invoice:</p>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
v-model="invoiceInput"
|
v-model="invoiceInput"
|
||||||
|
|
|
||||||
|
|
@ -98,14 +98,16 @@ function confirmCashTaken() {
|
||||||
atmStore.cashTaken()
|
atmStore.cashTaken()
|
||||||
}
|
}
|
||||||
|
|
||||||
function skipReceipt() {
|
|
||||||
atmStore.skipReceipt()
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatSats(sats: number): string {
|
function formatSats(sats: number): string {
|
||||||
return sats.toLocaleString()
|
return sats.toLocaleString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Convert sats-per-fiat-unit exchange rate to fiat-per-BTC display string */
|
||||||
|
function formatFiatPrice(rateOrPrice: number): string {
|
||||||
|
const price = rateOrPrice > 10000 ? rateOrPrice : Math.round(100_000_000 / rateOrPrice)
|
||||||
|
return price.toLocaleString()
|
||||||
|
}
|
||||||
|
|
||||||
function formatFiat(cents: number): string {
|
function formatFiat(cents: number): string {
|
||||||
return (cents / 100).toFixed(2)
|
return (cents / 100).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
@ -196,9 +198,15 @@ function formatFiat(cents: number): string {
|
||||||
<p v-if="context" class="mt-1 text-lg text-muted-foreground">
|
<p v-if="context" class="mt-1 text-lg text-muted-foreground">
|
||||||
Rate:
|
Rate:
|
||||||
<Badge variant="secondary" class="text-lg">
|
<Badge variant="secondary" class="text-lg">
|
||||||
{{ formatSats(context.exchangeRate) }} sats/{{ atmStore.fiatSymbol }}
|
{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC
|
||||||
</Badge>
|
</Badge>
|
||||||
</p>
|
</p>
|
||||||
|
<p
|
||||||
|
v-if="context && atmStore.fiatCode !== 'USD' && atmStore.btcPriceUsd"
|
||||||
|
class="mt-1 text-base text-muted-foreground"
|
||||||
|
>
|
||||||
|
${{ formatFiatPrice(atmStore.btcPriceUsd) }} USD/BTC
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
|
|
@ -339,27 +347,15 @@ function formatFiat(cents: number): string {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Ask for Receipt (centered) -->
|
|
||||||
<div
|
|
||||||
v-else-if="nestedState === 'askForReceipt'"
|
|
||||||
key="askForReceipt"
|
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
|
||||||
>
|
|
||||||
<h3 class="text-[3rem] font-bold">Would you like a receipt?</h3>
|
|
||||||
<p class="text-2xl text-muted-foreground">Scan your npub to receive a Nostr DM receipt</p>
|
|
||||||
<div class="flex h-32 w-32 items-center justify-center rounded-lg bg-qr">
|
|
||||||
<p class="text-qr-foreground">📱 Scan npub</p>
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="kiosk" @click="skipReceipt">No Thanks</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Complete (centered, success accent) -->
|
<!-- Complete (centered, success accent) -->
|
||||||
<div
|
<div
|
||||||
v-else-if="nestedState === 'complete'"
|
v-else-if="nestedState === 'complete'"
|
||||||
key="complete"
|
key="complete"
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
class="flex flex-1 items-center justify-center gap-10 p-8"
|
||||||
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
||||||
>
|
>
|
||||||
|
<!-- Left side — details -->
|
||||||
|
<div class="flex flex-col items-center gap-5">
|
||||||
<div class="text-[8vh]">🎉</div>
|
<div class="text-[8vh]">🎉</div>
|
||||||
<h3 class="text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
<h3 class="text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
||||||
|
|
||||||
|
|
@ -374,18 +370,22 @@ function formatFiat(cents: number): string {
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-2xl">
|
||||||
<span class="text-muted-foreground">Rate</span>
|
<span class="text-muted-foreground">Rate</span>
|
||||||
<span>{{ formatSats(context.exchangeRate) }} sats/{{ atmStore.fiatSymbol }}</span>
|
<span
|
||||||
|
>{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-2xl">
|
||||||
<span class="text-muted-foreground"
|
<span class="text-muted-foreground"
|
||||||
>Commission ({{ (context.feePercent * 100).toFixed(2) }}%)</span
|
>Commission ({{ (context.feePercent * 100).toFixed(1) }}%)</span
|
||||||
>
|
>
|
||||||
<span class="text-destructive"
|
<span
|
||||||
>-{{
|
>{{
|
||||||
formatSats(
|
formatSats(
|
||||||
|
Math.abs(
|
||||||
context.satsAmount -
|
context.satsAmount -
|
||||||
Math.floor((context.fiatAmount / 100) * context.exchangeRate)
|
Math.floor((context.fiatAmount / 100) * context.exchangeRate)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
sats</span
|
sats</span
|
||||||
>
|
>
|
||||||
|
|
@ -399,7 +399,22 @@ function formatFiat(cents: number): string {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-2xl text-muted-foreground">Thank you!</p>
|
<p class="text-xl text-muted-foreground">
|
||||||
|
Take a photo of this screen for your receipt
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button variant="outline" size="kiosk" @click="cancel"> Done </Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right side — txid QR -->
|
||||||
|
<div v-if="context?.txid" class="flex flex-col items-center gap-4">
|
||||||
|
<QRCode :value="context.txid" :size="280" />
|
||||||
|
<p
|
||||||
|
class="font-mono-code text-sm text-muted-foreground max-w-[300px] text-center break-all"
|
||||||
|
>
|
||||||
|
{{ context.txid }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error (centered, destructive accent) -->
|
<!-- Error (centered, destructive accent) -->
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Timezone - set to your location
|
# Timezone - set to your location
|
||||||
time.timeZone = "UTC";
|
time.timeZone = "America/Guatemala";
|
||||||
|
|
||||||
# Locale
|
# Locale
|
||||||
i18n.defaultLocale = "en_US.UTF-8";
|
i18n.defaultLocale = "en_US.UTF-8";
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,7 @@ export function createATMMachine(
|
||||||
},
|
},
|
||||||
delays: {
|
delays: {
|
||||||
TIMEOUT_MS: 300000, // 5 minutes
|
TIMEOUT_MS: 300000, // 5 minutes
|
||||||
COMPLETE_DELAY: 3000,
|
COMPLETE_DELAY: 15000,
|
||||||
},
|
},
|
||||||
}).createMachine({
|
}).createMachine({
|
||||||
id: 'atm',
|
id: 'atm',
|
||||||
|
|
@ -482,7 +482,7 @@ export function createATMMachine(
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
PAYMENT_RECEIVED: {
|
PAYMENT_RECEIVED: {
|
||||||
target: 'askForReceipt',
|
target: 'complete',
|
||||||
actions: 'setPaymentReceived',
|
actions: 'setPaymentReceived',
|
||||||
},
|
},
|
||||||
PAYMENT_FAILED: {
|
PAYMENT_FAILED: {
|
||||||
|
|
@ -523,6 +523,9 @@ export function createATMMachine(
|
||||||
after: {
|
after: {
|
||||||
COMPLETE_DELAY: '#atm.idle',
|
COMPLETE_DELAY: '#atm.idle',
|
||||||
},
|
},
|
||||||
|
on: {
|
||||||
|
CANCEL: '#atm.idle',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
on: {
|
on: {
|
||||||
|
|
@ -653,8 +656,8 @@ export function createATMMachine(
|
||||||
},
|
},
|
||||||
waitingForCashTaken: {
|
waitingForCashTaken: {
|
||||||
on: {
|
on: {
|
||||||
CASH_DISPENSED: 'askForReceipt',
|
CASH_DISPENSED: 'complete',
|
||||||
TIMEOUT: 'askForReceipt', // Assume taken
|
TIMEOUT: 'complete', // Assume taken
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
askForReceipt: {
|
askForReceipt: {
|
||||||
|
|
@ -680,6 +683,9 @@ export function createATMMachine(
|
||||||
after: {
|
after: {
|
||||||
COMPLETE_DELAY: '#atm.idle',
|
COMPLETE_DELAY: '#atm.idle',
|
||||||
},
|
},
|
||||||
|
on: {
|
||||||
|
CANCEL: '#atm.idle',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dispenseError: {
|
dispenseError: {
|
||||||
// Critical error - payment received but cash not dispensed
|
// Critical error - payment received but cash not dispensed
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue