Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Customization Options for Payment Methods in Payment Request Modal #213

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions dev/vite/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,21 @@ <h2>Request Payment Screen</h2>
<bc-send-payment
id="send-payment"
invoice="lnbc10n1pj4g7ugpp5h0lld9qzh06ngn8ur756rva0ntfdfg34h298337rljjyztxy8rfqhp50kncf9zk35xg4lxewt4974ry6mudygsztsz8qn3ar8pn3mtpe50scqzzsxqyz5vqsp5lwlnu7xjs7hj7vltx7rhzsjf60l5mwckuw93qngjrz7sdhdkw7gs9qyyssqzy543rxhkdtncjrmuzlz4lrh2eauldawa9np5yysh7dhqhv8kaahydurha2cfelr7t6wt33xse7fdykupnjv36na6x6crrhy53lyg6cqf2jhjj"
payment-methods="all"
></bc-send-payment>

<h2>Request Payment Screen Internal (no QR)</h2>
<bc-send-payment
id="send-payment"
invoice="lnbc10n1pj4g7ugpp5h0lld9qzh06ngn8ur756rva0ntfdfg34h298337rljjyztxy8rfqhp50kncf9zk35xg4lxewt4974ry6mudygsztsz8qn3ar8pn3mtpe50scqzzsxqyz5vqsp5lwlnu7xjs7hj7vltx7rhzsjf60l5mwckuw93qngjrz7sdhdkw7gs9qyyssqzy543rxhkdtncjrmuzlz4lrh2eauldawa9np5yysh7dhqhv8kaahydurha2cfelr7t6wt33xse7fdykupnjv36na6x6crrhy53lyg6cqf2jhjj"
payment-methods="internal"
></bc-send-payment>

<h2>Request Payment Screen External (no connect)</h2>
<bc-send-payment
id="send-payment"
invoice="lnbc10n1pj4g7ugpp5h0lld9qzh06ngn8ur756rva0ntfdfg34h298337rljjyztxy8rfqhp50kncf9zk35xg4lxewt4974ry6mudygsztsz8qn3ar8pn3mtpe50scqzzsxqyz5vqsp5lwlnu7xjs7hj7vltx7rhzsjf60l5mwckuw93qngjrz7sdhdkw7gs9qyyssqzy543rxhkdtncjrmuzlz4lrh2eauldawa9np5yysh7dhqhv8kaahydurha2cfelr7t6wt33xse7fdykupnjv36na6x6crrhy53lyg6cqf2jhjj"
payment-methods="external"
></bc-send-payment>

<h2>Start screen</h2>
Expand Down
299 changes: 197 additions & 102 deletions src/components/pages/bc-send-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) {
@state()
_showQR = false;

@state()
_qr = null as QRCode | null;

@property({
type: String,
})
Expand All @@ -41,6 +44,12 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) {
})
paid?: boolean;

@property({
type: String,
attribute: 'payment-methods',
})
paymentMethods: 'all' | 'internal' | 'external' = 'all';

protected override updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);

Expand All @@ -51,11 +60,174 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) {
}
}

private renderHeading(decodedInvoice: Invoice) {
return html`
<h2 class="text-2xl mb-6 ${classes['text-neutral-secondary']}">
<span
class="font-bold font-mono text-4xl align-bottom ${classes[
'text-brand-mixed'
]}"
>${decodedInvoice.satoshi.toLocaleString(undefined, {
useGrouping: true,
})}</span
>&nbsp;sats
</h2>
`;
}

private renderPaidState() {
return html`
<div
class="flex flex-col justify-center items-center ${classes[
'text-brand-mixed'
]}"
>
<p class="font-bold">Paid!</p>
${successAnimation}
</div>
`;
}

private renderPayingState() {
return html`
<div class="flex flex-col justify-center items-center">
<p class="${classes['text-neutral-secondary']} mb-5">Paying...</p>
${waitingIcon(`w-48 h-48 ${classes['text-brand-mixed']}`)}
</div>
`;
}

private renderPaymentConfirmation() {
return html`
<bci-button variant="primary" @click=${this._payInvoice}>
<span class="-ml-0.5">${bcIcon}</span>
Confirm Payment
</bci-button>
${disconnectSection(this._connectorName)}
`;
}

private renderWaitingForPayment() {
return html`
<div class="flex justify-center items-center">
${waitingIcon(`w-7 h-7 ${classes['text-brand-mixed']}`)}
<p class="${classes['text-neutral-secondary']}">Waiting for payment</p>
</div>
`;
}

private renderConnectWalletMobile() {
let internalMethods = null;
let externalMethods = null;
let qrSection = null;

if (this.paymentMethods === 'all' || this.paymentMethods === 'internal') {
internalMethods = html`
<bci-button block @click=${this._onClickConnectWallet}>
<span class="-ml-0.5">${bcIcon}</span>Connect Wallet
</bci-button>
`;
}

if (this.paymentMethods === 'all' || this.paymentMethods === 'external') {
externalMethods = html`
<bci-button block @click=${this._copyAndDisplayInvoice}>
${qrIcon} Copy & Display Invoice
</bci-button>
`;

if (this._showQR) {
qrSection = this.renderQR();
}
}

return html`
<div class="mt-8 w-full flex flex-col gap-4">
<a href="lightning:${this.invoice}">
<bci-button variant="primary" block>
${walletIcon} Open in a Bitcoin Wallet
</bci-button>
</a>
${internalMethods} ${externalMethods}
</div>
${qrSection}
`;
}

private renderConnectWalletDesktop() {
let internalMethods = null;
if (this.paymentMethods === 'all' || this.paymentMethods === 'internal') {
internalMethods = html`
<div class="mt-8">
<bci-button variant="primary" @click=${this._onClickConnectWallet}>
<span class="-ml-0.5">${bcIcon}</span>
Connect Wallet to Pay
</bci-button>
</div>
`;
}

let separator = null;
if (this.paymentMethods === 'all') {
separator = html` <div class="w-full py-4">${hr('or')}</div> `;
}

let externalMethods = null;
if (this.paymentMethods === 'all' || this.paymentMethods === 'external') {
externalMethods = html`
<div
class="flex flex-col items-center ${this.paymentMethods === 'external'
? 'mt-8'
: ''}"
>
<p class="font-medium ${classes['text-neutral-secondary']}">
Scan to Pay
</p>
${this.renderQR()}
</div>
`;
}

return html` ${internalMethods} ${separator} ${externalMethods} `;
}

private renderQR() {
if (!this._showQR || !this.invoice || !this._qr) {
return null;
}

return html`
<!-- add margin only on dark mode because on dark mode the qr has a white border -->
<a href="lightning:${this.invoice}" class="dark:mt-2">
<img src=${this._qr.createDataURL(4)} class="rounded-lg"></img>
</a>
<a
@click=${this._copyInvoice}
class="
flex gap-1
mt-4
${classes['text-brand-mixed']} ${
classes.interactive
} font-semibold text-xs"
>
${this._hasCopiedInvoice ? copiedIcon : copyIcon}
${this._hasCopiedInvoice ? 'Copied!' : 'Copy Invoice'}
</a>
`;
}

override render() {
if (!this.invoice) {
return null;
}

const errorCorrectionLevel = 'L';
const qr = qrcode(0, errorCorrectionLevel);
qr.addData(this.invoice);
qr.make();

this._qr = qr;

let decodedInvoice: Invoice;
try {
decodedInvoice = new Invoice({pr: this.invoice});
Expand All @@ -64,111 +236,34 @@ export class SendPayment extends withTwind()(BitcoinConnectElement) {
store.getState().setError((error as Error).message);
return null;
}
const errorCorrectionLevel = 'L';
const qr = qrcode(0, errorCorrectionLevel);
qr.addData(this.invoice);
qr.make();

const isMobileView = window.innerWidth < 600;
if (!isMobileView) {
this._showQR = true;
}

return html` <div
class="flex flex-col justify-center items-center font-sans w-full"
>
<h2 class="text-2xl mb-6 ${classes['text-neutral-secondary']}">
<span
class="font-bold font-mono text-4xl align-bottom ${classes[
'text-brand-mixed'
]}"
>${decodedInvoice.satoshi.toLocaleString(undefined, {
useGrouping: true,
})}</span
>&nbsp;sats
</h2>
${this._connected || this.paid
? this.paid
? html`<div
class="flex flex-col justify-center items-center ${classes[
'text-brand-mixed'
]}"
>
<p class="font-bold">Paid!</p>
${successAnimation}
</div>`
: this._isPaying
? html`<div class="flex flex-col justify-center items-center">
<p class="${classes['text-neutral-secondary']} mb-5">Paying...</p>
${waitingIcon(`w-48 h-48 ${classes['text-brand-mixed']}`)}
</div>`
: html`<bci-button variant="primary" @click=${this._payInvoice}>
<span class="-ml-0.5">${bcIcon}</span>
Confirm Payment
</bci-button>
${disconnectSection(this._connectorName)} `
: html`
<div class="flex justify-center items-center">
${waitingIcon(`w-7 h-7 ${classes['text-brand-mixed']}`)}
<p class="${classes['text-neutral-secondary']}">
Waiting for payment
</p>
</div>

${!isMobileView
? html`<div class="mt-8">
<bci-button variant="primary" @click=${
this._onClickConnectWallet
}>
<span class="-ml-0.5">${bcIcon}</span>
Connect Wallet to Pay
</bci-button>
</div>
<div class="w-full py-4">${hr('or')}</div>

<p class="font-medium ${classes['text-neutral-secondary']}">
Scan to Pay
</p>
</div>`
: html`
<div class="mt-8 w-full flex flex-col gap-4">
<a href="lightning:${this.invoice}">
<bci-button variant="primary" block>
${walletIcon} Open in a Bitcoin Wallet
</bci-button>
</a>
<bci-button block @click=${this._onClickConnectWallet}>
<span class="-ml-0.5">${bcIcon}</span>Connect Wallet
</bci-button>
${this._showQR
? null
: html`<bci-button
block
@click=${this._copyAndDisplayInvoice}
>
${qrIcon}Copy & Display Invoice
</bci-button>`}
</div>
`}
${!isMobileView || this._showQR
? html`
<!-- add margin only on dark mode because on dark mode the qr has a white border -->
<a href="lightning:${this.invoice}" class="dark:mt-2">
<img src=${qr.createDataURL(4)} class="rounded-lg"></img>
</a>
<a
@click=${this._copyInvoice}
class="
flex gap-1
mt-4
${classes['text-brand-mixed']} ${
classes.interactive
} font-semibold text-xs"
>
${this._hasCopiedInvoice ? copiedIcon : copyIcon}
${this._hasCopiedInvoice ? 'Copied!' : 'Copy Invoice'}
</a>
`
: null}
`}
</div>`;
let paymentStateElement;

if (this.paid) {
paymentStateElement = this.renderPaidState();
} else if (this._isPaying) {
paymentStateElement = this.renderPayingState();
} else if (this._connected) {
paymentStateElement = this.renderPaymentConfirmation();
} else {
paymentStateElement = html`
${this.renderWaitingForPayment()}
${isMobileView
? this.renderConnectWalletMobile()
: this.renderConnectWalletDesktop()}
`;
}

return html`
<div class="flex flex-col justify-center items-center font-sans w-full">
${this.renderHeading(decodedInvoice)} ${paymentStateElement}
</div>
`;
}

private _onClickConnectWallet() {
Expand Down