feat(machine): redesign cash-out with split-screen layout
Split-screen for displayingInvoice (info left, QR right), full-width for selectingAmount. Add StepIndicator with warning flow accent header. Replace Skeleton with BounceDots, remove ScrollArea and Card wrappers, wrap states in Transition for fade animations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d3e652bce3
commit
3d9d85ba7f
1 changed files with 249 additions and 224 deletions
|
|
@ -3,17 +3,31 @@ import { watch, computed, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAtmStore } from '@/stores/atm'
|
import { useAtmStore } from '@/stores/atm'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
|
||||||
import QRCode from '@/components/QRCode.vue'
|
import QRCode from '@/components/QRCode.vue'
|
||||||
|
import BounceDots from '@/components/BounceDots.vue'
|
||||||
|
import StepIndicator from '@/components/StepIndicator.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const atmStore = useAtmStore()
|
const atmStore = useAtmStore()
|
||||||
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined
|
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined
|
||||||
|
|
||||||
|
const cashOutSteps = ['Select', 'Pay', 'Collect']
|
||||||
|
|
||||||
|
const currentStepIndex = computed(() => {
|
||||||
|
switch (nestedState.value) {
|
||||||
|
case 'fetchingRate':
|
||||||
|
case 'selectingAmount':
|
||||||
|
return 0
|
||||||
|
case 'generatingInvoice':
|
||||||
|
case 'displayingInvoice':
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Copy invoice functionality
|
// Copy invoice functionality
|
||||||
const copyButtonText = ref('Copy Invoice')
|
const copyButtonText = ref('Copy Invoice')
|
||||||
|
|
||||||
|
|
@ -99,172 +113,168 @@ function formatFiat(cents: number): string {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-0 flex-1 flex-col">
|
<div class="flex min-h-0 flex-1 flex-col">
|
||||||
<!-- Header -->
|
<!-- Header with warning flow accent -->
|
||||||
<header class="flex items-center bg-foreground/10 px-4 pb-3 pt-12 sm:px-8 sm:py-4">
|
<header
|
||||||
<Button variant="ghost" class="text-muted-foreground" @click="cancel">← Cancel</Button>
|
class="flex items-center px-8 py-4"
|
||||||
<h2 class="flex-1 text-center text-lg font-semibold sm:text-xl">Sell Bitcoin</h2>
|
style="background: color-mix(in srgb, var(--warning) 10%, var(--background))"
|
||||||
|
>
|
||||||
|
<Button variant="ghost" size="kiosk" class="text-muted-foreground" @click="cancel"
|
||||||
|
>← Cancel</Button
|
||||||
|
>
|
||||||
|
<div class="flex flex-1 flex-col items-center gap-2">
|
||||||
|
<h2 class="text-[2.5rem] font-semibold">Sell Bitcoin</h2>
|
||||||
|
<StepIndicator :steps="cashOutSteps" :current-step="currentStepIndex" />
|
||||||
|
</div>
|
||||||
<div class="w-20"></div>
|
<div class="w-20"></div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content area -->
|
||||||
<ScrollArea class="min-h-0 flex-1">
|
<main class="flex 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">
|
<Transition name="state-fade" mode="out-in">
|
||||||
<!-- Fetching Rate -->
|
<!-- Fetching Rate (centered) -->
|
||||||
<Card v-if="nestedState === 'fetchingRate'" class="w-full max-w-80 border-0 bg-card">
|
<div
|
||||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
v-if="nestedState === 'fetchingRate'"
|
||||||
<Skeleton class="h-10 w-10 rounded-full" />
|
key="fetchingRate"
|
||||||
<p class="text-muted-foreground">Fetching exchange rate...</p>
|
class="flex flex-1 flex-col items-center justify-center gap-6"
|
||||||
</CardContent>
|
>
|
||||||
</Card>
|
<BounceDots class="scale-150" />
|
||||||
|
<p class="text-2xl text-muted-foreground">Fetching exchange rate...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Selecting Amount - Main cash-out screen -->
|
<!-- Selecting Amount (full-width) -->
|
||||||
<Card
|
<div
|
||||||
v-else-if="nestedState === 'selectingAmount'"
|
v-else-if="nestedState === 'selectingAmount'"
|
||||||
class="w-full max-w-[520px] border-0 bg-card"
|
key="selectingAmount"
|
||||||
|
class="flex flex-1 flex-col justify-center px-[8vw] py-6 gap-6"
|
||||||
>
|
>
|
||||||
<CardHeader class="text-center">
|
<!-- Denomination grid -->
|
||||||
<CardTitle>Select Cash Amount</CardTitle>
|
<div class="grid grid-cols-2 gap-6">
|
||||||
<CardDescription v-if="context">
|
<div
|
||||||
Rate:
|
v-for="denom in availableDenominations"
|
||||||
<Badge variant="secondary"
|
:key="denom"
|
||||||
>{{ formatSats(context.exchangeRate) }} sats/{{ atmStore.fiatSymbol }}</Badge
|
class="flex items-center justify-between rounded-lg bg-card px-6 py-5"
|
||||||
|
>
|
||||||
|
<span class="text-[2.5rem] font-bold text-success"
|
||||||
|
>{{ atmStore.fiatSymbol }}{{ denom }}</span
|
||||||
>
|
>
|
||||||
</CardDescription>
|
<div class="flex items-center gap-3">
|
||||||
</CardHeader>
|
<Button
|
||||||
<CardContent class="space-y-6">
|
variant="outline"
|
||||||
<!-- Denomination buttons -->
|
size="kiosk-icon"
|
||||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4">
|
:disabled="!canRemove(denom)"
|
||||||
<div
|
@click="atmStore.removeDenomination(denom)"
|
||||||
v-for="denom in availableDenominations"
|
|
||||||
:key="denom"
|
|
||||||
class="flex items-center justify-between rounded-lg bg-card px-4 py-3 sm:p-4"
|
|
||||||
>
|
|
||||||
<span class="text-xl font-bold text-success sm:text-2xl"
|
|
||||||
>{{ atmStore.fiatSymbol }}{{ denom }}</span
|
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3">
|
-
|
||||||
<Button
|
</Button>
|
||||||
variant="outline"
|
<span class="w-16 text-center text-[2rem] font-medium">{{
|
||||||
size="sm"
|
selectionCounts[denom] || 0
|
||||||
class="h-9 w-9"
|
}}</span>
|
||||||
:disabled="!canRemove(denom)"
|
<Button
|
||||||
@click="atmStore.removeDenomination(denom)"
|
variant="outline"
|
||||||
>
|
size="kiosk-icon"
|
||||||
-
|
:disabled="!canAdd(denom)"
|
||||||
</Button>
|
@click="atmStore.addDenomination(denom)"
|
||||||
<span class="w-8 text-center text-lg font-medium">{{
|
>
|
||||||
selectionCounts[denom] || 0
|
+
|
||||||
}}</span>
|
</Button>
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
class="h-9 w-9"
|
|
||||||
:disabled="!canAdd(denom)"
|
|
||||||
@click="atmStore.addDenomination(denom)"
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Selected amount summary -->
|
<!-- Amount summary -->
|
||||||
<div class="rounded-lg bg-muted/50 p-6 text-center">
|
<div class="text-center py-4">
|
||||||
<p class="text-sm text-muted-foreground">You will receive</p>
|
<p class="text-xl text-muted-foreground">You will receive</p>
|
||||||
<p class="text-4xl font-bold text-success">
|
<p class="text-[7vh] font-bold text-success leading-tight">
|
||||||
{{ atmStore.fiatSymbol }}{{ context ? formatFiat(context.fiatAmount) : '0.00' }}
|
{{ atmStore.fiatSymbol }}{{ context ? formatFiat(context.fiatAmount) : '0.00' }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-2xl text-muted-foreground">
|
||||||
|
You pay:
|
||||||
|
<Badge variant="secondary" class="ml-1 text-2xl">
|
||||||
|
{{ context ? formatSats(context.satsAmount) : 0 }} sats
|
||||||
|
</Badge>
|
||||||
|
</p>
|
||||||
|
<p v-if="context" class="mt-1 text-lg text-muted-foreground">
|
||||||
|
Rate:
|
||||||
|
<Badge variant="secondary" class="text-lg">
|
||||||
|
{{ formatSats(context.exchangeRate) }} sats/{{ atmStore.fiatSymbol }}
|
||||||
|
</Badge>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="kiosk-lg"
|
||||||
|
class="flex-1"
|
||||||
|
:disabled="!context?.cashOutSelection?.length"
|
||||||
|
@click="atmStore.clearSelection()"
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="kiosk-lg"
|
||||||
|
class="flex-1 bg-gradient-to-r from-orange-500 to-yellow-400 text-black hover:from-orange-600 hover:to-yellow-500"
|
||||||
|
:disabled="!context?.cashOutSelection?.length"
|
||||||
|
@click="atmStore.confirmAmount()"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Instructions -->
|
||||||
|
<Alert class="border-bitcoin/50 bg-bitcoin/10">
|
||||||
|
<AlertDescription>
|
||||||
|
<p class="text-xl text-muted-foreground">
|
||||||
|
Select the cash amount you want to receive. You'll pay the equivalent in Bitcoin.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-sm text-muted-foreground">
|
</AlertDescription>
|
||||||
You pay:
|
</Alert>
|
||||||
<Badge variant="secondary" class="ml-1">
|
</div>
|
||||||
{{ context ? formatSats(context.satsAmount) : 0 }} sats
|
|
||||||
</Badge>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action buttons -->
|
<!-- Generating Invoice (centered) -->
|
||||||
<div class="flex gap-4">
|
<div
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
class="flex-1"
|
|
||||||
:disabled="!context?.cashOutSelection?.length"
|
|
||||||
@click="atmStore.clearSelection()"
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="flex-1 bg-gradient-to-r from-orange-500 to-yellow-400 text-black hover:from-orange-600 hover:to-yellow-500"
|
|
||||||
:disabled="!context?.cashOutSelection?.length"
|
|
||||||
@click="atmStore.confirmAmount()"
|
|
||||||
>
|
|
||||||
Continue
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Instructions -->
|
|
||||||
<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.
|
|
||||||
</p>
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Generating Invoice -->
|
|
||||||
<Card
|
|
||||||
v-else-if="nestedState === 'generatingInvoice'"
|
v-else-if="nestedState === 'generatingInvoice'"
|
||||||
class="w-full max-w-80 border-0 bg-card"
|
key="generatingInvoice"
|
||||||
|
class="flex flex-1 flex-col items-center justify-center gap-6"
|
||||||
>
|
>
|
||||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
<BounceDots class="scale-150" />
|
||||||
<Skeleton class="h-10 w-10 rounded-full" />
|
<p class="text-2xl text-muted-foreground">Creating invoice...</p>
|
||||||
<p class="text-muted-foreground">Creating invoice...</p>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Displaying Invoice - Show QR for payment -->
|
<!-- Displaying Invoice (split-screen) -->
|
||||||
<Card
|
<div
|
||||||
v-else-if="nestedState === 'displayingInvoice'"
|
v-else-if="nestedState === 'displayingInvoice'"
|
||||||
class="w-full max-w-[480px] border-0 bg-card"
|
key="displayingInvoice"
|
||||||
|
class="flex flex-1"
|
||||||
>
|
>
|
||||||
<CardHeader class="text-center">
|
<!-- Left panel (52%) — info -->
|
||||||
<CardTitle>Scan to Pay</CardTitle>
|
<div class="flex w-[52%] flex-col items-center justify-center gap-5 px-[4vw]">
|
||||||
<CardDescription v-if="context">
|
<p class="text-[2rem] font-semibold text-warning">Scan to Pay</p>
|
||||||
Pay <Badge variant="secondary">{{ formatSats(context.satsAmount) }} sats</Badge> to
|
<p class="text-[7vh] font-bold text-bitcoin leading-tight">
|
||||||
receive
|
{{ context ? formatSats(context.satsAmount) : 0 }} sats
|
||||||
<Badge class="bg-success"
|
</p>
|
||||||
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</Badge
|
<p class="text-2xl text-muted-foreground">
|
||||||
>
|
to receive
|
||||||
</CardDescription>
|
<Badge class="bg-success text-success-foreground text-2xl">
|
||||||
</CardHeader>
|
{{ atmStore.fiatSymbol }}{{ context ? formatFiat(context.fiatAmount) : '0.00' }}
|
||||||
<CardContent class="space-y-6">
|
</Badge>
|
||||||
<!-- Invoice QR Code -->
|
</p>
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<QRCode v-if="context?.invoice" :value="context.invoice" :size="220" />
|
|
||||||
<p class="mt-4 text-center text-sm text-muted-foreground">
|
|
||||||
Scan with any Lightning wallet
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Payment status -->
|
<!-- Waiting indicator -->
|
||||||
<div class="flex flex-col items-center gap-4 py-4">
|
<div class="flex items-center gap-3 pt-4">
|
||||||
<div class="h-12 w-12 animate-pulse rounded-full bg-bitcoin/20">
|
<BounceDots />
|
||||||
<div class="flex h-full items-center justify-center text-2xl">⏳</div>
|
<p class="text-xl text-muted-foreground">Waiting for payment...</p>
|
||||||
</div>
|
|
||||||
<p class="text-center text-muted-foreground">Waiting for payment...</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Invoice info with copy button (web-ui only) -->
|
<!-- Invoice info with copy button (web-ui only) -->
|
||||||
<div v-if="context?.invoice && !isElectron" class="rounded-lg bg-card p-3">
|
<div v-if="context?.invoice && !isElectron" class="pt-4 text-center">
|
||||||
<p class="mb-2 truncate text-center font-mono text-xs text-muted-foreground">
|
<p class="font-mono-code mb-2 truncate text-xs text-muted-foreground max-w-[300px]">
|
||||||
{{ context.invoice.slice(0, 50) }}...
|
{{ context.invoice.slice(0, 50) }}...
|
||||||
</p>
|
</p>
|
||||||
<div class="flex justify-center">
|
<Button variant="outline" size="sm" @click="copyInvoice">
|
||||||
<Button variant="outline" size="sm" @click="copyInvoice">
|
{{ copyButtonText }}
|
||||||
{{ copyButtonText }}
|
</Button>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dev controls -->
|
<!-- Dev controls -->
|
||||||
|
|
@ -280,90 +290,105 @@ function formatFiat(cents: number): string {
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Dispensing Cash -->
|
<!-- Right panel (48%) — QR code -->
|
||||||
<Card v-else-if="nestedState === 'dispensingCash'" class="w-full max-w-80 border-0 bg-card">
|
<div class="flex w-[48%] flex-col items-center justify-center bg-foreground/5 p-6">
|
||||||
<CardContent class="flex flex-col items-center gap-4 p-8">
|
<div v-if="context?.invoice">
|
||||||
<Skeleton class="h-10 w-10 rounded-full" />
|
<QRCode :value="context.invoice" :size="380" />
|
||||||
<p class="text-muted-foreground">Dispensing cash...</p>
|
|
||||||
<p class="text-sm text-muted-foreground">Please wait</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Cash Ready -->
|
|
||||||
<Card
|
|
||||||
v-else-if="nestedState === 'waitingForCashTaken'"
|
|
||||||
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-success">✅</div>
|
|
||||||
<CardTitle class="text-success">Cash Ready!</CardTitle>
|
|
||||||
<p v-if="context">
|
|
||||||
Please take your
|
|
||||||
<Badge class="bg-success text-lg"
|
|
||||||
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</Badge
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
class="bg-gradient-to-r from-orange-500 to-yellow-400 text-black hover:from-orange-600 hover:to-yellow-500"
|
|
||||||
size="lg"
|
|
||||||
@click="confirmCashTaken"
|
|
||||||
>
|
|
||||||
I've Taken the Cash
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Ask for Receipt -->
|
|
||||||
<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">
|
|
||||||
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>
|
</div>
|
||||||
<Button variant="outline" @click="skipReceipt">No Thanks</Button>
|
<div v-else class="flex flex-col items-center gap-4">
|
||||||
</CardContent>
|
<BounceDots class="scale-150" />
|
||||||
</Card>
|
<p class="text-xl text-muted-foreground">Generating invoice...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Complete -->
|
<!-- Dispensing Cash (centered) -->
|
||||||
<Card v-else-if="nestedState === 'complete'" class="w-full max-w-80 border-0 bg-success/20">
|
<div
|
||||||
<CardContent class="flex flex-col items-center gap-4 p-8 text-center">
|
v-else-if="nestedState === 'dispensingCash'"
|
||||||
<div class="text-6xl">🎉</div>
|
key="dispensingCash"
|
||||||
<CardTitle class="text-success">Transaction Complete!</CardTitle>
|
class="flex flex-1 flex-col items-center justify-center gap-6"
|
||||||
<p class="text-muted-foreground">Thank you!</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Error -->
|
|
||||||
<Card
|
|
||||||
v-else-if="nestedState === 'error' || nestedState === 'dispenseError'"
|
|
||||||
class="w-full max-w-80 border-0 bg-destructive/20"
|
|
||||||
>
|
>
|
||||||
<CardContent class="flex flex-col items-center gap-4 p-8 text-center">
|
<BounceDots class="scale-150" />
|
||||||
<div class="text-6xl">⚠️</div>
|
<p class="text-2xl text-muted-foreground">Dispensing cash...</p>
|
||||||
<CardTitle class="text-destructive">Error</CardTitle>
|
<p class="text-xl text-muted-foreground">Please wait</p>
|
||||||
<p class="text-muted-foreground">{{ context?.error || 'An error occurred' }}</p>
|
</div>
|
||||||
<Button
|
|
||||||
class="bg-gradient-to-r from-orange-500 to-yellow-400 text-black"
|
<!-- Cash Ready (centered, success accent) -->
|
||||||
@click="cancel"
|
<div
|
||||||
|
v-else-if="nestedState === 'waitingForCashTaken'"
|
||||||
|
key="waitingForCashTaken"
|
||||||
|
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
||||||
|
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
||||||
|
>
|
||||||
|
<div class="text-[8vh] text-success">✅</div>
|
||||||
|
<h3 class="text-[3rem] font-bold text-success">Cash Ready!</h3>
|
||||||
|
<p v-if="context" class="text-2xl">
|
||||||
|
Please take your
|
||||||
|
<Badge class="bg-success text-success-foreground text-2xl"
|
||||||
|
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</Badge
|
||||||
>
|
>
|
||||||
Return to Start
|
</p>
|
||||||
</Button>
|
<Button
|
||||||
</CardContent>
|
class="bg-gradient-to-r from-orange-500 to-yellow-400 text-black hover:from-orange-600 hover:to-yellow-500"
|
||||||
</Card>
|
size="kiosk-lg"
|
||||||
|
@click="confirmCashTaken"
|
||||||
|
>
|
||||||
|
I've Taken the Cash
|
||||||
|
</Button>
|
||||||
|
</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) -->
|
||||||
|
<div
|
||||||
|
v-else-if="nestedState === 'complete'"
|
||||||
|
key="complete"
|
||||||
|
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
||||||
|
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
||||||
|
>
|
||||||
|
<div class="text-[8vh]">🎉</div>
|
||||||
|
<h3 class="text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
||||||
|
<p class="text-2xl text-muted-foreground">Thank you!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error (centered, destructive accent) -->
|
||||||
|
<div
|
||||||
|
v-else-if="nestedState === 'error' || nestedState === 'dispenseError'"
|
||||||
|
key="error"
|
||||||
|
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
||||||
|
style="background: color-mix(in srgb, var(--destructive) 8%, var(--background))"
|
||||||
|
>
|
||||||
|
<div class="text-[8vh]">⚠️</div>
|
||||||
|
<h3 class="text-[3rem] font-bold text-destructive">Error</h3>
|
||||||
|
<p class="text-2xl text-muted-foreground">{{ context?.error || 'An error occurred' }}</p>
|
||||||
|
<Button
|
||||||
|
class="bg-gradient-to-r from-orange-500 to-yellow-400 text-black"
|
||||||
|
size="kiosk"
|
||||||
|
@click="cancel"
|
||||||
|
>
|
||||||
|
Return to Start
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Fallback -->
|
<!-- Fallback -->
|
||||||
<Card v-else class="w-full max-w-80 border-0 bg-card">
|
<div v-else key="fallback" class="flex flex-1 items-center justify-center">
|
||||||
<CardContent class="p-8 text-center">
|
<p class="text-xl text-muted-foreground">State: {{ nestedState }}</p>
|
||||||
<p class="text-muted-foreground">State: {{ nestedState }}</p>
|
</div>
|
||||||
</CardContent>
|
</Transition>
|
||||||
</Card>
|
</main>
|
||||||
</main>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue