test: improve checkout UX

This commit is contained in:
nate 2026-03-25 22:42:26 +04:00
parent b80a8ee240
commit 430e7f1dbe
1 changed files with 96 additions and 73 deletions

View File

@ -128,7 +128,7 @@
</form>
<% } else { %>
<!-- ─── Step 2: Payment display (SSR, auto-refreshes without JS) ─── -->
<!-- ─── Step 2: Payment display ─── -->
<%
const inv = invoice;
const isPending = inv.status === 'pending' || inv.status === 'underpaid' || inv.status === 'confirming';
@ -140,110 +140,133 @@
const remainingSec = Math.max(0, Math.floor((expiresAt.getTime() - now.getTime()) / 1000));
const mins = Math.floor(remainingSec / 60);
const secs = remainingSec % 60;
const pct = Math.min(100, Math.round((received / total) * 100));
const invPlanNames = { pro: 'Pro', pro2x: 'Pro 2x', pro4x: 'Pro 4x', lifetime: 'Lifetime' };
const invPlanLabel = invPlanNames[inv.plan] || inv.plan;
%>
<% if (isPending) { %>
<!-- Auto-refresh every 5s for status updates (no-JS polling) -->
<meta http-equiv="refresh" content="5">
<% } %>
<div class="card-static px-6 py-10 text-center space-y-5">
<div class="card-static overflow-hidden max-w-md mx-auto">
<% if (inv.status === 'confirming') { %>
<!-- intentionally empty — confirming state is handled below -->
<% } else if (inv.status !== 'paid') { %>
<!-- QR -->
<div>
<a href="<%= inv.pay_uri %>" class="inline-block"><img src="<%= inv.qr_url %>" alt="QR Code" class="w-48 h-48 rounded-lg bg-white p-2" id="pay-qr"></a>
<!-- Header bar -->
<div class="px-5 py-3 border-b divider flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-gray-200"><%= invPlanLabel %></span>
<% if (inv.months) { %><span class="text-xs text-gray-500"><%= inv.months %>mo</span><% } %>
</div>
<div class="text-sm font-medium text-gray-300">$<%= Number(inv.amount_usd).toFixed(2) %></div>
</div>
<% } %>
<% if (inv.status === 'pending' || inv.status === 'underpaid') { %>
<!-- Amount -->
<div>
<div class="text-2xl font-bold font-mono text-gray-100"><%= received > 0 && remaining > 0 ? remaining.toFixed(8) : inv.amount_crypto %></div>
<div class="text-sm text-gray-500"><%= inv.coin_label %> (<%= inv.coin_ticker %>)</div>
<div class="text-xs text-gray-600 mt-1">$<%= Number(inv.amount_usd).toFixed(2) %> USD</div>
</div>
<div class="px-5 pt-6 pb-5">
<% if (received > 0 && remaining > 0) { %>
<!-- Received / Remaining -->
<div class="flex justify-center gap-6 text-sm">
<div>
<span class="text-gray-500">Received:</span>
<span class="text-green-400 font-mono"><%= received.toFixed(8) %></span>
</div>
<div>
<span class="text-gray-500">Remaining:</span>
<span class="text-yellow-400 font-mono"><%= remaining.toFixed(8) %></span>
<!-- QR + amount side by side on wider, stacked on narrow -->
<div class="flex flex-col items-center gap-5">
<!-- QR -->
<a href="<%= inv.pay_uri %>" class="inline-block">
<img src="<%= inv.qr_url %>" alt="QR Code" class="w-44 h-44 rounded-xl bg-white p-2" id="pay-qr">
</a>
<!-- Amount -->
<div class="text-center">
<div class="text-xs text-gray-500 uppercase tracking-wider mb-1">Send exactly</div>
<div class="flex items-center justify-center gap-2">
<span class="text-xl font-bold font-mono text-white"><%= received > 0 && remaining > 0 ? remaining.toFixed(8) : inv.amount_crypto %></span>
<span class="text-sm font-medium text-gray-400"><%= inv.coin_ticker %></span>
</div>
</div>
<% if (received > 0 && remaining > 0) { %>
<!-- Progress bar -->
<div class="w-full">
<div class="w-full h-1.5 rounded-full bg-gray-800 overflow-hidden">
<div class="h-full rounded-full bg-green-500 transition-all" style="width: <%= pct %>%"></div>
</div>
<div class="flex justify-between mt-1.5 text-xs">
<span class="text-green-400 font-mono"><%= received.toFixed(8) %> received</span>
<span class="text-gray-500 font-mono"><%= remaining.toFixed(8) %> left</span>
</div>
</div>
<% } %>
<!-- Address -->
<div class="w-full">
<div class="flex items-center justify-between mb-1.5">
<span class="text-xs text-gray-500">Address</span>
<button onclick="copyAddr()" id="copy-btn" class="text-xs text-gray-500 hover:text-gray-300 transition-colors">Copy</button>
</div>
<code class="block bg-surface-solid border border-border-subtle rounded-lg px-3 py-2.5 text-blue-400 text-[11px] font-mono select-all break-all leading-relaxed" id="pay-address"><%= inv.address %></code>
</div>
</div>
</div>
<% } %>
<!-- Address -->
<div>
<label class="block text-xs text-gray-500 mb-1">Send to</label>
<code class="block bg-surface-solid border border-border-subtle rounded-lg px-3 py-2.5 text-blue-400 text-xs font-mono select-all break-all" id="pay-address"><%= inv.address %></code>
</div>
<% } %>
<!-- Status -->
<% if (inv.status === 'pending') { %>
<div class="flex items-center justify-center gap-2 text-sm">
<span class="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></span>
<span class="text-gray-400">Waiting for payment...</span>
</div>
<div class="text-xs text-gray-600 mt-2">
Expires in <span class="font-mono"><%= mins %>:<%= String(secs).padStart(2, '0') %></span>
</div>
<% } else if (inv.status === 'underpaid') { %>
<div class="flex items-center justify-center gap-2 text-sm">
<span class="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></span>
<span class="text-yellow-400">Underpaid - please send the remaining amount</span>
</div>
<div class="text-xs text-gray-600 mt-2">
Expires in <span class="font-mono"><%= mins %>:<%= String(secs).padStart(2, '0') %></span>
<!-- Footer: status + timer -->
<div class="px-5 py-3 border-t divider flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full <%= inv.status === 'underpaid' ? 'bg-yellow-500' : 'bg-blue-500' %> animate-pulse"></span>
<span class="text-xs <%= inv.status === 'underpaid' ? 'text-yellow-400' : 'text-gray-400' %>"><%= inv.status === 'underpaid' ? 'Underpaid, send the rest' : 'Waiting for payment' %></span>
</div>
<span class="text-xs text-gray-500 font-mono"><%= mins %>:<%= String(secs).padStart(2, '0') %></span>
</div>
<% } else if (inv.status === 'confirming') { %>
<div class="w-16 h-16 mx-auto rounded-full bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-4">
<svg class="w-8 h-8 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
<div class="px-5 py-12 text-center">
<div class="w-14 h-14 mx-auto rounded-full bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-5">
<svg class="w-7 h-7 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
</div>
<p class="text-base font-semibold text-white mb-1">Payment received</p>
<p class="text-sm text-gray-400">Waiting for network confirmation</p>
<% if (inv.address) { %>
<a href="https://transaction.st/address/<%= inv.address %>" target="_blank" rel="noopener" class="inline-block text-xs text-gray-500 hover:text-gray-300 transition-colors mt-4">View on explorer &rarr;</a>
<% } %>
</div>
<p class="text-lg font-semibold text-white">Payment received</p>
<p class="text-sm text-gray-400">Waiting for 1 network confirmation...</p>
<% if (inv.address) { %>
<a href="https://transaction.st/address/<%= inv.address %>" target="_blank" rel="noopener" class="block text-xs text-gray-500 hover:text-gray-400 transition-colors mt-1">View on block explorer &rarr;</a>
<% } %>
<% } else if (inv.status === 'paid') { %>
<div class="w-16 h-16 mx-auto rounded-full bg-green-500/10 border border-green-500/20 flex items-center justify-center mb-4">
<svg class="w-8 h-8 text-green-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
<div class="px-5 py-12 text-center">
<div class="w-14 h-14 mx-auto rounded-full bg-green-500/10 border border-green-500/20 flex items-center justify-center mb-5">
<svg class="w-7 h-7 text-green-400" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
</div>
<p class="text-base font-semibold text-white mb-1">Payment confirmed</p>
<p class="text-sm text-gray-400 mb-5">Your <%= invPlanLabel %> plan is now active.</p>
<div class="flex items-center justify-center gap-3">
<a href="/dashboard/settings" class="btn-primary px-5 py-2 text-sm">Back to settings</a>
<a href="/dashboard/checkout/<%= inv.id %>/receipt" target="_blank" class="btn-secondary px-5 py-2 text-sm">Receipt</a>
</div>
<% if (inv.address) { %>
<a href="https://transaction.st/address/<%= inv.address %>" target="_blank" rel="noopener" class="inline-block text-xs text-gray-500 hover:text-gray-300 transition-colors mt-4">View on explorer &rarr;</a>
<% } %>
</div>
<p class="text-lg font-semibold text-white">Payment confirmed</p>
<p class="text-sm text-gray-400">Your plan has been activated.</p>
<div class="flex items-center justify-center gap-3 mt-2">
<a href="/dashboard/settings" class="btn-primary inline-block px-5 py-2 text-sm">Back to settings</a>
<a href="/dashboard/checkout/<%= inv.id %>/receipt" target="_blank" class="btn-secondary inline-block px-5 py-2 text-sm">View Receipt</a>
</div>
<% if (inv.address) { %>
<a href="https://transaction.st/address/<%= inv.address %>" target="_blank" rel="noopener" class="block text-xs text-gray-500 hover:text-gray-400 transition-colors mt-3">View on block explorer &rarr;</a>
<% } %>
<% } else if (inv.status === 'expired') { %>
<div class="flex items-center justify-center gap-2 text-red-400">
<span class="font-medium">Payment expired</span>
<div class="px-5 py-12 text-center">
<div class="w-14 h-14 mx-auto rounded-full bg-red-500/10 border border-red-500/20 flex items-center justify-center mb-5">
<svg class="w-7 h-7 text-red-400" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
</div>
<p class="text-base font-semibold text-white mb-1">Invoice expired</p>
<p class="text-sm text-gray-400 mb-5">This payment window has closed.</p>
<a href="/dashboard/checkout" class="btn-primary inline-block px-5 py-2 text-sm">Try again</a>
</div>
<a href="/dashboard/checkout" class="mt-3 text-sm text-blue-400 hover:text-blue-300">Try again</a>
<% } %>
</div>
<p class="text-xs text-gray-600 text-center mt-4">You can leave this page. Payments are detected and confirmed automatically.</p>
<p class="text-xs text-gray-600 text-center mt-4">You can close this page. Payments are detected automatically.</p>
<% } %>
</main>
<script>
// Copy address
function copyAddr() {
const addr = document.getElementById('pay-address');
const btn = document.getElementById('copy-btn');
if (addr) navigator.clipboard.writeText(addr.textContent);
if (btn) { btn.textContent = 'Copied'; setTimeout(() => btn.textContent = 'Copy', 1500); }
}
const multipliers = { pro: 1, pro2x: 2, pro4x: 4 };
const planValue = document.getElementById('plan-value');
let selectedTier = 'pro';