feat(machine): confirm-abandon warning, retry on error, hourglass animation

- Add confirmAbandon state UI: warns user cash can't be returned,
  offers "Show QR Code Again" or "Abandon (lose cash)"
- Error state shows "Try Again" button when bills are inserted
- Replace Skeleton loading placeholders with hourglass emoji
- Add animate-hourglass: 45-degree step rotation (Tailwind v4 @utility)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-02-25 19:15:51 -05:00
commit 165ad59704
2 changed files with 69 additions and 2 deletions

View file

@ -491,3 +491,38 @@
@utility animate-float {
animation: float 3s ease-in-out infinite;
}
/* Hourglass 45-degree step rotation */
@keyframes hourglass {
0% {
transform: rotate(0deg);
}
12.5% {
transform: rotate(45deg);
}
25% {
transform: rotate(90deg);
}
37.5% {
transform: rotate(135deg);
}
50% {
transform: rotate(180deg);
}
62.5% {
transform: rotate(225deg);
}
75% {
transform: rotate(270deg);
}
87.5% {
transform: rotate(315deg);
}
100% {
transform: rotate(360deg);
}
}
@utility animate-hourglass {
animation: hourglass 4s steps(8) infinite;
}

View file

@ -290,7 +290,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
class="w-full max-w-80 border-0 bg-white/5"
>
<CardContent class="flex flex-col items-center gap-4 p-8">
<Skeleton class="h-10 w-10 rounded-full" />
<span class="animate-hourglass text-5xl">⏳</span>
<p class="text-muted-foreground">Preparing payment code...</p>
</CardContent>
</Card>
@ -485,7 +485,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
class="w-full max-w-80 border-0 bg-white/5"
>
<CardContent class="flex flex-col items-center gap-4 p-8">
<Skeleton class="h-10 w-10 rounded-full" />
<span class="animate-hourglass text-5xl">⏳</span>
<p class="text-muted-foreground">Sending receipt...</p>
</CardContent>
</Card>
@ -506,12 +506,44 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
</CardContent>
</Card>
<!-- Confirm Abandon Warning -->
<Card
v-else-if="nestedState === 'confirmAbandon'"
class="w-full max-w-80 border-0 bg-red-900/30"
>
<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>
<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"
@click="atmStore.send({ type: 'RETRY' })"
>
Show QR Code Again
</Button>
<Button variant="ghost" class="w-full text-red-400" @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">
<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>
<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"
@click="atmStore.send({ type: 'RETRY' })"
>
Try Again
</Button>
<Button
class="bg-gradient-to-r from-orange-500 to-yellow-400 text-black"
@click="cancel"