mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-20 13:34:47 +01:00
Add voucher design (#704)
* allow for custom print of vouchers * lnbits defaul voucher * reduce png size * add template for custom design * rename lnbits voucher * send voucher amount to UI * add some tweaks for default voucher * added bonus in readme * blacked * make default string as constant
This commit is contained in:
parent
ff5b1189b5
commit
7fbea79e62
10 changed files with 224 additions and 7 deletions
|
@ -26,6 +26,8 @@ LNBits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes t
|
|||
- on details you can print the vouchers\
|
||||

|
||||
- every printed LNURLw QR code is unique, it can only be used once
|
||||
3. Bonus: you can use an LNbits themed voucher, or use a custom one. There's a _template.svg_ file in `static/images` folder if you want to create your own.\
|
||||

|
||||
|
||||
#### Advanced
|
||||
|
||||
|
|
|
@ -26,9 +26,10 @@ async def create_withdraw_link(
|
|||
k1,
|
||||
open_time,
|
||||
usescsv,
|
||||
webhook_url
|
||||
webhook_url,
|
||||
custom_url
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
link_id,
|
||||
|
@ -44,6 +45,7 @@ async def create_withdraw_link(
|
|||
int(datetime.now().timestamp()) + data.wait_time,
|
||||
usescsv,
|
||||
data.webhook_url,
|
||||
data.custom_url,
|
||||
),
|
||||
)
|
||||
link = await get_withdraw_link(link_id, 0)
|
||||
|
|
|
@ -115,3 +115,10 @@ async def m004_webhook_url(db):
|
|||
Adds webhook_url
|
||||
"""
|
||||
await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_url TEXT;")
|
||||
|
||||
|
||||
async def m005_add_custom_print_design(db):
|
||||
"""
|
||||
Adds custom print design
|
||||
"""
|
||||
await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN custom_url TEXT;")
|
||||
|
|
|
@ -16,6 +16,7 @@ class CreateWithdrawData(BaseModel):
|
|||
wait_time: int = Query(..., ge=1)
|
||||
is_unique: bool
|
||||
webhook_url: str = Query(None)
|
||||
custom_url: str = Query(None)
|
||||
|
||||
|
||||
class WithdrawLink(BaseModel):
|
||||
|
@ -34,6 +35,7 @@ class WithdrawLink(BaseModel):
|
|||
usescsv: str = Query(None)
|
||||
number: int = Query(0)
|
||||
webhook_url: str = Query(None)
|
||||
custom_url: str = Query(None)
|
||||
|
||||
@property
|
||||
def is_spent(self) -> bool:
|
||||
|
|
|
@ -20,9 +20,12 @@ var mapWithdrawLink = function (obj) {
|
|||
obj.uses_left = obj.uses - obj.used
|
||||
obj.print_url = [locationPath, 'print/', obj.id].join('')
|
||||
obj.withdraw_url = [locationPath, obj.id].join('')
|
||||
obj._data.use_custom = Boolean(obj.custom_url)
|
||||
return obj
|
||||
}
|
||||
|
||||
const CUSTOM_URL = '/static/images/default_voucher.png'
|
||||
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
|
@ -59,13 +62,15 @@ new Vue({
|
|||
secondMultiplier: 'seconds',
|
||||
secondMultiplierOptions: ['seconds', 'minutes', 'hours'],
|
||||
data: {
|
||||
is_unique: false
|
||||
is_unique: false,
|
||||
use_custom: false
|
||||
}
|
||||
},
|
||||
simpleformDialog: {
|
||||
show: false,
|
||||
data: {
|
||||
is_unique: true,
|
||||
use_custom: true,
|
||||
title: 'Vouchers',
|
||||
min_withdrawable: 0,
|
||||
wait_time: 1
|
||||
|
@ -106,12 +111,14 @@ new Vue({
|
|||
},
|
||||
closeFormDialog: function () {
|
||||
this.formDialog.data = {
|
||||
is_unique: false
|
||||
is_unique: false,
|
||||
use_custom: false
|
||||
}
|
||||
},
|
||||
simplecloseFormDialog: function () {
|
||||
this.simpleformDialog.data = {
|
||||
is_unique: false
|
||||
is_unique: false,
|
||||
use_custom: false
|
||||
}
|
||||
},
|
||||
openQrCodeDialog: function (linkId) {
|
||||
|
@ -133,6 +140,9 @@ new Vue({
|
|||
id: this.formDialog.data.wallet
|
||||
})
|
||||
var data = _.omit(this.formDialog.data, 'wallet')
|
||||
if (data.use_custom && !data?.custom_url) {
|
||||
data.custom_url = CUSTOM_URL
|
||||
}
|
||||
|
||||
data.wait_time =
|
||||
data.wait_time *
|
||||
|
@ -141,7 +151,6 @@ new Vue({
|
|||
minutes: 60,
|
||||
hours: 3600
|
||||
}[this.formDialog.secondMultiplier]
|
||||
|
||||
if (data.id) {
|
||||
this.updateWithdrawLink(wallet, data)
|
||||
} else {
|
||||
|
@ -159,6 +168,10 @@ new Vue({
|
|||
data.title = 'vouchers'
|
||||
data.is_unique = true
|
||||
|
||||
if (data.use_custom && !data?.custom_url) {
|
||||
data.custom_url = '/static/images/default_voucher.png'
|
||||
}
|
||||
|
||||
if (data.id) {
|
||||
this.updateWithdrawLink(wallet, data)
|
||||
} else {
|
||||
|
@ -181,7 +194,8 @@ new Vue({
|
|||
'uses',
|
||||
'wait_time',
|
||||
'is_unique',
|
||||
'webhook_url'
|
||||
'webhook_url',
|
||||
'custom_url'
|
||||
)
|
||||
)
|
||||
.then(function (response) {
|
||||
|
|
|
@ -217,6 +217,32 @@
|
|||
label="Webhook URL (optional)"
|
||||
hint="A URL to be called whenever this link gets used."
|
||||
></q-input>
|
||||
<q-list>
|
||||
<q-item tag="label" class="rounded-borders">
|
||||
<q-item-section avatar>
|
||||
<q-checkbox
|
||||
v-model="formDialog.data.use_custom"
|
||||
color="primary"
|
||||
></q-checkbox>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>Use a custom voucher design </q-item-label>
|
||||
<q-item-label caption
|
||||
>You can use an LNbits voucher design or a custom
|
||||
one</q-item-label
|
||||
>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-input
|
||||
v-if="formDialog.data.use_custom"
|
||||
filled
|
||||
dense
|
||||
v-model="formDialog.data.custom_url"
|
||||
type="text"
|
||||
label="Custom design .png (optional)"
|
||||
hint="Enter a URL if you want to use a custom design or leave blank for showing only the QR"
|
||||
></q-input>
|
||||
<q-list>
|
||||
<q-item tag="label" class="rounded-borders">
|
||||
<q-item-section avatar>
|
||||
|
@ -303,6 +329,32 @@
|
|||
:default="1"
|
||||
label="Number of vouchers"
|
||||
></q-input>
|
||||
<q-list>
|
||||
<q-item tag="label" class="rounded-borders">
|
||||
<q-item-section avatar>
|
||||
<q-checkbox
|
||||
v-model="simpleformDialog.data.use_custom"
|
||||
color="primary"
|
||||
></q-checkbox>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>Use a custom voucher design </q-item-label>
|
||||
<q-item-label caption
|
||||
>You can use an LNbits voucher design or a custom
|
||||
one</q-item-label
|
||||
>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-input
|
||||
v-if="simpleformDialog.data.use_custom"
|
||||
filled
|
||||
dense
|
||||
v-model="simpleformDialog.data.custom_url"
|
||||
type="text"
|
||||
label="Custom design .png (optional)"
|
||||
hint="Enter a URL if you want to use a custom design or leave blank for showing only the QR"
|
||||
></q-input>
|
||||
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
{% extends "print.html" %} {% block page %}
|
||||
|
||||
<div class="row">
|
||||
<div class="" id="vue">
|
||||
{% for page in link %}
|
||||
<page size="A4" id="pdfprint">
|
||||
{% for one in page %}
|
||||
<div class="wrapper">
|
||||
<img src="{{custom_url}}" alt="..." />
|
||||
<span>{{ amt }} sats</span>
|
||||
<div class="lnurlw">
|
||||
<qrcode :value="'{{one}}'" :options="{width: 95, margin: 1}"></qrcode>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</page>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %} {% block styles %}
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400');
|
||||
body {
|
||||
background: rgb(204, 204, 204);
|
||||
}
|
||||
page {
|
||||
background: white;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 0.5cm;
|
||||
box-shadow: 0 0 0.5cm rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
page[size='A4'] {
|
||||
width: 21cm;
|
||||
height: 29.7cm;
|
||||
}
|
||||
.wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
padding: 1rem;
|
||||
width: fit-content;
|
||||
}
|
||||
.wrapper span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
font-family: 'Inter';
|
||||
font-size: 0.75rem;
|
||||
color: #fff;
|
||||
top: calc(3.2mm + 1rem);
|
||||
right: calc(4mm + 1rem);
|
||||
}
|
||||
.wrapper img {
|
||||
display: block;
|
||||
width: 187mm;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.wrapper .lnurlw {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: calc(7.3mm + 1rem);
|
||||
left: calc(7.5mm + 1rem);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
@media print {
|
||||
body,
|
||||
page {
|
||||
margin: 0px !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.q-page,
|
||||
.wrapper {
|
||||
padding: 0px !important;
|
||||
}
|
||||
.wrapper span {
|
||||
top: 3mm;
|
||||
right: 4mm;
|
||||
}
|
||||
.wrapper .lnurlw {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 7.3mm;
|
||||
left: 7.5mm;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %} {% block scripts %}
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
data: function () {
|
||||
return {
|
||||
theurl: location.protocol + '//' + location.host,
|
||||
printDialog: {
|
||||
show: true,
|
||||
data: null
|
||||
},
|
||||
links: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.links = '{{ link | tojson }}'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -99,6 +99,18 @@ async def print_qr(request: Request, link_id):
|
|||
page_link = list(chunks(links, 2))
|
||||
linked = list(chunks(page_link, 5))
|
||||
|
||||
if link.custom_url:
|
||||
return withdraw_renderer().TemplateResponse(
|
||||
"withdraw/print_qr_custom.html",
|
||||
{
|
||||
"request": request,
|
||||
"link": page_link,
|
||||
"unique": True,
|
||||
"custom_url": link.custom_url,
|
||||
"amt": link.max_withdrawable,
|
||||
},
|
||||
)
|
||||
|
||||
return withdraw_renderer().TemplateResponse(
|
||||
"withdraw/print_qr.html", {"request": request, "link": linked, "unique": True}
|
||||
)
|
||||
|
|
BIN
lnbits/static/images/default_voucher.png
Normal file
BIN
lnbits/static/images/default_voucher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 177 KiB |
16
lnbits/static/images/voucher_template.svg
Normal file
16
lnbits/static/images/voucher_template.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<svg width="2000" height="1422" viewBox="0 0 2000 1422" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2_2)">
|
||||
<rect width="2000" height="1422" fill="#F0F0F0"/>
|
||||
<line x1="-0.707107" y1="710.293" x2="710.293" y2="-0.707106" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
|
||||
<line x1="0.707107" y1="710.293" x2="711.707" y2="1421.29" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
|
||||
<line x1="710" y1="-0.00140647" x2="712" y2="1422" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
|
||||
<line y1="710" x2="2000" y2="710" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
|
||||
<line x1="709.707" y1="-0.707107" x2="1420.71" y2="710.293" stroke="#696969" stroke-opacity="0.5" stroke-width="2"/>
|
||||
<rect x="26" y="216.454" width="275" height="275" transform="rotate(-45 26 216.454)" fill="white" fill-opacity="0.5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2_2">
|
||||
<rect width="2000" height="1422" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 985 B |
Loading…
Add table
Reference in a new issue