196 lines
9.0 KiB
Plaintext
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">← 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 →</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 →</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') %>
|