mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-28 21:02:32 +02:00
351 lines
9.3 KiB
Svelte
351 lines
9.3 KiB
Svelte
<script>
|
|
import analytics from '../utils/analytics.js'
|
|
import config from '../config.js'
|
|
import { onMount } from 'svelte'
|
|
import Overlay from '../components/Overlay.svelte'
|
|
import Icon from '../components/Icon.svelte'
|
|
import boltIcon from '../assets/icon/cil-bolt-filled.svg'
|
|
import tickIcon from '../assets/icon/cil-check-alt.svg'
|
|
import timerIcon from '../assets/icon/cil-av-timer.svg'
|
|
import { fade } from 'svelte/transition'
|
|
import { durationFormat } from '../utils/format.js'
|
|
import { overlay } from '../stores.js'
|
|
|
|
let amount = 5000
|
|
let invoice = null
|
|
let invoicePaid = false
|
|
let invoiceExpired = false
|
|
let invoicePoll
|
|
let pollingEnabled = false
|
|
|
|
let invoiceAmountLabel
|
|
let invoiceExpiryLabel
|
|
let invoiceHexLabel
|
|
const invoiceHexPlaceholder = 'lnbcxxxxxxt8l4pp5umz5kyakc0u8z3w2y568entyyq2gafgc3n5a7khdtk5m9fehkxnqdq6gf5hgen9v4jzqer0deshg6t0dccqzpgxqzjcsp5arlylwgraa2u75g4wh40swvxyvt0cpyrmnl4cha40uj5x2fr0t8q9qy9qsqzh3dtfag0ymaf8dpyxrly9p04jwlgdaxkh6g9ysaxyzz7jtrrkpsxv52mlzl6wgn6l6eur9yrl5q2quh5p8kagmng45gqjz9e2c6uxgqx5ezjr'
|
|
let qrSrc = null
|
|
$: {
|
|
if (invoice && invoice.id && invoice.amount && invoice.BOLT11) {
|
|
invoiceAmountLabel = `${Number.parseInt(invoice.amount) / 1000} sats`
|
|
invoiceHexLabel = invoice.BOLT11
|
|
qrSrc = `https://chart.googleapis.com/chart?chs=500x500&chld=L|2&cht=qr&chl=${invoice.BOLT11}`
|
|
} else {
|
|
stopExpiryTimer()
|
|
invoiceAmountLabel = `${amount || 5000} sats`
|
|
invoiceHexLabel = invoiceHexPlaceholder
|
|
qrSrc = null
|
|
}
|
|
}
|
|
|
|
let expiryTick
|
|
let expiresIn = null // time till expiry in seconds
|
|
$: {
|
|
invoiceExpiryLabel = expiresIn == null ? '' : durationFormat.format(expiresIn * 1000)
|
|
}
|
|
$: {
|
|
if ($overlay === 'lightning') {
|
|
startExpiryTimer()
|
|
stopPollingInvoice()
|
|
pollingEnabled = true
|
|
pollInvoice()
|
|
} else {
|
|
stopExpiryTimer()
|
|
stopPollingInvoice()
|
|
checkResetInvoice()
|
|
}
|
|
}
|
|
function expiryTimer () {
|
|
if (invoice && invoice.expiresAt) expiresIn = Math.round(((invoice.expiresAt * 1000) - Date.now()) / 1000)
|
|
else expiresIn = null
|
|
}
|
|
function stopExpiryTimer () {
|
|
if (expiryTick) clearInterval(expiryTick)
|
|
expiryTick = null
|
|
}
|
|
function startExpiryTimer () {
|
|
if (!expiryTick && $overlay === 'lightning' && invoice && invoice.id) {
|
|
expiryTick = setInterval(expiryTimer, 200)
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
// check for existing invoice in local storage:
|
|
const loadedInvoiceJSON = localStorage.getItem(`lightning-invoice`)
|
|
localStorage.removeItem('lightning-invoice')
|
|
if (loadedInvoiceJSON) {
|
|
try {
|
|
const loadedInvoice = JSON.parse(loadedInvoiceJSON)
|
|
if (loadedInvoice && loadedInvoice.id && loadedInvoice.status && loadedInvoice.status === 'Unpaid' && (loadedInvoice.expiresAt * 1000) > Date.now()) {
|
|
invoice = loadedInvoice
|
|
processInvoice()
|
|
}
|
|
} catch (err) {
|
|
console.log('error loading/parsing invoice')
|
|
}
|
|
}
|
|
})
|
|
|
|
function resetInvoice () {
|
|
invoicePaid = false
|
|
invoiceExpired = false
|
|
invoice = null
|
|
qrSrc = null
|
|
}
|
|
|
|
function checkResetInvoice () {
|
|
if (invoice && invoice.status !== 'Unpaid') resetInvoice()
|
|
}
|
|
|
|
function stopPollingInvoice() {
|
|
pollingEnabled = false
|
|
if (invoicePoll) clearTimeout(invoicePoll)
|
|
invoicePoll = null
|
|
}
|
|
|
|
function processInvoice () {
|
|
if (invoice) {
|
|
startExpiryTimer()
|
|
if (invoice.status === 'Unpaid') {
|
|
invoicePaid = false
|
|
invoiceExpired = false
|
|
localStorage.setItem('lightning-invoice', JSON.stringify(invoice))
|
|
invoicePoll = setTimeout(pollInvoice, 2000)
|
|
} else if (invoice.status === 'Paid') {
|
|
invoicePaid = true
|
|
invoiceExpired = false
|
|
analytics.trackEvent('donations', 'lightning', 'paid', invoice.amount / 1000)
|
|
localStorage.removeItem('lightning-invoice')
|
|
} else if (invoice.status === 'Expired') {
|
|
invoicePaid = false
|
|
invoiceExpired = true
|
|
localStorage.removeItem('lightning-invoice')
|
|
}
|
|
}
|
|
}
|
|
|
|
async function pollInvoice () {
|
|
if (pollingEnabled && invoice && invoice.status === 'Unpaid') {
|
|
const response = await fetch(`${config.dev ? config.devLightningRoot : ''}/api/lightning/invoice/${invoice.id}`, {
|
|
method: 'GET'
|
|
})
|
|
invoice = await response.json()
|
|
processInvoice()
|
|
}
|
|
}
|
|
|
|
async function generateInvoice () {
|
|
if (amount) {
|
|
analytics.trackEvent('donations', 'lightning', 'generate', amount)
|
|
resetInvoice()
|
|
const response = await fetch(`${config.dev ? config.devLightningRoot : ''}/api/lightning/invoice`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ amount })
|
|
})
|
|
invoice = await response.json()
|
|
if (invoice && invoice.amount) analytics.trackEvent('donations', 'lightning', 'generate-success', invoice.amount / 1000)
|
|
processInvoice()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style type="text/scss">
|
|
.info {
|
|
p {
|
|
text-align: justify;
|
|
}
|
|
|
|
.lightning-form {
|
|
.sats-input {
|
|
position: relative;
|
|
display: inline-block;
|
|
.units-label {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 1.4em;
|
|
bottom: 0;
|
|
text-align: right;
|
|
color: var(--dark-c);
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
}
|
|
input {
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
.lightning-button {
|
|
background: var(--bold-a);
|
|
color: white;
|
|
padding: 5px 8px;
|
|
margin: .5em;
|
|
}
|
|
}
|
|
|
|
.invoice-area {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: stretch;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.invoice-info {
|
|
position: relative;
|
|
padding: 10px;
|
|
width: 280px;
|
|
min-width: 200px;
|
|
margin: .5em;
|
|
flex: 1;
|
|
|
|
.field {
|
|
word-break: break-all;
|
|
visibility: hidden;
|
|
|
|
.field-label {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.hex {
|
|
font-family: monospace;
|
|
}
|
|
}
|
|
|
|
.placeholder-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 10px;
|
|
background: var(--dark-d);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
.placeholder-label {
|
|
color: var(--light-a);
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
&.ready {
|
|
.field {
|
|
visibility: visible;
|
|
}
|
|
}
|
|
}
|
|
|
|
.qr-container {
|
|
position: relative;
|
|
flex: 1;
|
|
width: 280px;
|
|
min-width: 200px;
|
|
min-height: 240px;
|
|
margin: .5em;
|
|
|
|
border-radius: 10px;
|
|
background: var(--dark-d);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
.invoice-status {
|
|
font-weight: bold;
|
|
font-size: 1.6em;
|
|
color: white;
|
|
}
|
|
|
|
.invoice-icon {
|
|
font-size: 8em;
|
|
}
|
|
|
|
.qr-image-wrapper {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
.qr-image {
|
|
width: 90%;
|
|
height: 90%;
|
|
object-fit: contain;
|
|
object-position: center;
|
|
}
|
|
}
|
|
|
|
&.paid {
|
|
background: var(--light-good);
|
|
}
|
|
|
|
&.expired {
|
|
background: var(--light-unsure);
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<Overlay name="lightning">
|
|
<section class="info">
|
|
<h2>Donate with Lightning</h2>
|
|
<p>
|
|
Enter the amount you would like to donate, then click the button to generate a payment request:
|
|
</p>
|
|
|
|
<div class="lightning-form">
|
|
<div class="sats-input">
|
|
<input type="number" bind:value={amount}>
|
|
<span class="units-label">sats</span>
|
|
</div>
|
|
<button class="lightning-button" on:click={generateInvoice} >
|
|
<Icon icon={boltIcon} color="white" inline />
|
|
{#if invoice && invoice.id }
|
|
Generate New Request
|
|
{:else}
|
|
Generate Payment Request
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
|
|
|
|
<div class="invoice-area">
|
|
<div class="invoice-info" class:ready={invoice && invoice.id}>
|
|
<p class="field invoice-amount"><span class="field-label">Amount:</span> { invoiceAmountLabel }</p>
|
|
<p class="field invoice-expires"><span class="field-label">Expiry:</span> { invoiceExpiryLabel }</p>
|
|
<p class="field invoice"><span class="field-label">Payment request:</span> <span class="hex">{ invoiceHexLabel }</span></p>
|
|
{#if !invoice || !invoice.id }
|
|
<div class="placeholder-overlay" transition:fade={{ duration: 300 }}>
|
|
<p class="placeholder-label">Payment Request</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="qr-container" class:paid={invoicePaid} class:expired={invoiceExpired}>
|
|
{#if invoicePaid }
|
|
<div class="invoice-icon"><Icon icon={tickIcon} color="white" /></div>
|
|
<h3 class="invoice-status">Received, Thanks!</h3>
|
|
{:else if invoiceExpired}
|
|
<div class="invoice-icon"><Icon icon={timerIcon} color="white" /></div>
|
|
<h3 class="invoice-status">Request Expired</h3>
|
|
{:else}
|
|
<div class="invoice-icon"><Icon icon={boltIcon} color="white" /></div>
|
|
{/if}
|
|
{#if qrSrc && !invoicePaid && !invoiceExpired}
|
|
<div class="qr-image-wrapper">
|
|
<img src={qrSrc} class="qr-image" alt="invoice qr code">
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</Overlay>
|