aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/LandingHeader.svelte8
-rw-r--r--src/components/RegisterOrLogin.svelte19
-rw-r--r--src/components/Spinner.svelte13
-rw-r--r--src/components/index.js1
-rw-r--r--src/lib/parse.js271
-rw-r--r--src/lib/utils.js5
-rw-r--r--src/locales/en.json6
-rw-r--r--src/locales/es.json3
-rw-r--r--src/locales/fr.json3
-rw-r--r--src/routes/[username]/dashboard/+page.svelte140
-rw-r--r--src/routes/scan/+page.svelte24
-rw-r--r--src/routes/send/+page.svelte62
12 files changed, 429 insertions, 126 deletions
diff --git a/src/components/LandingHeader.svelte b/src/components/LandingHeader.svelte
index c979e2c8..adeac98e 100644
--- a/src/components/LandingHeader.svelte
+++ b/src/components/LandingHeader.svelte
@@ -67,11 +67,11 @@
</button>
<div
- class="block md:hidden absolute top-0 {showMobileMenu
+ class="container w-full px-10 md:hidden absolute top-0 {showMobileMenu
? 'right-0'
: 'right-[-100%]'} transition-all ease-in-out duration-300 h-[100vh] w-full bg-white"
>
- <div class="space-y-8 mt-24 ml-10 font-bold text-xl">
+ <div class="space-y-8 mt-24 font-bold text-xl">
<button on:click={() => mobileMenuButtonClick(howItWorks)} class="block"
>{$t('howItWorks.header')}</button
>
@@ -82,12 +82,12 @@
>
{#if !$user}
<button
- class="border rounded-full px-6 py-2 font-bold block"
+ class="border rounded-full px-6 py-2 font-bold block w-full"
on:click={() => goto('/register')}
>{$t('nav.startInSeconds')}
</button>
<button
- class="bg-black text-white border rounded-full px-6 py-2 font-bold block"
+ class="bg-black text-white border rounded-full px-6 py-2 font-bold block w-full"
on:click={() => goto('/login')}
>
{$t('nav.signIn')}
diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte
index 88fe62e6..fa805a1d 100644
--- a/src/components/RegisterOrLogin.svelte
+++ b/src/components/RegisterOrLogin.svelte
@@ -1,5 +1,5 @@
<script>
- import { browser } from "$app/environment";
+ import { browser } from '$app/environment';
import { page } from '$app/stores';
import { tick } from 'svelte';
import { Icon } from '$comp';
@@ -23,12 +23,12 @@
signIn: '/login'
}[pageID];
- // TODO: move to server side
+ // TODO: move to server side
$: if (browser && $user) goto(`/${$user.username}/receive`);
- let focus = (el) => setTimeout(() => el.focus());
+ let focus = (el) => setTimeout(() => el.focus());
- console.log("REDIRECT STORE", $redirect)
+ console.log('REDIRECT STORE', $redirect);
</script>
<div class="pt-10">
@@ -56,13 +56,21 @@
class="bg-primary"
bind:value={username}
use:focus
+ autocapitalize="none"
/>
</div>
<div class="relative">
<label for="password" class="block font-semibold">{$t('login.password')}</label>
{#if revealPassword}
- <input name="password" type="text" required class="bg-primary" bind:value={password} />
+ <input
+ name="password"
+ type="text"
+ required
+ class="bg-primary"
+ bind:value={password}
+ autocapitalize="none"
+ />
{:else}
<input
name="password"
@@ -70,6 +78,7 @@
required
class="bg-primary"
bind:value={password}
+ autocapitalize="none"
/>
{/if}
<button
diff --git a/src/components/Spinner.svelte b/src/components/Spinner.svelte
new file mode 100644
index 00000000..eccf98ce
--- /dev/null
+++ b/src/components/Spinner.svelte
@@ -0,0 +1,13 @@
+<svg
+ class="mx-auto h-6 w-6 animate-spin text-white"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+>
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
+ <path
+ class="opacity-75"
+ fill="currentColor"
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
+ />
+</svg>
diff --git a/src/components/index.js b/src/components/index.js
index 7d01e338..ba0f6516 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,3 +1,4 @@
+export { default as Spinner } from './Spinner.svelte';
export { default as Check } from './Check.svelte';
export { default as Heart } from './Heart.svelte';
export { default as Quote } from './Quote.svelte';
diff --git a/src/lib/parse.js b/src/lib/parse.js
new file mode 100644
index 00000000..2a827956
--- /dev/null
+++ b/src/lib/parse.js
@@ -0,0 +1,271 @@
+export default (text) => {
+ if (!text) return;
+
+ if (text.includes('@') && text.includes('.')) {
+ let [name, domain] = text.split('@');
+ try {
+ clearTimeout(debounce);
+ await new Promise((r) => (debounce = setTimeout(r, 1500)));
+ ({ data: text } = await Vue.axios.get(
+ `/encode?domain=${domain}&name=${name}`
+ ));
+ } catch (e) {}
+ }
+
+ try {
+ if (text.toLowerCase().slice(0, 10) === 'lightning:')
+ text = text.slice(10);
+ let payreq = text.toLowerCase();
+ payment.payreq = payreq;
+ payment.payobj = bolt11.decode(payment.payreq);
+ let {
+ network: { bech32 },
+ } = payment.payobj;
+
+ if (!networks.includes('lightning'))
+ return commit('error', 'Lightning not supported in this account');
+
+ if (bech32 !== process.env.VUE_APP_NETWORK_BECH32) {
+ dispatch('clearPayment');
+ commit(
+ 'error',
+ `Wrong network prefix '${bech32}' instead of '${process.env.VUE_APP_NETWORK_BECH32}'`
+ );
+ throw new Error('Wrong network');
+ }
+
+ if (user.account.ticker !== 'BTC')
+ await dispatch(
+ 'shiftAccount',
+ user.accounts.find((a) => a.asset === BTC).id
+ );
+
+ let { tags, satoshis, millisatoshis } = payment.payobj;
+ let description = tags.find((t) => t.tagName === 'description');
+ if (description) payment.memo = description.data;
+ payment.amount = millisatoshis
+ ? Math.round(millisatoshis / 1000)
+ : satoshis;
+ payment.fiatAmount = ((payment.amount * getters.rate) / SATS).toFixed(
+ 2
+ );
+ payment.network = 'lightning';
+
+ await Vue.axios.post('/lightning/query', { payreq });
+ if (!getters.payment.recipient)
+ go({ name: 'send', params: { keep: true } });
+
+ return;
+ } catch (e) {
+ if (e.response) commit('error', e.response.data);
+ }
+ //
+ // let url;
+ // try {
+ // if (nodes.includes('bitcoin')) {
+ // url = bip21.decode(text);
+ // if (!networks.includes('bitcoin'))
+ // return commit('error', 'Bitcoin not supported in this account');
+ // payment.network = 'bitcoin';
+ // url.options.asset = BTC;
+ // }
+ // } catch (e) {
+ // if (nodes.includes('liquid')) {
+ // try {
+ // url = bip21.decode(text, 'liquidnetwork');
+ // if (!networks.includes('liquid'))
+ // return commit('error', 'Liquid not supported in this account');
+ // payment.network = 'liquid';
+ // } catch (e) {
+ // /**/
+ // }
+ // }
+ // }
+ //
+ // if (url) {
+ // let { amount, asset, assetid, message } = url.options;
+ // if (assetid) asset = assetid;
+ // if (!asset) asset = BTC;
+ // let account = user.accounts.find((a) => a.asset === asset);
+ // if (account) {
+ // if (account.asset !== user.account.asset)
+ // await dispatch('shiftAccount', account.id);
+ // } else return commit('error', 'Unrecognized asset');
+ //
+ // payment.address = url.address;
+ //
+ // if (message) payment.memo = message;
+ //
+ // if (amount) {
+ // payment.amount = parseInt((amount * SATS).toFixed(0));
+ // payment.fiatAmount = ((payment.amount * getters.rate) / SATS).toFixed(
+ // 2
+ // );
+ // }
+ //
+ // await dispatch('isInternal');
+ // await dispatch('estimateFee');
+ // go({ name: 'send', params: { keep: true } });
+ // return;
+ // }
+ //
+ // if (nodes.includes('bitcoin') && validate(text)) {
+ // if (!networks.includes('bitcoin'))
+ // return commit('error', 'Bitcoin not supported in this account');
+ // payment.address = text;
+ // payment.network = 'bitcoin';
+ // await dispatch('isInternal');
+ // go({ name: 'send', params: { keep: true } });
+ // return;
+ // }
+ //
+ // // Liquid
+ // if (nodes.includes('liquid') && isLiquid(text)) {
+ // if (!networks.includes('liquid'))
+ // return commit('error', 'Liquid not supported in this account');
+ // payment.address = text;
+ // payment.network = 'liquid';
+ // await dispatch('isInternal');
+ // go({ name: 'send', params: { keep: true } });
+ // return;
+ // }
+ //
+ // if (text.startsWith('6P')) {
+ // commit('text', text);
+ // return go('/decrypt');
+ // }
+ //
+ // try {
+ // let ecpair = ECPair.fromWIF(text, {
+ // ...this._vm.$network,
+ // confidentialPrefix: 1,
+ // assetHash: '',
+ // });
+ // commit('ecpair', ecpair);
+ // go('/sweep');
+ // } catch (e) {
+ // /* */
+ // }
+ //
+ // if (text.toLowerCase().startsWith('lnurl:')) {
+ // try {
+ // const { data: lnurl } = await Vue.axios.get(`/url?code=${text}`);
+ // await dispatch('handleScan', lnurl);
+ // return;
+ // } catch (e) {
+ // commit('error', e.response ? e.response.data : e.message);
+ // }
+ // }
+ //
+ // if (text.toLowerCase().includes('lnurl')) {
+ // commit('loading', true);
+ // let txt = text.toLowerCase().match(/lnurl[a-z0-9]+/);
+ //
+ // try {
+ // const { data: params } = await Vue.axios.get(`/decode?text=${txt}`);
+ // if (params.status === 'ERROR') {
+ // let { reason } = params;
+ // try {
+ // ({ reason } = JSON.parse(reason.replace(/.*{/, '{')));
+ // } catch {
+ // /**/
+ // }
+ //
+ // commit('loading', false);
+ // return commit('error', reason);
+ // }
+ //
+ // let { seed } = getters;
+ //
+ // switch (params.tag) {
+ // case 'channelRequest':
+ // await dispatch('openChannel', params);
+ // break;
+ // case 'login':
+ // if (!seed) ({ seed } = await dispatch('passwordPrompt'));
+ // try {
+ // const key = linkingKey(params.domain, seed);
+ // const signedMessage = secp256k1.ecdsaSign(
+ // hexToUint8Array(params.k1),
+ // key.privateKey
+ // );
+ // const signedMessageDER = secp256k1.signatureExport(
+ // signedMessage.signature
+ // );
+ // const linkingKeyPub = secp256k1.publicKeyCreate(
+ // key.privateKey,
+ // true
+ // );
+ // const sig = bytesToHexString(signedMessageDER);
+ //
+ // const response = await Vue.axios.post('/login', {
+ // params,
+ // sig,
+ // key: bytesToHexString(linkingKeyPub),
+ // });
+ //
+ // if (response.data.status === 'OK') {
+ // commit('snack', 'Login success');
+ // } else {
+ // commit('error', response.status);
+ // }
+ //
+ // go('/home');
+ // } catch (e) {
+ // commit('error', e.response ? e.response.data : e.message);
+ // }
+ // break;
+ //
+ // case 'withdrawRequest':
+ // commit('lnurl', params);
+ // go('/withdraw');
+ // break;
+ // case 'payRequest':
+ // commit('lnurl', params);
+ // go('/pay');
+ // break;
+ // }
+ // } catch (e) {
+ // commit('error', e.response ? e.response.data : e.message);
+ // }
+ //
+ // commit('loading', false);
+ // }
+ //
+ // if (isUuid(text)) {
+ // try {
+ // const { data: payment } = await Vue.axios.post('/checkRedeemCode', {
+ // redeemcode: text,
+ // });
+ //
+ // if (payment) {
+ // window.location.href = `${window.location.protocol}//${window.location.host}/redeem/${text}`;
+ // return;
+ // }
+ // } catch (e) {
+ // commit('error', e.response ? e.response.data : e.message);
+ // }
+ //
+ // try {
+ // const { data: invoice } = await Vue.axios.get(
+ // `/invoice?uuid=${text}`
+ // );
+ // await dispatch('handleScan', invoice.text);
+ // return;
+ // } catch (e) {
+ // commit('error', e.response ? e.response.data : e.message);
+ // }
+ // }
+ //
+ // if (
+ // text.startsWith(
+ // `${window.location.protocol}//${window.location.host}/redeem`
+ // )
+ // ) {
+ // window.location.href = text;
+ // }
+ //
+ // commit('stopScanning', true);
+ //
+ // return 'no';
+};
diff --git a/src/lib/utils.js b/src/lib/utils.js
index df621c4b..61e2ddac 100644
--- a/src/lib/utils.js
+++ b/src/lib/utils.js
@@ -1,6 +1,7 @@
import cookie from 'cookie';
import { browser } from '$app/environment';
import { toast } from '@zerodevx/svelte-toast';
+import { goto } from '$app/navigation';
export function scroll(section) {
section.scrollIntoView({ behavior: 'smooth' });
@@ -144,3 +145,7 @@ export const toggleFullscreen = (el) =>
: el.requestFullscreen().catch(console.log);
export const sats = 100000000;
+
+export const goBack = () => {
+ browser && history.go(-1);
+};
diff --git a/src/locales/en.json b/src/locales/en.json
index 7c07f0e9..0a99b947 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -162,7 +162,9 @@
"remove": "Remove",
"scanToPayBTC": "Scan to pay with bitcoin",
"dashboard": {
- "ACCOUNT_BALANCE": "ACCOUNT BALANCE",
+ "go": "Go",
+ "dashboard": "Dashboard",
+ "accountBalance": "Account Balance",
"bitcoinPrice": "bitcoin Price:",
"conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.",
"dataFromBinance": "Data from <span class=\"text-black\">Binance</span>.",
@@ -170,7 +172,7 @@
"optionalNote": "Note (Optional)",
"send": "Send",
"receive": "Receive a Payment",
- "withdraw": "Withdraw"
+ "withdraw": "Withdraw Funds"
},
"receive": {
"lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.",
diff --git a/src/locales/es.json b/src/locales/es.json
index f378c555..41452f4a 100644
--- a/src/locales/es.json
+++ b/src/locales/es.json
@@ -159,7 +159,8 @@
"remove": "Eliminar",
"scanToPayBTC": "Escanea para pagar en bitcoin",
"dashboard": {
- "ACCOUNT_BALANCE": "ACCOUNT BALANCE",
+ "dashboard": "Tablero",
+ "accountBalance": "Saldo",
"bitcoinPrice": "Precio de bitcoin:",
"conversionFeeWarning": "Cobraremos una tarifa de retiro del 1% sobre el monto",
"dataFromBinance": "Datos de <span class=\"text-black\">Binance</span>.",
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 221679ab..263bc74c 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -131,7 +131,8 @@
"remove": "Enlève",
"scanToPayBTC": "Scanner pour payer avec bitcoin",
"dashboard": {
- "ACCOUNT_BALANCE": "SOLDE",
+ "dashboard": "Tableau de bord",
+ "accountBalance": "Solde",
"bitcoinPrice": "Prix de bitcoin:",
"conversionFeeWarning": "Il y a un prix de 1% pour convertir entre bitcoin, Liquid et Lightning.",
"dataFromBinance": "Données de <span class=\"text-black\">Binance</span>.",
diff --git a/src/routes/[username]/dashboard/+page.svelte b/src/routes/[username]/dashboard/+page.svelte
index a580f349..55fe69ee 100644
--- a/src/routes/[username]/dashboard/+page.svelte
+++ b/src/routes/[username]/dashboard/+page.svelte
@@ -13,48 +13,29 @@
$: animateRate($rate);
let animateRate = (newRate) => {
- if (!accountBalanceFiat) return;
+ if (!accountBalanceFiat) return;
let t = 0;
- let newBalance = (user.account.balance * newRate / sats);
- let oldBalance = parseFloat(accountBalanceFiat);
- let diff = oldBalance - newBalance;
+ let newBalance = (user.account.balance * newRate) / sats;
+ let oldBalance = parseFloat(accountBalanceFiat);
+ let diff = oldBalance - newBalance;
clearInterval(interval);
interval = setInterval(() => {
let delta = diff * ease(t / 100);
- accountBalanceFiat = (oldBalance - delta).toFixed(2)
+ accountBalanceFiat = (oldBalance - delta).toFixed(2);
if (t > 80) clearInterval(interval);
t++;
}, 10);
};
$: user = data.user;
- $: accountBalanceFiat = accountBalanceFiat || user && $rate && (user.account.balance * $rate / sats).toFixed(2);
+ $: accountBalanceFiat =
+ accountBalanceFiat || (user && $rate && ((user.account.balance * $rate) / sats).toFixed(2));
let payreq = '',
payreqField,
- withdrawing,
loading;
- let toggle = async () => {
- payreq = '';
- withdrawing = !withdrawing;
- await tick();
- if (withdrawing) payreqField.focus();
- };
-
- let withdraw = async () => {
- loading = true;
- try {
- await post('/withdraw', { payreq });
- withdrawing = false;
- loading = false;
- } catch (e) {
- failure(e.message);
- loading = false;
- }
- };
-
$: btcPrice = f($selectedRate, user.currency);
$: accountBalanceBtc = user.account.balance / sats;
@@ -75,96 +56,29 @@
<div class="px-3 md:px-0 flex justify-center items-center mt-10 lg:mt-0 mb-20">
<div>
<h1 class="text-3xl md:text-4xl font-semibold mb-8">
- {withdrawing ? 'Withdraw' : 'Dashboard'}
+ {$t('user.dashboard.dashboard')}
</h1>
- {#if !withdrawing}
- <!-- <span class="text-secondary mx-auto text-lg font-bold" -->
- <!-- >{$t('user.dashboard.bitcoinPrice')} -->
- <!-- <span class="text-black" -->
- <!-- >{$selectedRate ? btcPrice : $t('user.dashboard.fetchingRate')}</span -->
- <!-- > -->
- <!-- </span> -->
- <!-- -->
- <h3 class="text-secondary font-bold border-b pb-1 mt-10 mb-6 w-full md:w-[500px]">
- {$t('user.dashboard.ACCOUNT_BALANCE')}
- </h3>
-
- <span class="text-3xl font-bold block mb-1">{accountBalanceSats} SAT</span>
+ <h3 class="text-secondary font-bold border-b pb-1 mt-10 mb-6 w-full md:w-[500px] uppercase">
+ {$t('user.dashboard.accountBalance')}
+ </h3>
- <span class="text-secondary text-xl block"
- >{$selectedRate ? f(accountBalanceFiat, user.currency) : $t('user.dashboard.fetchingRate')}</span
- >
-
- <div class="md:flex md:space-x-5">
- <button
- class="rounded-full border py-2 px-5 font-bold mt-4 block md:inline hover:opacity-80"
- on:click={() => goto(`/${user.username}/receive`)}>{$t('user.dashboard.receive')}</button
- >
- <button
- class="rounded-full border py-2 px-5 font-bold mt-5 md:mt-4 hover:opacity-80"
- on:click={toggle}>{$t('user.dashboard.withdraw')}</button
- >
- </div>
- {:else}
- <div class="space-y-3 w-[250px] md:w-[500px] md:pr-20">
- <button on:click={() => (withdrawing = false)}>
- <Icon icon="arrow-left" style="w-8 hover:opacity-80" />
- </button>
-
- <div>
- <!-- found missing translation -->
- <label for="invoice" class="font-bold mb-1 block">Lightning Invoice</label>
- <input
- bind:this={payreqField}
- type="text"
- name="payreq"
- required
- bind:value={payreq}
- class="block rounded-2xl p-3 w-full bg-primary"
- />
- </div>
+ <span class="text-3xl font-bold block mb-1">{accountBalanceSats} SAT</span>
- <!-- TODO -->
- <!--
- <div>
- <label for="note" class="font-bold mb-1 block">{$t('user.dashboard.optionalNote')}</label>
- <textarea name="note" rows={2} class="block border rounded-2xl p-3 w-full bg-primary" />
- </div>
--->
+ <span class="text-secondary text-xl block"
+ >{$selectedRate
+ ? f(accountBalanceFiat, user.currency)
+ : $t('user.dashboard.fetchingRate')}</span
+ >
- <button
- disabled={!payreq}
- class="{!payreq
- ? 'opacity-50'
- : 'opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold w-full mt-2 bg-black text-white"
- on:click={withdraw}
- >
- {#if loading}
- <svg
- class="mx-auto h-6 w-6 animate-spin text-white"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- >
- <circle
- class="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- stroke-width="4"
- />
- <path
- class="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
- />
- </svg>
- {:else}
- {$t('user.dashboard.send')}
- {/if}
- </button>
- </div>
- {/if}
+ <div class="grid sm:grid-cols-2 gap-2 mt-8">
+ <button
+ class="rounded-full border py-2 px-5 font-bold hover:opacity-80 text-xl w-full mb-2"
+ on:click={() => goto(`/${user.username}/receive`)}>{$t('user.dashboard.receive')}</button
+ >
+ <button
+ class="rounded-full border py-2 px-5 font-bold hover:opacity-80 text-xl w-full mb-2"
+ on:click={() => goto('/send')}>{$t('user.dashboard.withdraw')}</button
+ >
+ </div>
</div>
</div>
diff --git a/src/routes/scan/+page.svelte b/src/routes/scan/+page.svelte
new file mode 100644
index 00000000..5c8c09f6
--- /dev/null
+++ b/src/routes/scan/+page.svelte
@@ -0,0 +1,24 @@
+<script>
+ import { Icon } from "$comp";
+ import QrScanner from 'qr-scanner';
+ import { onMount } from 'svelte';
+ import { goBack } from "$lib/utils";
+
+ let scanner, vid;
+ onMount(() => {
+ scanner = new QrScanner(vid, console.log);
+ scanner.start();
+ });
+
+</script>
+
+ <button
+ class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80"
+ on:click={goBack}
+ >
+ <Icon icon="arrow-left" style="w-10" />
+ </button>
+
+ <div class="flex w-full">
+<video bind:this={vid} class="w-full max-w-[600px] mx-auto" />
+</div>
diff --git a/src/routes/send/+page.svelte b/src/routes/send/+page.svelte
new file mode 100644
index 00000000..777f9766
--- /dev/null
+++ b/src/routes/send/+page.svelte
@@ -0,0 +1,62 @@
+<script>
+ import { t } from '$lib/translations';
+ import { AppHeader, Icon, Spinner } from '$comp';
+
+ let payreq = '',
+ loading,
+ payreqField;
+
+ let withdraw = async () => {
+ loading = true;
+ try {
+ await post('/withdraw', { payreq });
+ loading = false;
+ } catch (e) {
+ failure(e.message);
+ loading = false;
+ }
+ };
+</script>
+
+<AppHeader avatarPosition="left-[calc(50vw-64px)] lg:left-[calc(15vw-64px)]" />
+
+<div class="container px-4 mt-20 max-w-xl mx-auto">
+ <div>
+ <!-- found missing translation -->
+ <label for="invoice" class="font-bold mb-1 block"
+ >Destination <small class="text-secondary">Address, Invoice, User, Email or Phone</small
+ ></label
+ >
+
+ <div class="relative">
+ <input
+ bind:this={payreqField}
+ type="text"
+ name="payreq"
+ required
+ bind:value={payreq}
+ class="block rounded-2xl p-3 w-full bg-primary"
+ />
+
+ <a href="/scan">
+ <div class="flex absolute h-[48px] right-0 top-0 px-2 mt-[1px]">
+ <Icon icon="scan" style="mr-2 w-6 my-auto" />
+ </div>
+ </a>
+ </div>
+ </div>
+
+ <button
+ disabled={!payreq}
+ class="{!payreq
+ ? 'opacity-50'
+ : 'opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold w-full mt-2 bg-black text-white"
+ on:click={withdraw}
+ >
+ {#if loading}
+ <Spinner />
+ {:else}
+ {$t('user.dashboard.go')}
+ {/if}
+ </button>
+</div>