Add invoice view

This commit is contained in:
ekzyis 2023-11-09 03:02:03 +01:00
parent a21756389e
commit 4aaada7cb7
5 changed files with 152 additions and 1 deletions

View File

@ -18,6 +18,7 @@ func HandleInvoiceStatus(sc context.ServerContext) echo.HandlerFunc {
invoiceId string invoiceId string
invoice db.Invoice invoice db.Invoice
u db.User u db.User
qr string
err error err error
) )
invoiceId = c.Param("id") invoiceId = c.Param("id")
@ -29,8 +30,23 @@ func HandleInvoiceStatus(sc context.ServerContext) echo.HandlerFunc {
if u = c.Get("session").(db.User); invoice.Pubkey != u.Pubkey { if u = c.Get("session").(db.User); invoice.Pubkey != u.Pubkey {
return echo.NewHTTPError(http.StatusUnauthorized) return echo.NewHTTPError(http.StatusUnauthorized)
} }
if qr, err = lib.ToQR(invoice.PaymentRequest); err != nil {
return err
}
invoice.Preimage = "" invoice.Preimage = ""
return c.JSON(http.StatusOK, invoice) data := map[string]any{
"Id": invoice.Id,
"Msats": invoice.Msats,
"MsatsReceived": invoice.MsatsReceived,
"Hash": invoice.Hash,
"PaymentRequest": invoice.PaymentRequest,
"CreatedAt": invoice.CreatedAt,
"ExpiresAt": invoice.ExpiresAt,
"ConfirmedAt": invoice.ConfirmedAt,
"HeldSince": invoice.HeldSince,
"Qr": qr,
}
return c.JSON(http.StatusOK, data)
} }
} }

View File

@ -0,0 +1,108 @@
<template>
<div class="flex flex-col">
<router-link v-if="success" :to="callbackUrl" class="label success font-mono">
<div>Paid</div>
<small>Redirecting in {{ redirectTimeout }} ...</small>
</router-link>
<div class="font-mono my-3">
Payment Required
</div>
<div v-if="error" class="label error font-mono">
<div>Error</div>
<small>{{ error }}</small>
</div>
<div v-if="invoice">
<figure class="flex flex-col m-auto">
<a class="m-auto" :href="'lightning:' + invoice.PaymentRequest">
<img :src="'data:image/png;base64,' + invoice.Qr" />
</a>
<figcaption class="flex flex-row my-3 font-mono text-xs">
<span class="w-[80%] text-ellipsis overflow-hidden">{{ invoice.PaymentRequest }}</span>
<button @click.prevent="copy">{{ label }}</button>
</figcaption>
</figure>
<div class="grid text-muted text-xs">
<span class="mx-3 my-1">payment hash</span><span class="text-ellipsis overflow-hidden font-mono me-3 my-1">
{{ invoice.Hash }}
</span>
<span class="mx-3 my-1">created at</span><span class="text-ellipsis overflow-hidden font-mono me-3 my-1">
{{ invoice.CreatedAt }}
</span>
<span class="mx-3 my-1">expires at</span><span class="text-ellipsis overflow-hidden font-mono me-3 my-1">
{{ invoice.ExpiresAt }}
</span>
<span class="mx-3 my-1">msats</span><span class="text-ellipsis overflow-hidden font-mono me-3 my-1">
{{ invoice.Msats }}
</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()
// TODO validate callback url
const callbackUrl = route.params.callbackUrl ?? '/'
const INVOICE_POLL = 2000
const poll = async () => {
const url = window.origin + '/api/invoice/' + route.params.id
const res = await fetch(url)
const body = await res.json()
if (body.ConfirmedAt) {
success.value = true
clearInterval(interval)
setInterval(() => {
if (--redirectTimeout.value === 0) {
router.push(callbackUrl)
}
}, 1000)
}
}
const interval = setInterval(poll, INVOICE_POLL)
const invoice = ref(null)
const redirectTimeout = ref(3)
const success = ref(null)
const error = ref(null)
const label = ref('copy')
let copyTimeout = null
const copy = () => {
navigator.clipboard?.writeText(invoice.value.PaymentRequest)
label.value = 'copied'
if (copyTimeout) clearTimeout(copyTimeout)
copyTimeout = setTimeout(() => { label.value = 'copy' }, 1500)
}
await (async () => {
const url = window.origin + '/api/invoice/' + route.params.id
const res = await fetch(url)
const body = await res.json()
invoice.value = body
})()
</script>
<style scoped>
img {
width: 256px;
height: auto;
}
figcaption {
margin: 0.75em auto;
width: 256px;
}
.label {
margin: 1em auto;
}
div.grid {
grid-template-columns: auto auto;
}
</style>

View File

@ -59,4 +59,8 @@ a.selected {
.error:hover { .error:hover {
background-color: #ff7386; background-color: #ff7386;
}
.text-muted {
opacity: 0.5
} }

View File

@ -9,6 +9,7 @@ import MarketsView from '@/views/MarketsView'
import LoginView from '@/views/LoginView' import LoginView from '@/views/LoginView'
import UserView from '@/views/UserView' import UserView from '@/views/UserView'
import MarketView from '@/views/MarketView' import MarketView from '@/views/MarketView'
import InvoiceView from '@/views/InvoiceView'
const routes = [ const routes = [
{ {
@ -22,6 +23,9 @@ const routes = [
}, },
{ {
path: '/market/:id', component: MarketView path: '/market/:id', component: MarketView
},
{
path: '/invoice/:id', component: InvoiceView
} }
] ]
const router = VueRouter.createRouter({ const router = VueRouter.createRouter({

View File

@ -0,0 +1,19 @@
<template>
<!-- eslint-disable -->
<div class="my-3">
<pre>
_ _ ___ ____
| || | / _ \___ \
| || |_| | | |__) |
|__ _| |_| / __/
|_| \___/_____|</pre>
</div>
<!-- eslint-enable -->
<Suspense>
<Invoice />
</Suspense>
</template>
<script setup>
import Invoice from '@/components/Invoice'
</script>