171 lines
4.4 KiB
Svelte
171 lines
4.4 KiB
Svelte
<script>
|
||
import { Styles,
|
||
Container,
|
||
Row,
|
||
Col,
|
||
Card, CardBody, CardHeader, CardTitle,
|
||
Button,
|
||
Tooltip,
|
||
Icon,
|
||
InputGroup,
|
||
Input,
|
||
Nav, NavItem, NavLink,
|
||
Modal, ModalHeader, ModalBody, ModalFooter,
|
||
} from 'sveltestrap';
|
||
|
||
import InputMask from './InputMask.svelte';
|
||
import QR from './QR.svelte';
|
||
import * as pf from './payflow.js';
|
||
import CTC from './CTC.svelte';
|
||
import Tipped from './Tipped.svelte';
|
||
|
||
export let lnurl = undefined;
|
||
let isOpen;
|
||
let res1;
|
||
let res2;
|
||
let memo;
|
||
let err;
|
||
let bolt;
|
||
|
||
let confirmAmount;
|
||
let amount;
|
||
|
||
let minSendSat=0, maxSendSat=0;
|
||
|
||
$: minSendSat = res1 && (Math.round((res1.minSendable+999)/1000))
|
||
$: maxSendSat = res1 && (Math.round(res1.maxSendable/1000))
|
||
|
||
$: if (lnurl && !isOpen) {
|
||
res1 = res2 = err = memo = confirmAmount = undefined;
|
||
isOpen = true;
|
||
fetchInvoice()
|
||
}
|
||
|
||
let stage;
|
||
$: if (!res1) {
|
||
stage = err || "Connecting to server…";
|
||
} else if (confirmAmount) {
|
||
stage = "Amount confirmation";
|
||
} else if (!res2) {
|
||
stage = err || "Requesting invoice";
|
||
} else {
|
||
stage = err || "Invoice ready.";
|
||
}
|
||
|
||
async function fetchInvoice() {
|
||
try {
|
||
let url = pf.decodeLnurl(lnurl)
|
||
//await new Promise(r => setTimeout(r, 5000));
|
||
res1 = await pf.payStep1(url)
|
||
if (res1.status == "ERROR") {
|
||
throw new Error(res1.reason)
|
||
}
|
||
|
||
if (!isOpen) { return }
|
||
if (res1.minSendable != res1.maxSendable) {
|
||
amount = Math.round((res1.minSendable+999)/1000).toString();
|
||
await new Promise((resolve, reject)=>{confirmAmount = resolve})
|
||
confirmAmount = undefined;
|
||
} else {
|
||
amount = (res1.minSendable/1000).toString();
|
||
}
|
||
res2 = await pf.payStep2(url,res1,amount*1000)
|
||
if (!isOpen) { return }
|
||
if (res2.status == "ERROR") {
|
||
throw new Error(res2.reason)
|
||
}
|
||
} catch(e) {
|
||
err = e.toString()
|
||
}
|
||
}
|
||
|
||
function mdText() {
|
||
for (let md of res1.decodedMetadata) {
|
||
if (md[0]=="text/plain") {
|
||
return md[1]
|
||
}
|
||
}
|
||
}
|
||
const canShare = !!navigator.share;
|
||
|
||
</script>
|
||
|
||
<Modal {isOpen} size='lg'>
|
||
<ModalHeader>BOLT11 invoice: {stage}</ModalHeader>
|
||
<ModalBody>
|
||
{#if confirmAmount}
|
||
<div class="row">
|
||
<label class="form-label mb-3">
|
||
Amount: {minSendSat} – {maxSendSat} satoshi
|
||
<div class="input-group">
|
||
{#key minSendSat}
|
||
<InputMask bind:value={amount}
|
||
autofocus
|
||
unmask
|
||
imask={{
|
||
mask: Number, scale:0,
|
||
min: minSendSat , max: maxSendSat,
|
||
normalizeZeros: true}}
|
||
inputmode={"numeric"}
|
||
class="form-control"/>
|
||
{/key}
|
||
<button class="btn btn-outline-secondary"
|
||
type="button"
|
||
on:click={()=> amount=minSendSat.toString()}>Min
|
||
</button>
|
||
<button class="btn btn-outline-secondary"
|
||
type="button"
|
||
on:click={()=> amount=maxSendSat.toString()}>Max
|
||
</button>
|
||
<button class="btn btn-secondary"
|
||
type="button"
|
||
disabled={!(amount && amount <= maxSendSat
|
||
&& amount >= minSendSat)}
|
||
on:click={()=> confirmAmount()}>OK
|
||
</button>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
{/if}
|
||
{#if (res2&&res2.pr)}
|
||
<div class="row">
|
||
<CTC let:id let:action force text={res2.pr}>
|
||
<div class="form-text user-select-all mb-2 mt-2"
|
||
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
||
on:click={action} id={id}>{res2.pr.toUpperCase()}
|
||
</div>
|
||
</CTC>
|
||
<div class="d-flex justify-content-center">
|
||
<a href="lightning:{res2.pr}">
|
||
<QR value="{res2.pr}" size="230"/>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
{/if}
|
||
</ModalBody>
|
||
<ModalFooter>
|
||
<div class="btn-group flex-wrap">
|
||
{#if res2 && res2.pr}
|
||
<CTC let:id let:action text={res2.pr}>
|
||
<button type="button" class="btn btn-outline-secondary"
|
||
id={id} on:click={action}>Copy</button>
|
||
</CTC>
|
||
{#if canShare}
|
||
<button type="button" class="btn btn-outline-secondary"
|
||
on:click={()=>{ navigator.share({text:res2.pr})}}>Share
|
||
</button>
|
||
{/if}
|
||
<Tipped let:id>
|
||
<a slot="thing" role="button" class="btn btn-outline-secondary"
|
||
href="lightning:{res2.pr}" {id}>Pay</a>
|
||
<div slot="tip">open invoice in your wallet</div>
|
||
</Tipped>
|
||
{/if}
|
||
<button
|
||
class="btn btn-outline-primary"
|
||
type="button" on:click="{()=>{isOpen=false; lnurl = null}}">Cancel</button>
|
||
</div>
|
||
|
||
</ModalFooter>
|
||
</Modal>
|