From ded5b2707232ac1407996fe59c059e3c4aadc37d Mon Sep 17 00:00:00 2001 From: ocknamo Date: Sun, 7 Apr 2024 01:15:31 +0900 Subject: [PATCH 1/4] feat: invoice dialog fix: update id style: update invoice-dialog fix: set class feat: add loader fix: update loader position --- index.html | 13 ++++++++--- lib/pos.js | 2 +- main.js | 32 +++++++++++++++++++-------- styles.css | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 15 deletions(-) diff --git a/index.html b/index.html index 81c8637..0b50d82 100644 --- a/index.html +++ b/index.html @@ -184,7 +184,7 @@ POS
- +
@@ -211,8 +211,15 @@

-
-
+ +
+
+
+
+ +
+
diff --git a/lib/pos.js b/lib/pos.js index 5c8c4be..6329a00 100644 --- a/lib/pos.js +++ b/lib/pos.js @@ -36,7 +36,7 @@ export class Pos { #satsInput = window.document.getElementById('sats'); #messageArea = window.document.getElementById('pos-message'); #otherUnits = ['btc', 'jpy', 'usd', 'eur'] - #posPayButton = window.document.getElementById('pos-pay-button'); + #posPayButton = window.document.getElementById('show-invoice-dialog'); // 連打による連続的なリクエストを制限するためのフラグ #isRequesting = false; diff --git a/main.js b/main.js index 0e6947a..175cfca 100644 --- a/main.js +++ b/main.js @@ -799,16 +799,10 @@ import { Pos } from './lib/pos.js'; const pos = new Pos(); pos.initialize(); -// 支払いインボイスのQRコード表示の制御 -const posPayButton = document.getElementById('pos-pay-button'); -posPayButton.addEventListener('click', () => { - pos.generateInvoice(); -}); - /** - * ライトニングアドレスの変更ダイアログの制御 + * ライトニングアドレスのダイアログの制御 */ -const showButton = document.getElementById('show-lightning-address-dialog'); +const showAddressButton = document.getElementById('show-lightning-address-dialog'); const lnDialog = document.getElementById('update-lightning-address-dialog'); const lnDialogSubmitButton = document.getElementById('lightning-address-submit-button'); const lnDialogCloseButton = document.getElementById('lightning-address-close-button'); @@ -816,7 +810,7 @@ const lnDialogClearButton = document.getElementById('lightning-address-clear-but const lnAddressForm = document.getElementById('lightning-address-form'); // ダイアログを開く -showButton.addEventListener('click', () => { +showAddressButton.addEventListener('click', () => { lnDialog.showModal(); }); @@ -844,6 +838,26 @@ lnDialogSubmitButton.addEventListener('click', (event) => { lnDialog.close(); }); +/** + * 支払いインボイスのQRコードダイアログの制御 + */ +const showInvoiceButton = document.getElementById('show-invoice-dialog'); +const invoiceDialog = document.getElementById('lightning-invoice-dialog'); +const invoiceDialogCloseButton = document.getElementById('lightning-invoice-close-button'); + +// ダイアログを開く +showInvoiceButton.addEventListener('click', () => { + invoiceDialog.showModal(); + pos.generateInvoice(); +}); + +// ダイアログを閉じる +invoiceDialogCloseButton.addEventListener('click', (event) => { + event.preventDefault(); // フォームを送信しない + invoiceDialog.close(); +}); + + // index.htmlで使用する関数をグローバルスコープで使用できるようにwindowに追加する window.satsRate = { calculateValues, diff --git a/styles.css b/styles.css index 91ff13b..be55423 100644 --- a/styles.css +++ b/styles.css @@ -716,7 +716,7 @@ input:checked+.slider:before { color: #D6D6D6; } -#pos-pay-button { +#show-invoice-dialog { height: 50px; line-height: 50px; padding: 0 20px; @@ -742,4 +742,64 @@ input:checked+.slider:before { #lightning-address-form button { background-color: var(--main-bg-color); -} \ No newline at end of file +} + +.pos .invoice-dialog { + min-height: 420px; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + padding: 1.2em; +} + +.pos .lightning-pos-qr-box { + width: 340px; + height: 340px; + margin-bottom: 1em; +} + +.pos .loader-position { + position: relative; + top: 170px; + z-index: -1; +} + +.pos .loader { + width: 4px; + color: var(--base-color); + aspect-ratio: 1; + border-radius: 50%; + box-shadow: + 19px -19px 0 0px, 38px -19px 0 0px, 57px -19px 0 0px, + 19px 0 0 5px, 38px 0 0 5px, 57px 0 0 5px, + 19px 19px 0 0px, 38px 19px 0 0px, 57px 19px 0 0px; + transform: translateX(-38px); + animation: l26 2s infinite linear; + } + @keyframes l26 { + 12.5% {box-shadow: + 19px -19px 0 0px, 38px -19px 0 0px, 57px -19px 0 5px, + 19px 0 0 5px, 38px 0 0 0px, 57px 0 0 5px, + 19px 19px 0 0px, 38px 19px 0 0px, 57px 19px 0 0px} + 25% {box-shadow: + 19px -19px 0 5px, 38px -19px 0 0px, 57px -19px 0 5px, + 19px 0 0 0px, 38px 0 0 0px, 57px 0 0 0px, + 19px 19px 0 0px, 38px 19px 0 5px, 57px 19px 0 0px} + 50% {box-shadow: + 19px -19px 0 5px, 38px -19px 0 5px, 57px -19px 0 0px, + 19px 0 0 0px, 38px 0 0 0px, 57px 0 0 0px, + 19px 19px 0 0px, 38px 19px 0 0px, 57px 19px 0 5px} + 62.5% {box-shadow: + 19px -19px 0 0px, 38px -19px 0 0px, 57px -19px 0 0px, + 19px 0 0 5px, 38px 0 0 0px, 57px 0 0 0px, + 19px 19px 0 0px, 38px 19px 0 5px, 57px 19px 0 5px} + 75% {box-shadow: + 19px -19px 0 0px, 38px -19px 0 5px, 57px -19px 0 0px, + 19px 0 0 0px, 38px 0 0 0px, 57px 0 0 5px, + 19px 19px 0 0px, 38px 19px 0 0px, 57px 19px 0 5px} + 87.5% {box-shadow: + 19px -19px 0 0px, 38px -19px 0 5px, 57px -19px 0 0px, + 19px 0 0 0px, 38px 0 0 5px, 57px 0 0 0px, + 19px 19px 0 5px, 38px 19px 0 0px, 57px 19px 0 0px} + } From 9700b1b4feae124d1bcbe670d9a20a677b6c8986 Mon Sep 17 00:00:00 2001 From: ocknamo Date: Tue, 9 Apr 2024 23:41:49 +0900 Subject: [PATCH 2/4] feat: amount table in dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit style: update amount table styles chore: dialog表示になったためinputを監視する必要がなくなった feat: show current amount in invoice dialog fix: add invoice callback API error handring fix: correct amount validation fix: correct error handring style: update sats amount overflow style: update error message color fix: clear message when close dialog --- index.html | 34 +++++++++++++++++++++-- lib/lightning-address.js | 40 +++++++++++++++++---------- lib/pos.js | 40 +++++++++++++-------------- main.js | 3 ++- styles.css | 58 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 37 deletions(-) diff --git a/index.html b/index.html index 0b50d82..d73eb38 100644 --- a/index.html +++ b/index.html @@ -215,9 +215,39 @@
-
+
+
+
+
+
sats
+
+
+
+
+
+
BTC
+
+
+
+
+
+
JPY
+
+
+
+
+
+
USD
+
+
+
+
+
+
EUR
+
+
+ formmethod="dialog">閉じる
diff --git a/lib/lightning-address.js b/lib/lightning-address.js index 91b834e..e3746c7 100644 --- a/lib/lightning-address.js +++ b/lib/lightning-address.js @@ -30,15 +30,6 @@ export class LightningAddress { return res.json(); }) - return this.data; - } - - /** - * 入力値の数量でインボイスを生成する - * @param {number} amount ミリサトシ - * @returns void - */ - async getInvoice(amount) { if(!this.data || this.data.tag !== 'payRequest' || !this.data.callback) { throw new Error('支払い可能なライトニングアドレスではないようです') } @@ -52,18 +43,39 @@ export class LightningAddress { throw new Error(`Invalid lnurlp response: ${JSON.stringify(this.data)}`) } } + } + + /** + * 入力値の数量でインボイスを生成する + * @param {number} amount ミリサトシ + * @returns void + */ + async getInvoice(amount) { + if(!this.data || this.data.tag !== 'payRequest' || !this.data.callback) { + throw new Error('支払い可能なライトニングアドレスではないようです') + } // 数量のバリデーション - if( amount > this.data.maxSendable && amount < (this.data.minSendable ?? 0)) { - return; + if( amount > this.data.maxSendable || amount < (this.data.minSendable ?? 0)) { + throw new Error(`Invalid amount. Make sure the amount is within the range. 数量は${this.data.minSendable/1000}sats~${this.data.maxSendable/1000}satsの間である必要があります`) } const callbackUrl = new URL(this.data.callback) callbackUrl.searchParams.append('amount', amount); - return fetch(callbackUrl).then(async (res) => { - return res.json(); - }); + const invoice = await fetch(callbackUrl).then(async (res) => res.json()); + + if(invoice.status === 'ERROR') { + // 仕様上this.data.reasonが存在するはずなのでエラー理由をスローする + // https://github.com/lnurl/luds/blob/luds/06.md + if(invoice.reason) { + throw new Error(invoice.reason) + } else { + throw new Error(`Invalid callback response: ${JSON.stringify(invoice)}`) + } + } + + return invoice; } toString() { diff --git a/lib/pos.js b/lib/pos.js index 6329a00..8b142e8 100644 --- a/lib/pos.js +++ b/lib/pos.js @@ -3,7 +3,6 @@ import { LightningAddress } from './lightning-address.js'; /** * POS機能 * ライトニングアドレスを保管して入力された金額のインボイスのQRコードの表示を行う。 - * またインプットの値とインボイスの値が乖離することを避けるため入力値を監視し変更された場合は直ちに破棄する。 */ export class Pos { localStorageKey = 'POS:LnAddress'; @@ -35,8 +34,8 @@ export class Pos { #qrWrapper = window.document.getElementById('lightning-pos-qr-box'); #satsInput = window.document.getElementById('sats'); #messageArea = window.document.getElementById('pos-message'); - #otherUnits = ['btc', 'jpy', 'usd', 'eur'] #posPayButton = window.document.getElementById('show-invoice-dialog'); + #units = ['sats', 'btc', 'jpy', 'usd', 'eur'] // 連打による連続的なリクエストを制限するためのフラグ #isRequesting = false; @@ -46,9 +45,6 @@ export class Pos { this.#lnAddress = new LightningAddress(window.localStorage.getItem(this.localStorageKey) ?? ''); this.#updatePosPayButton(); - - // 値が変更されたら請求書QRコードは削除しなければいけないため入力値を監視する - this.#handleInputChange(); } setLnAddress(form) { @@ -73,8 +69,15 @@ export class Pos { return satsAmount || null; } + async showInvoice() { + this.#clearQrCode(); + this.#showCurrentAmounts(); + + await this.#generateInvoice(); + } + // 支払いボタン押下時 - async generateInvoice() { + async #generateInvoice() { if (this.#isRequesting) { return; } @@ -90,8 +93,6 @@ export class Pos { return; } - this.#clearQrCode(); - try { await this.#lnAddress.fetchAddressData(); const invoice = await this.#lnAddress.getInvoice(amount * 1000); @@ -110,6 +111,13 @@ export class Pos { this.#clearQrCode(); } + #showCurrentAmounts() { + for (const unit of this.#units) { + const currentValue = window.document.getElementById(unit).value; + window.document.getElementById(`current-${unit}`).innerText = currentValue; + } + } + #clearQrCode() { this.#qrWrapper.innerHTML = ''; } @@ -119,18 +127,6 @@ export class Pos { qrCode.append(this.#qrWrapper); } - // どの通貨が変更された場合もQRコードをクリアする - #handleInputChange() { - let inputs = this.#otherUnits.map(u => window.document.getElementById(u)) - inputs = [...inputs, this.#satsInput]; - - inputs.forEach(input => { - input.addEventListener('input', () => { - this.#clearQrCode(); - }); - }); - } - // エラーメッセージなどを表示する #setMessage(message) { this.#messageArea.innerHTML = ''; @@ -140,6 +136,10 @@ export class Pos { this.#messageArea.appendChild(p) } + clearMessage() { + this.#messageArea.innerHTML = ''; + } + // 請求書を表示のボタンの状態を切り替える #updatePosPayButton() { this.#posPayButton.disabled = !this.#lnAddress || !this.#lnAddress.hasValidAddress(); diff --git a/main.js b/main.js index 175cfca..78a0d62 100644 --- a/main.js +++ b/main.js @@ -848,13 +848,14 @@ const invoiceDialogCloseButton = document.getElementById('lightning-invoice-clos // ダイアログを開く showInvoiceButton.addEventListener('click', () => { invoiceDialog.showModal(); - pos.generateInvoice(); + pos.showInvoice(); }); // ダイアログを閉じる invoiceDialogCloseButton.addEventListener('click', (event) => { event.preventDefault(); // フォームを送信しない invoiceDialog.close(); + pos.clearMessage(); }); diff --git a/styles.css b/styles.css index be55423..ab49235 100644 --- a/styles.css +++ b/styles.css @@ -759,6 +759,64 @@ input:checked+.slider:before { margin-bottom: 1em; } +.pos .error-message { + color: var(--base-color); + max-width: 100%; + overflow-wrap: break-word; +} + +.pos .amounts-grid { + display: grid; + grid-template-columns: 100px 40px 100px 40px; + gap: 10px; + margin: 12px; + overflow-wrap: break-word; +} + +.pos .amount-text { + color: var(--main-text-color); +} + +.pos .unit-text { + color: var(--subject-text-color); +} + +.amounts-grid .sats { + grid-column: 1 / 5; + display: flex; + flex-direction: row; + justify-content: center; + align-items: baseline; + margin-bottom: 1rem; +} + +.amounts-grid .sats-amount { + font-size: 2rem; + max-width: 100%; +} + +.amounts-grid .sats-unit { + font-size: 1.4rem; + margin-left: 0.2em; +} + +.amounts-grid .amount { + grid-column: auto; + display: flex; + align-items: center; + justify-content: flex-end; +} + +.amount .value { + max-width: 100%; +} + +.amounts-grid .unit { + grid-column: auto; + display: flex; + align-items: center; +} + .pos .loader-position { position: relative; top: 170px; From c3bf5268af0182ee0e1b8f43a5b5c8bfc985927b Mon Sep 17 00:00:00 2001 From: ocknamo Date: Fri, 12 Apr 2024 00:53:40 +0900 Subject: [PATCH 3/4] fix: modify error handring and message --- lib/lightning-address.js | 10 +++++++++- lib/pos.js | 14 +++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/lightning-address.js b/lib/lightning-address.js index e3746c7..3585080 100644 --- a/lib/lightning-address.js +++ b/lib/lightning-address.js @@ -27,6 +27,9 @@ export class LightningAddress { // Document: https://github.com/lnurl/luds/blob/luds/16.md this.data = await fetch(`https://${this.#domain}/.well-known/lnurlp/${this.#userName}`).then(async (res) => { + if (!res.ok) { + throw new Error('Error: cannot fetch Address Data.'); + } return res.json(); }) @@ -63,7 +66,12 @@ export class LightningAddress { const callbackUrl = new URL(this.data.callback) callbackUrl.searchParams.append('amount', amount); - const invoice = await fetch(callbackUrl).then(async (res) => res.json()); + const invoice = await fetch(callbackUrl).then(async (res) => { + if (!res.ok) { + throw new Error('Error: cannot get invoice.'); + } + return res.json() + }); if(invoice.status === 'ERROR') { // 仕様上this.data.reasonが存在するはずなのでエラー理由をスローする diff --git a/lib/pos.js b/lib/pos.js index 8b142e8..8711ce8 100644 --- a/lib/pos.js +++ b/lib/pos.js @@ -98,7 +98,19 @@ export class Pos { const invoice = await this.#lnAddress.getInvoice(amount * 1000); this.#showQrCode(invoice.pr); } catch (error) { - this.#setMessage(error.message ?? JSON.stringify(error)); + let message; + + const jaMessage = 'インボイスを作成できません。ライトニングアドレスが間違っていないか確認してください。'; + + if (error.message) { + message = error.message; + } else if (typeof error === 'object') { + message = JSON.stringify(error); + } else { + message = `Error: ${jaMessage}`; + } + + this.#setMessage(`${jaMessage} error: ${message}`); } finally { this.#isRequesting = false; } From 818265f95f9fa7f7bdffc9d891884cb7ceddae59 Mon Sep 17 00:00:00 2001 From: Lokuyow <94349922+Lokuyow@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:08:58 +0900 Subject: [PATCH 4/4] fix: UI color --- styles.css | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/styles.css b/styles.css index ab49235..32f6a8c 100644 --- a/styles.css +++ b/styles.css @@ -585,6 +585,7 @@ input:checked+.slider:before { @media (prefers-color-scheme: light) { :root { --two-color: var(--accent-color); + --two-color-r: var(--base-color); --input-text-color: #000000; --main-text-color: #3D3D3D; --svg-color: #525252; @@ -598,6 +599,7 @@ input:checked+.slider:before { :root[data-theme="dark"] { --two-color: var(--base-color); + --two-color-r: var(--accent-color); --input-text-color: #FFFFFF; --main-text-color: #E6E6E6; --svg-color: #F0F0F0; @@ -613,6 +615,7 @@ input:checked+.slider:before { @media (prefers-color-scheme: dark) { :root { --two-color: var(--base-color); + --two-color-r: var(--accent-color); --input-text-color: #FFFFFF; --main-text-color: #E6E6E6; --svg-color: #F0F0F0; @@ -626,6 +629,7 @@ input:checked+.slider:before { :root[data-theme="light"] { --two-color: var(--accent-color); + --two-color-r: var(--base-color); --input-text-color: #000000; --main-text-color: #3D3D3D; --svg-color: #525252; @@ -703,7 +707,7 @@ input:checked+.slider:before { .pos dialog { color: var(--main-text-color); - background-color: var(--unit-bg-color); + background-color: var(--main-bg-color); border-radius: 1em; border: 1px solid #333333; padding: 0; @@ -734,14 +738,14 @@ input:checked+.slider:before { .lightning-address-input { font-size: 1.2rem; color: var(--input-text-color); - background-color: var(--main-bg-color); + background-color: var(--input-bg-color); text-align: center; padding: 0 8px; border: 0; } #lightning-address-form button { - background-color: var(--main-bg-color); + background-color: var(--input-bg-color); } .pos .invoice-dialog { @@ -760,7 +764,7 @@ input:checked+.slider:before { } .pos .error-message { - color: var(--base-color); + color: var(--two-color-r); max-width: 100%; overflow-wrap: break-word; } @@ -825,7 +829,7 @@ input:checked+.slider:before { .pos .loader { width: 4px; - color: var(--base-color); + color: var(--two-color); aspect-ratio: 1; border-radius: 50%; box-shadow: