ng-app: wallet info panel qr scanner and generator frontend

pull/33/head
Laurin Weger 6 months ago
parent 9882667604
commit 4edb821e5e
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 1
      ng-app/package.json
  2. 18
      ng-app/src/locales/en.json
  3. 324
      ng-app/src/routes/WalletInfo.svelte
  4. 3666
      pnpm-lock.yaml

@ -23,6 +23,7 @@
"classnames": "^2.3.2", "classnames": "^2.3.2",
"flowbite": "^1.6.5", "flowbite": "^1.6.5",
"flowbite-svelte": "^0.43.3", "flowbite-svelte": "^0.43.3",
"html5-qrcode": "^2.3.8",
"ng-sdk-js": "workspace:^0.1.0-preview.1", "ng-sdk-js": "workspace:^0.1.0-preview.1",
"svelte-i18n": "^4.0.0", "svelte-i18n": "^4.0.0",
"svelte-spa-router": "^3.3.0", "svelte-spa-router": "^3.3.0",

@ -18,13 +18,29 @@
}, },
"wallet_info": { "wallet_info": {
"title": "Wallet", "title": "Wallet",
"scan_qr": "Scan QR to export",
"generate_qr": "Generate QR to export",
"download": "Download Wallet File", "download": "Download Wallet File",
"download_failed": "Download Failed:<br/>{error}", "download_failed": "Download Failed:<br/>{error}",
"download_in_progress": "Download in progress...", "download_in_progress": "Download in progress...",
"download_successful": "You will find the file named \"{wallet_file}\" in your Downloads folder", "download_successful": "You will find the file named \"{wallet_file}\" in your Downloads folder",
"download_file_button": "Click here to download the wallet file", "download_file_button": "Click here to download the wallet file",
"remove_wallet": "Remove wallet from Device", "remove_wallet": "Remove wallet from Device",
"remove_wallet_confirm": "Are you sure you want to remove this wallet from your device?" "remove_wallet_modal.title": "Remove wallet?",
"remove_wallet_modal.confirm": "Are you sure you want to remove this wallet from your device?",
"offline_warning": "You cannot export your Wallet with QR-Code while being offline.<br />Please connect to the internet and to your broker first.",
"scan_qr.title": "Export by scanning QR-Code",
"scan_qr.notes": "Scan the QR-Code on the device that you want to transfer your wallet to.<br />For the wallet transfer, you agree that your double-encrypted wallet is stored on our servers for no more than 5 minutes.",
"scan_qr.scan_btn": "Scan QR Code ",
"scan_qr.scanner.title": "Scan your QR Code",
"scan_qr.scanner.loading": "Loading scanner",
"scan_qr.syncing": "Synchronizing wallet",
"scan_qr.scan_successful": "Success!<br />Your wallet has been synchronized to the new device.",
"gen_qr.title": "Export with generated QR-Code",
"gen_qr.notes": "Use the following QR-Code to scan with the device that you want to transfer your wallet to.<br />For the wallet transfer, you agree for your double-encrypted wallet to be stored on our servers for no more than 5 minutes.",
"gen_qr.img_title": "Your Export QR Code to Scan",
"gen_qr.img_alt": "Your Export QR Code to Scan",
"gen_qr.gen_button": "Generate QR Code"
}, },
"settings": { "settings": {
"title": "Settings" "title": "Settings"

@ -15,8 +15,8 @@
Provides info about wallet, broker, etc. and download option. Provides info about wallet, broker, etc. and download option.
--> -->
<script> <script lang="ts">
import { Modal } from "flowbite-svelte"; import { Alert, Button, Modal, Spinner } from "flowbite-svelte";
import { link, push } from "svelte-spa-router"; import { link, push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte"; import CenteredLayout from "../lib/CenteredLayout.svelte";
import { import {
@ -27,14 +27,28 @@
QrCode, QrCode,
Link, Link,
ArrowDownOnSquare, ArrowDownOnSquare,
Camera,
CheckBadge,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte"; import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte";
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
import { close_active_wallet, active_session, active_wallet } from "../store"; import {
type Html5QrcodeResult,
type Html5QrcodeScanner,
} from "html5-qrcode";
import {
close_active_wallet,
active_session,
active_wallet,
online,
} from "../store";
import { default as ng } from "../api"; import { default as ng } from "../api";
let WebQRScannerClassPromise: Promise<typeof Html5QrcodeScanner>;
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
let error; let error;
let nonActiveClass = let nonActiveClass =
@ -42,6 +56,29 @@
let top; let top;
let sub_menu: "scan_qr" | "generate_qr" | "text_code" | null = null;
/** QR source / blob URL */
let generation_state: "loading" | "generated" | null = null;
let generated_qr: string | undefined = undefined;
let generated_text_code: string | null = null;
let scanner_open = false;
let scanned_qr = null;
let scan_successful: null | true = null;
let html5QrcodeScanner: Html5QrcodeScanner;
async function load_qr_scanner_lib() {
// Load in browser only
if (!tauri_platform && !WebQRScannerClassPromise) {
WebQRScannerClassPromise = new Promise((resolve) => {
import("html5-qrcode").then((lib) => resolve(lib.Html5QrcodeScanner));
});
}
// TODO: Load alternative for native apps?
}
async function scrollToTop() { async function scrollToTop() {
await tick(); await tick();
top.scrollIntoView(); top.scrollIntoView();
@ -54,6 +91,68 @@
} }
}); });
function open_scan_menu() {
sub_menu = "scan_qr";
load_qr_scanner_lib();
}
async function open_gen_menu() {
sub_menu = "generate_qr";
generation_state = null;
}
async function gen_qr() {
generation_state = "loading"; // TODO: @niko = await ng.generate_export_qr();
// ToRemove:
setTimeout(() => {
generation_state = "generated";
generated_qr = "dummy";
}, 3000);
}
function on_qr_scanned(text: string) {
scanned_qr = text;
// TODO: API calls for synchronization @niko
// ToRemove:
setTimeout(() => {
scan_successful = true;
}, 2_000);
}
async function open_scanner() {
scanner_open = true;
const onScanSuccess = (
decoded_text: string,
decoded_result: Html5QrcodeResult
) => {
// handle the scanned code as you like, for example:
on_qr_scanned(decoded_text);
close_scanner();
// console.log(`Code matched = ${decoded_text}`, decodedResult);
};
const WebQRScanner = await WebQRScannerClassPromise;
html5QrcodeScanner = new WebQRScanner(
"scanner-div",
{ fps: 10, qrbox: { width: 300, height: 300 }, formatsToSupport: [0] },
/* verbose= */ false
);
html5QrcodeScanner.render(onScanSuccess, undefined);
}
function close_scanner() {
scanner_open = false;
if (html5QrcodeScanner) html5QrcodeScanner.clear();
html5QrcodeScanner = null;
}
function to_main_menu() {
sub_menu = null;
generated_qr = "loading";
generated_text_code = null;
}
let downloading = false; let downloading = false;
let wallet_file_ready = false; let wallet_file_ready = false;
let download_link = false; let download_link = false;
@ -102,6 +201,7 @@
<SidebarWrapper <SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800" divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
> >
{#if sub_menu === null}
<SidebarGroup ulClass="space-y-2" role="menu"> <SidebarGroup ulClass="space-y-2" role="menu">
<li> <li>
<h2 class="text-xl mb-6">{$t("pages.wallet_info.title")}</h2> <h2 class="text-xl mb-6">{$t("pages.wallet_info.title")}</h2>
@ -119,11 +219,44 @@
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3">Back</span> <span class="ml-3">{$t("buttons.back")}</span>
</li> </li>
<!-- Download Wallet --> <!-- Scan QR Code to log in with another device -->
<li
tabindex="0"
role="menuitem"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={open_scan_menu}
on:click={open_scan_menu}
>
<div>
<Camera
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
</div>
<span class="ml-3">{$t("pages.wallet_info.scan_qr")}</span>
</li>
<!-- Generate QR Code to log in with another device -->
<li
tabindex="0"
role="menuitem"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={open_gen_menu}
on:click={open_gen_menu}
>
<div>
<QrCode
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
</div>
<span class="ml-3">{$t("pages.wallet_info.generate_qr")}</span>
</li>
<!-- Download Wallet -->
{#if !downloading} {#if !downloading}
<li <li
tabindex="0" tabindex="0"
@ -189,7 +322,7 @@
class="flex items-center text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700" class="flex items-center text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
> >
<a <a
href={download_link} href={download_link || ""}
target="_blank" target="_blank"
download={wallet_file_ready} download={wallet_file_ready}
> >
@ -223,22 +356,25 @@
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</div> </div>
<span class="ml-3">{$t("pages.wallet_info.remove_wallet")}</span> <span class="ml-3">{$t("pages.wallet_info.remove_wallet")}</span
>
</li> </li>
<!-- Confirm Remove Wallet Modal -->
<Modal <Modal
autoclose autoclose
outsideclose outsideclose
bind:open={wallet_remove_modal_open} bind:open={wallet_remove_modal_open}
title="Remove Wallet" title={$t("pages.wallet_info.remove_wallet_modal.title")}
> >
<p class="mt-4"> <p class="mt-4">
{$t("pages.wallet_info.remove_confirm")} {$t("pages.wallet_info.remove_wallet_modal.confirm")}
</p> </p>
<div class="mt-4 flex justify-end"> <div class="mt-4 flex justify-end">
<button on:click={close_modal}>{$t("buttons.cancel")}</button> <button class="mr-2" on:click={close_modal}
>{$t("buttons.cancel")}</button
>
<button <button
class="mr-2 bg-primary-700 text-white" class=" text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
on:click={remove_wallet_confirmed} on:click={remove_wallet_confirmed}
> >
{$t("buttons.remove")} {$t("buttons.remove")}
@ -246,7 +382,7 @@
</div> </div>
</Modal> </Modal>
<!-- TODO: Show QRCode --> <!-- TODO: Copy Wallet Link -->
{#if false} {#if false}
<li <li
tabindex="0" tabindex="0"
@ -254,54 +390,182 @@
class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700" class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
> >
<div> <div>
<QrCode <Link
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</div> </div>
<span class="ml-3">{$t("qr_code")}</span> <span class="ml-3">{$t("pages.login.copy_wallet_link")}</span>
</li> </li>
<Modal
autoclose
outsideclose
title={$t("pages.wallet_info.qr_modal_title")}
>{@html $t("pages.wallet_info.qr_modal_description")}
</Modal>
{/if} {/if}
<!-- TODO: Copy Wallet Link --> <!-- TODO: Save to Device -->
{#if false} {#if false}
<li <li
tabindex="0" tabindex="0"
role="menuitem" role="menuitem"
class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700" class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
> >
<div> <!-- TODO: Same as with the trash icon, this is not same-sized as the others. -->
<Link <ArrowDownOnSquare
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
</div> <span class="ml-3">{$t("pages.login.keep_wallet")}</span>
<span class="ml-3">{$t("pages.login.copy_wallet_link")}</span> </li>
{/if}
</SidebarGroup>
{:else if sub_menu === "scan_qr"}
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">
{$t("pages.wallet_info.scan_qr.title")}
</h2>
</li>
<!-- Go Back -->
<li
tabindex="0"
role="menuitem"
class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={to_main_menu}
on:click={to_main_menu}
>
<ArrowLeft
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">{$t("buttons.back")}</span>
</li>
<!-- NOTES ABOUT QR-->
<li class="text-left">
{@html $t("pages.wallet_info.scan_qr.notes")}
</li>
<!-- Warning if offline -->
{#if !$online}
<li class="text-left">
<Alert color="red">
{@html $t("pages.wallet_info.offline_warning")}
</Alert>
</li> </li>
{/if} {/if}
<!-- TODO: Save to Device --> {#if scan_successful}
<li class="text-green-800 flex flex-col items-center">
<div class="mt-4">
<CheckBadge color="green" size="3em" />
</div>
<div class="mt-4">
{@html $t("pages.wallet_info.scan_qr.scan_successful")}
</div>
</li>
{:else if scanned_qr}
<li class="">
<Spinner class="mt-4 mb-2" />
<div>
{@html $t("pages.wallet_info.scan_qr.syncing")}...
<br />
<br />
{scanned_qr}
</div>
</li>
{:else if !scanner_open}
<Button
on:click={open_scanner}
disabled={false || !$online}
class="w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
{#if false} {#if false}
<Spinner class="mr-2" size="6" />
{/if}
{$t("pages.wallet_info.scan_qr.scan_btn")}
</Button>
{:else}
<!-- Scanner Open-->
<Modal
title={$t("pages.wallet_info.scan_qr.scanner.title")}
placement="center"
on:hide={close_scanner}
open={scanner_open}
class="h-[90vh]"
>
<div id="scanner-div" class="h-full">
{$t("pages.wallet_info.scan_qr.scanner.loading")}...
</div>
</Modal>
{/if}
</SidebarGroup>
<!-- Generate QR-Code screen -->
{:else if sub_menu === "generate_qr"}
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">
{$t("pages.wallet_info.gen_qr.title")}
</h2>
</li>
<!-- Go Back -->
<li <li
tabindex="0" tabindex="0"
role="menuitem" role="menuitem"
class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700" class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={to_main_menu}
on:click={to_main_menu}
> >
<!-- TODO: Same as with the trash icon, this is not same-sized as the others. --> <ArrowLeft
<ArrowDownOnSquare
tabindex="-1" tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white" class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/> />
<span class="ml-3">{$t("pages.login.keep_wallet")}</span> <span class="ml-3">{$t("buttons.back")}</span>
</li>
<!-- Notes about generated QR -->
<li class="text-left">
{@html $t("pages.wallet_info.gen_qr.notes")}
</li>
<!-- Warning if offline -->
{#if !$online}
<li class="text-left">
<Alert color="red">
{@html $t("pages.wallet_info.offline_warning")}
</Alert>
</li> </li>
{/if} {/if}
{#if !generated_qr || generation_state === "loading"}
<Button
on:click={gen_qr}
disabled={generation_state === "loading" || !$online}
class="w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
{#if generation_state === "loading"}
<Spinner class="mr-2" size="6" />
{/if}
{$t("pages.wallet_info.gen_qr.gen_button")}
</Button>
{:else}
<!-- QR Code -->
<div>
{#if generated_qr === "dummy"}
<div title={$t("pages.wallet_info.gen_qr.img_title")}>
<QrCode class="w-full h-full" />
</div>
{:else}
<img
src={generated_qr}
title={$t("pages.wallet_info.gen_qr.img_title")}
alt="pages.wallet_info.gen_qr_alt"
class="w-full h-full"
/>
{/if}
</div>
{/if}
</SidebarGroup> </SidebarGroup>
{:else if sub_menu === "text_code"}
TODO: Export with text code
{/if}
</SidebarWrapper> </SidebarWrapper>
</Sidebar> </Sidebar>
</div> </div>

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save