pingql/apps/web/src/views/checkout.ejs

196 lines
9.0 KiB
Plaintext

<%~ include('./partials/head', { title: 'Upgrade' }) %>
<%~ include('./partials/nav', { nav: 'settings' }) %>
<%
const plan = it.account.plan;
const coins = it.coins || [];
const invoice = it.invoice;
const payApi = it.payApi || '';
%>
<style>
/* Show months section only when pro is selected */
#plan-pro:checked ~ #months-section { display: block; }
/* Highlight selected plan */
#plan-pro:checked ~ .plan-labels label[for="plan-pro"] { border-color: rgb(59 130 246 / 0.5); }
#plan-lifetime:checked ~ .plan-labels label[for="plan-lifetime"] { border-color: rgb(234 179 8 / 0.5); }
/* Highlight selected coin */
.coin-radio:checked + label { border-color: rgb(59 130 246); }
</style>
<main class="max-w-2xl mx-auto px-6 py-8">
<div class="mb-6">
<a href="/dashboard/settings" class="text-sm text-gray-500 hover:text-gray-300 transition-colors">&larr; Back to settings</a>
<h2 class="text-lg font-semibold text-gray-200 mt-2">Upgrade Plan</h2>
</div>
<% if (!invoice) { %>
<!-- ─── Step 1: Plan & coin selection (form, works without JS) ─── -->
<form action="/dashboard/checkout" method="POST" class="space-y-6">
<!-- Hidden radios for plan (CSS uses :checked) -->
<input type="radio" name="plan" value="pro" id="plan-pro" class="hidden peer/pro" checked>
<input type="radio" name="plan" value="lifetime" id="plan-lifetime" class="hidden peer/lifetime">
<div class="plan-labels grid grid-cols-2 gap-4">
<label for="plan-pro"
class="cursor-pointer text-left bg-surface border-2 border-border-subtle hover:border-blue-500/40 rounded-xl p-5 transition-colors">
<div class="text-xs text-gray-500 uppercase tracking-wider font-mono mb-1">Pro</div>
<div class="text-2xl font-bold text-gray-100">$12<span class="text-sm font-normal text-gray-500">/mo</span></div>
<div class="text-xs text-gray-500 mt-2">500 monitors, 2s intervals</div>
</label>
<label for="plan-lifetime"
class="cursor-pointer text-left bg-surface border-2 border-border-subtle hover:border-yellow-500/40 rounded-xl p-5 transition-colors">
<div class="text-xs text-yellow-500/70 uppercase tracking-wider font-mono mb-1">Lifetime</div>
<div class="text-2xl font-bold text-gray-100">$140</div>
<div class="text-xs text-gray-500 mt-2">One-time, forever</div>
</label>
</div>
<!-- Months (visible only when pro selected, via CSS) -->
<div id="months-section" class="hidden">
<label class="block text-sm text-gray-400 mb-2">How many months?</label>
<select name="months" class="input-base px-4 py-2.5 text-gray-100">
<% for (let i = 1; i <= 12; i++) { %>
<option value="<%= i %>"><%= i %> month<%= i > 1 ? 's' : '' %> — $<%= i * 12 %></option>
<% } %>
</select>
</div>
<!-- Coin selection -->
<div>
<label class="block text-sm text-gray-400 mb-2">Pay with</label>
<div class="grid grid-cols-3 gap-2">
<% coins.forEach(function(c, i) { %>
<input type="radio" name="coin" value="<%= c.id %>" id="coin-<%= c.id %>" class="hidden coin-radio" <%= i === 0 ? 'checked' : '' %>>
<label for="coin-<%= c.id %>"
class="cursor-pointer flex items-center gap-2 bg-surface border-2 border-border-subtle hover:border-border-strong rounded-lg px-3 py-2.5 transition-colors text-left">
<span class="text-sm font-medium text-gray-300"><%= c.ticker %></span>
<span class="text-xs text-gray-600"><%= c.label %></span>
</label>
<% }) %>
</div>
</div>
<button type="submit"
class="w-full btn-primary py-3">
Continue to Payment
</button>
</form>
<% } else { %>
<!-- ─── Step 2: Payment display (SSR, auto-refreshes without JS) ─── -->
<%
const inv = invoice;
const isPending = inv.status === 'pending' || inv.status === 'underpaid' || inv.status === 'confirming';
const received = parseFloat(inv.amount_received || '0');
const total = parseFloat(inv.amount_crypto);
const remaining = Math.max(0, total - received);
const expiresAt = new Date(inv.expires_at);
const now = new Date();
const remainingSec = Math.max(0, Math.floor((expiresAt.getTime() - now.getTime()) / 1000));
const mins = Math.floor(remainingSec / 60);
const secs = remainingSec % 60;
%>
<% if (isPending) { %>
<!-- Auto-refresh every 5s for status updates (no-JS polling) -->
<meta http-equiv="refresh" content="5">
<% } %>
<div class="card-static p-6 text-center space-y-5">
<% if (inv.status === 'confirming') { %>
<!-- Loading spinner instead of QR -->
<div class="w-48 h-48 mx-auto rounded-lg bg-surface flex items-center justify-center">
<svg class="w-10 h-10 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>
<% } else if (inv.status !== 'paid') { %>
<!-- QR -->
<div>
<img src="<%= inv.qr_url %>" alt="QR Code" class="w-48 h-48 mx-auto rounded-lg bg-white p-2" id="pay-qr">
</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>
<% 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>
</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>
</div>
<% } else if (inv.status === 'confirming') { %>
<div class="flex items-center justify-center gap-2 text-sm">
<span class="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></span>
<span class="text-blue-400">Transaction received, waiting for 1 confirmation...</span>
</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">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-2">
<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>
<p class="text-lg font-semibold text-white">Payment confirmed</p>
<p class="text-sm text-gray-400">Your plan has been activated.</p>
<a href="/dashboard/settings" class="btn-primary inline-block px-5 py-2 text-sm mt-2">Back to settings</a>
<% 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>
<a href="/dashboard/checkout" class="mt-3 text-sm text-blue-400 hover:text-blue-300">Try again</a>
<% } %>
</div>
<% } %>
</main>
<%~ include('./partials/foot') %>