wip login part

pull/33/head
Laurin Weger 6 months ago
parent 6f818448b3
commit e68bc0c4d6
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 6
      ng-app/src/App.svelte
  2. 25
      ng-app/src/locales/en.json
  3. 1
      ng-app/src/routes/WalletCreate.svelte
  4. 8
      ng-app/src/routes/WalletInfo.svelte
  5. 81
      ng-app/src/routes/WalletLogin.svelte
  6. 310
      ng-app/src/routes/WalletLoginQr.svelte
  7. 310
      ng-app/src/routes/WalletLoginTextCode.svelte
  8. 9
      ng-app/src/styles.css

@ -38,11 +38,15 @@
import ng from "./api"; import ng from "./api";
import AccountInfo from "./routes/AccountInfo.svelte"; import AccountInfo from "./routes/AccountInfo.svelte";
import WalletLoginQr from "./routes/WalletLoginQr.svelte";
import WalletLoginTextCode from "./routes/WalletLoginTextCode.svelte";
const routes = new Map(); const routes = new Map();
routes.set("/", Home); routes.set("/", Home);
routes.set("/test", Test); routes.set("/test", Test);
routes.set("/wallet/login", WalletLogin); routes.set("/wallet/login", WalletLogin);
routes.set("/wallet/login-qr", WalletLoginQr);
routes.set("/wallet/login-text-code", WalletLoginTextCode);
routes.set("/wallet/create", WalletCreate); routes.set("/wallet/create", WalletCreate);
routes.set("/i/:invitation", Invitation); routes.set("/i/:invitation", Invitation);
routes.set("/user", User); routes.set("/user", User);
@ -286,5 +290,3 @@
{:else} {:else}
<Router {routes} /> <Router {routes} />
{/if} {/if}

@ -30,17 +30,17 @@
"remove_wallet_modal.confirm": "Are you sure you want to remove this wallet from your device?", "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.", "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.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.notes": "Scan the QR-Code on the device that you want to transfer your wallet to.<br />During this wallet import, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using two levels of encryption.",
"scan_qr.scan_btn": "Scan QR Code ", "scan_qr.scan_btn": "Scan QR Code ",
"scan_qr.scanner.title": "Scan your QR Code", "scan_qr.scanner.title": "Scan your QR Code",
"scan_qr.scanner.loading": "Loading scanner", "scan_qr.scanner.loading": "Loading scanner",
"scan_qr.syncing": "Synchronizing wallet", "scan_qr.syncing": "Synchronizing wallet",
"scan_qr.scan_successful": "Success!<br />Your wallet has been synchronized to the new device.", "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.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.notes": "Use the following QR-Code to scan with the device that you want to transfer your wallet to.<br />During this wallet import, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using two levels of encryption.",
"gen_qr.img_title": "Your Export QR Code to Scan", "gen_qr.img_title": "Your Export QR Code to Scan",
"gen_qr.img_alt": "Your Export QR Code to Scan", "gen_qr.img_alt": "Your Export QR Code to Scan",
"gen_qr.gen_button": "Generate QR Code" "gen_qr.gen_button": "Display QR Code"
}, },
"settings": { "settings": {
"title": "Settings" "title": "Settings"
@ -244,6 +244,25 @@
"import_link": "Enter a Wallet Link", "import_link": "Enter a Wallet Link",
"new_wallet": "Create a new Wallet", "new_wallet": "Create a new Wallet",
"logged_in": "You are logged in.<br /> please wait while the app is loading" "logged_in": "You are logged in.<br /> please wait while the app is loading"
},
"wallet_login_qr": {
"title": "Import Wallet with QR-Code",
"offline_warning": "Cannot connect to the wallet synchronization service. Are you offline?",
"scan.description": "To import your wallet from another device, generate a wallet QR-Code there. On the other device, go to<br /><span class=\"path\">User Panel > Wallet > Generate QR</span> to export.<br />Both devices need to be online.<br />For the wallet transfer, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using double encryption.",
"scan.button": "Scan QR-Code",
"scan.syncing": "Importing wallet",
"scan.error": "An error occurred transferring wallet: {error}",
"scan.modal.title": "Scan Wallet QR-Code",
"scan.success": "Your wallet has been imported!",
"gen.button": "Generate",
"gen.description": "To import your wallet from another device, you have to display a QR-Code here on this device, and then scan it with your other device. On the other device, go to<br /><span class=\"path\">User Panel > Wallet > Scan QR</span> to export.<br />Both devices need to be online.<br />For the wallet transfer, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using double encryption. If your other device does not have a camera, then you should use the \"Enter a TextCode\" option on the previous screen.",
"gen.generated": "Scan this QR-Code from the the other device.",
"gen.success": "Your wallet has been imported!",
"success_btn": "Continue to Login"
},
"wallet_login_textcode": {
"title": "Import Wallet from TextCode",
"warning": "You have to exchange this TextCode with the other device by any means available to you (email, other messenger apps, etc...). It is highly recommended to use a tool that is end-to-end encrypted. If you can, you should use instead the \"Import with QRCode\" option, as it is safer. If your devices are not connected to the internet, then you can use the \"Import a Wallet File\" option. In this case, you will transfer the wallet file with a USB key, from one device to the other, or for mobile, by connecting your mobile with the USB cable, to the computer, and then transferring the file with the File Transfer utility on Android, or AirDrop/Finder/iTunes on an iPhone/mac/PC. We do not recommend uploading your wallet file to any cloud service."
} }
}, },
"buttons": { "buttons": {

@ -1622,7 +1622,6 @@
<br /> <br />
<!-- Mnemonic (copy button). TODO: once the component is available-->
<label for="mnemonic" <label for="mnemonic"
>{@html $t("pages.wallet_create.your_mnemonic")}</label >{@html $t("pages.wallet_create.your_mnemonic")}</label
> >

@ -136,9 +136,15 @@
html5QrcodeScanner = new WebQRScanner( html5QrcodeScanner = new WebQRScanner(
"scanner-div", "scanner-div",
{ fps: 10, qrbox: { width: 300, height: 300 }, formatsToSupport: [0] }, { fps: 10, qrbox: { width: 300, height: 300 }, formatsToSupport: [0] },
/* verbose= */ false false
); );
html5QrcodeScanner.render(onScanSuccess, undefined); html5QrcodeScanner.render(onScanSuccess, undefined);
// Auto-Request camera permissions (there's no native way, unfortunately...)
setTimeout(() => {
// Auto-start by clicking button
document.getElementById("html5-qrcode-button-camera-permission")?.click();
}, 100);
} }
function close_scanner() { function close_scanner() {

@ -18,7 +18,7 @@
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy, tick } from "svelte"; import { onMount, onDestroy, tick } from "svelte";
import { link, push } from "svelte-spa-router"; import { link, push } from "svelte-spa-router";
import { t, locale } from "svelte-i18n"; import { t, locale } from "svelte-i18n";
import Login from "../lib/Login.svelte"; import Login from "../lib/Login.svelte";
import CenteredLayout from "../lib/CenteredLayout.svelte"; import CenteredLayout from "../lib/CenteredLayout.svelte";
import ng from "../api"; import ng from "../api";
@ -33,6 +33,7 @@
set_active_session, set_active_session,
has_wallets, has_wallets,
} from "../store"; } from "../store";
import { QrCode } from "svelte-heros-v2";
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
@ -302,56 +303,40 @@
</svg> </svg>
{$t("pages.wallet_login.import_file")} {$t("pages.wallet_login.import_file")}
</button> </button>
<Button <a href="/wallet/login-qr" use:link>
style="min-width: 250px;justify-content: left;" <Button
disabled style="min-width: 250px;justify-content: left;"
class="disabled mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2" class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
> >
<path <QrCode class="w-8 h-8 mr-2 -ml-1" />
stroke-linecap="round" {$t("pages.wallet_login.import_qr")}
stroke-linejoin="round" </Button>
d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z" </a>
/> <a href="/wallet/login-text-code" use:link>
<path <Button
stroke-linecap="round" style="min-width: 250px;justify-content: left;"
stroke-linejoin="round" disabled
d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z" class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
/>
</svg>
{$t("pages.wallet_login.import_qr")}
</Button>
<Button
style="min-width: 250px;justify-content: left;"
disabled
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
> >
<path <svg
stroke-linecap="round" class="w-8 h-8 mr-2 -ml-1"
stroke-linejoin="round" fill="none"
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" stroke="currentColor"
/> stroke-width="1.5"
</svg> viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
/>
</svg>
{$t("pages.wallet_login.import_link")} {$t("pages.wallet_login.import_link")}
</Button> </Button>
</a>
<a href="/wallet/create" use:link> <a href="/wallet/create" use:link>
<button <button
tabindex="-1" tabindex="-1"

@ -0,0 +1,310 @@
<script lang="ts">
import { t } from "svelte-i18n";
import {
type Html5QrcodeResult,
type Html5QrcodeScanner,
} from "html5-qrcode";
import {
Alert,
Modal,
Sidebar,
SidebarGroup,
SidebarWrapper,
Spinner,
} from "flowbite-svelte";
import {
ArrowLeft,
ArrowRightCircle,
Camera,
CheckBadge,
QrCode,
} from "svelte-heros-v2";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { onMount } from "svelte";
import { push } from "svelte-spa-router";
let WebQRScannerClassPromise: Promise<typeof Html5QrcodeScanner>;
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?
}
let top;
const tauri_platform: string | undefined = import.meta.env.TAURI_PLATFORM;
let has_camera: boolean | "checking" = "checking";
let login_method: "scan" | "gen" | undefined = undefined;
let scan_state:
| "before_start"
| "scanning"
| "has_scanned"
| "success"
| Error = "before_start";
let scanned_qr: string | undefined = undefined;
let gen_state:
| "before_start"
| "generating"
| "generated"
| "success"
| Error = "before_start";
let generated_qr: string | undefined = undefined;
const check_has_camera = async () => {
if (!tauri_platform) {
// If there is a camera, go to scan mode, else gen mode.
try {
const devices = await navigator.mediaDevices.enumerateDevices();
has_camera =
devices.filter((device) => device.kind === "videoinput").length > 0;
} catch {
has_camera = false;
}
login_method = has_camera ? "scan" : "gen";
// Load Scanner lib, if necessary.
if (has_camera) load_qr_scanner_lib();
} else {
// TODO: rust API @niko
}
};
check_has_camera();
function on_qr_scanned(text: string) {
scan_state = "has_scanned";
scanned_qr = text;
// TODO: API calls for synchronization @niko
// ToRemove:
setTimeout(() => {
scan_state = "success";
}, 2_000);
}
function clear_scanner() {
if (html5QrcodeScanner) html5QrcodeScanner.clear();
html5QrcodeScanner = null;
}
async function open_scanner() {
scan_state = "scanning";
const WebQRScanner = await WebQRScannerClassPromise;
html5QrcodeScanner = new WebQRScanner(
"scanner-div",
{ fps: 10, qrbox: { width: 300, height: 300 }, formatsToSupport: [0] },
false
);
html5QrcodeScanner.render((decoded_text, decoded_result) => {
// Handle scan result
on_qr_scanned(decoded_text);
clear_scanner();
}, undefined);
// Auto-Request camera permissions (there's no native way, unfortunately...)
setTimeout(() => {
// Auto-start by clicking button
document.getElementById("html5-qrcode-button-camera-permission")?.click();
}, 100);
}
function close_scanner_modal() {
clear_scanner();
if (scanned_qr) {
scan_state = "has_scanned";
} else {
scan_state = "before_start";
}
}
function generate_qr() {
gen_state = "generating";
// TODO: @niko generated_qr = await ng.generate_export_qr();
// ToRemove:
setTimeout(() => {
gen_state = "generated";
generated_qr = "dummy";
}, 1500);
setTimeout(() => {
gen_state = "success";
}, 3500);
}
function continue_to_login(wallet) {}
function scrollToTop() {
top.scrollIntoView();
}
onMount(() => scrollToTop());
</script>
<CenteredLayout>
<div class="container3" bind:this={top}>
<div
class="flex flex-col justify-center max-w-md mx-6 mb-20 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<!-- Title -->
<div>
<h2 class="text-xl mb-6">{$t("pages.wallet_login_qr.title")}</h2>
</div>
<!-- Checking, if camera is available... -->
{#if login_method === undefined}
<!-- TODO: Check connectivity here-->
<div><Spinner /></div>
{:else if false}
<!-- Warning, if offline -->
<!-- TODO: get connection status to nextgraph.one -->
<div class="text-left">
<Alert color="red">
{@html $t("pages.wallet_login_qr.offline_warning")}
</Alert>
</div>
{:else if login_method === "scan"}
<!-- Scan Mode -->
{#if scan_state === "before_start"}
<!-- Notes about QR -->
<div class="text-left text-sm">
{@html $t("pages.wallet_login_qr.scan.description")}
</div>
{:else if scan_state === "scanning"}
<!-- Modal is down at the bottom -->
{:else if scan_state === "has_scanned"}
<!-- Scanned QR -->
<div>
<Spinner />
</div>
<div class="mt-2">
{$t("pages.wallet_login_qr.scan.syncing")}
</div>
{:else if scan_state === "success"}
<div class="mt-4">
<CheckBadge class="w-full" color="green" size="3em" />
</div>
<div class="mt-4">
{@html $t("pages.wallet_login_qr.scan.success")}
</div>
{:else}
<!-- Error -->
{$t("pages.wallet_login_qr.scan.error", {
values: { error: $t("errors." + scan_state) },
})}
{/if}
{:else if login_method === "gen"}
<!-- Generate QR Code to log in with another device -->
{#if gen_state == "before_start"}
<!-- Notes about QR Generation -->
<div class="text-left text-sm">
{@html $t("pages.wallet_login_qr.gen.description")}
</div>
{:else if gen_state === "generating"}
<div>
<Spinner class="w-full" />
</div>
{:else if gen_state === "generated"}
<!-- Notes about generated QR -->
<div class="text-left text-sm">
{@html $t("pages.wallet_login_qr.gen.generated")}
</div>
<!-- Generated 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>
{:else if gen_state === "success"}
<div class="mt-4">
<CheckBadge class="w-full" color="green" size="3em" />
</div>
<div class="mt-4">
{@html $t("pages.wallet_login_qr.gen.success")}
</div>
{:else}
<!-- gen_state has Error -->
{$t("pages.wallet_login_qr.gen.error")}
{/if}
{/if}
<div class="mx-auto">
<div class="my-4">
{#if login_method === "scan" && scan_state === "before_start"}
<!-- Open Scanner Button-->
<button
on:click={open_scanner}
class="mt-4 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 mb-2"
>
<Camera
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.scan.button")}
</button>
{:else if login_method === "gen" && gen_state === "before_start"}
<!-- Generate QR Button -->
<button
on:click={generate_qr}
class="mt-4 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 mb-2"
>
<QrCode
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.gen.button")}
</button>
{:else if scan_state === "success" || gen_state === "success"}
<a href="#/wallet/login">
<button
class="mt-4 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 mb-2"
>
<ArrowRightCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.success_btn")}
</button>
</a>
{/if}
<!-- Go Back -->
{#if scan_state !== "success" && gen_state !== "success"}
<button
on:click={() => window.history.go(-1)}
class="mt-8 w-full text-gray-500 dark:text-gray-400 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"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
{/if}
</div>
</div>
</div>
<!-- Scanner Open-->
<Modal
title={$t("pages.wallet_login_qr.scan.modal.title")}
placement="center"
on:hide={close_scanner_modal}
open={scan_state === "scanning"}
class="h-[85vh]"
>
<div id="scanner-div" class="h-full">
{$t("pages.wallet_login_qr.scan.modal.loading")}...
</div>
</Modal>
</div>
</CenteredLayout>

@ -0,0 +1,310 @@
<script lang="ts">
import { t } from "svelte-i18n";
import {
type Html5QrcodeResult,
type Html5QrcodeScanner,
} from "html5-qrcode";
import {
Alert,
Modal,
Sidebar,
SidebarGroup,
SidebarWrapper,
Spinner,
} from "flowbite-svelte";
import {
ArrowLeft,
ArrowRightCircle,
Camera,
CheckBadge,
QrCode,
} from "svelte-heros-v2";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { onMount } from "svelte";
import { push } from "svelte-spa-router";
let WebQRScannerClassPromise: Promise<typeof Html5QrcodeScanner>;
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?
}
let top;
const tauri_platform: string | undefined = import.meta.env.TAURI_PLATFORM;
let has_camera: boolean | "checking" = "checking";
let login_method: "scan" | "gen" | undefined = undefined;
let scan_state:
| "before_start"
| "scanning"
| "has_scanned"
| "success"
| Error = "before_start";
let scanned_qr: string | undefined = undefined;
let gen_state:
| "before_start"
| "generating"
| "generated"
| "success"
| Error = "before_start";
let generated_qr: string | undefined = undefined;
const check_has_camera = async () => {
if (!tauri_platform) {
// If there is a camera, go to scan mode, else gen mode.
try {
const devices = await navigator.mediaDevices.enumerateDevices();
has_camera =
devices.filter((device) => device.kind === "videoinput").length > 0;
} catch {
has_camera = false;
}
login_method = has_camera ? "scan" : "gen";
// Load Scanner lib, if necessary.
if (has_camera) load_qr_scanner_lib();
} else {
// TODO: rust API @niko
}
};
check_has_camera();
function on_qr_scanned(text: string) {
scan_state = "has_scanned";
scanned_qr = text;
// TODO: API calls for synchronization @niko
// ToRemove:
setTimeout(() => {
scan_state = "success";
}, 2_000);
}
function clear_scanner() {
if (html5QrcodeScanner) html5QrcodeScanner.clear();
html5QrcodeScanner = null;
}
async function open_scanner() {
scan_state = "scanning";
const WebQRScanner = await WebQRScannerClassPromise;
html5QrcodeScanner = new WebQRScanner(
"scanner-div",
{ fps: 10, qrbox: { width: 300, height: 300 }, formatsToSupport: [0] },
false
);
html5QrcodeScanner.render((decoded_text, decoded_result) => {
// Handle scan result
on_qr_scanned(decoded_text);
clear_scanner();
}, undefined);
// Auto-Request camera permissions (there's no native way, unfortunately...)
setTimeout(() => {
// Auto-start by clicking button
document.getElementById("html5-qrcode-button-camera-permission")?.click();
}, 100);
}
function close_scanner_modal() {
clear_scanner();
if (scanned_qr) {
scan_state = "has_scanned";
} else {
scan_state = "before_start";
}
}
function generate_qr() {
gen_state = "generating";
// TODO: @niko generated_qr = await ng.generate_export_qr();
// ToRemove:
setTimeout(() => {
gen_state = "generated";
generated_qr = "dummy";
}, 1500);
setTimeout(() => {
gen_state = "success";
}, 3500);
}
function continue_to_login(wallet) {}
function scrollToTop() {
top.scrollIntoView();
}
onMount(() => scrollToTop());
</script>
<CenteredLayout>
<div class="container3" bind:this={top}>
<div
class="flex flex-col justify-center max-w-md mx-6 mb-20 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<!-- Title -->
<div>
<h2 class="text-xl mb-6">{$t("pages.wallet_login_qr.title")}</h2>
</div>
<!-- Checking, if camera is available... -->
{#if login_method === undefined}
<!-- TODO: Check connectivity here-->
<div><Spinner /></div>
{:else if false}
<!-- Warning, if offline -->
<!-- TODO: get connection status to nextgraph.one -->
<div class="text-left">
<Alert color="red">
{@html $t("pages.wallet_login_qr.offline_warning")}
</Alert>
</div>
{:else if login_method === "scan"}
<!-- Scan Mode -->
{#if scan_state === "before_start"}
<!-- Notes about QR -->
<div class="text-left text-sm">
{@html $t("pages.wallet_login_qr.scan.description")}
</div>
{:else if scan_state === "scanning"}
<!-- Modal is down at the bottom -->
{:else if scan_state === "has_scanned"}
<!-- Scanned QR -->
<div>
<Spinner />
</div>
<div class="mt-2">
{$t("pages.wallet_login_qr.scan.syncing")}
</div>
{:else if scan_state === "success"}
<div class="mt-4">
<CheckBadge class="w-full" color="green" size="3em" />
</div>
<div class="mt-4">
{@html $t("pages.wallet_login_qr.scan.success")}
</div>
{:else}
<!-- Error -->
{$t("pages.wallet_login_qr.scan.error", {
values: { error: $t("errors." + scan_state) },
})}
{/if}
{:else if login_method === "gen"}
<!-- Generate QR Code to log in with another device -->
{#if gen_state == "before_start"}
<!-- Notes about QR Generation -->
<div class="text-left text-sm">
{@html $t("pages.wallet_login_qr.gen.description")}
</div>
{:else if gen_state === "generating"}
<div>
<Spinner class="w-full" />
</div>
{:else if gen_state === "generated"}
<!-- Notes about generated QR -->
<div class="text-left text-sm">
{@html $t("pages.wallet_login_qr.gen.generated")}
</div>
<!-- Generated 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>
{:else if gen_state === "success"}
<div class="mt-4">
<CheckBadge class="w-full" color="green" size="3em" />
</div>
<div class="mt-4">
{@html $t("pages.wallet_login_qr.gen.success")}
</div>
{:else}
<!-- gen_state has Error -->
{$t("pages.wallet_login_qr.gen.error")}
{/if}
{/if}
<div class="mx-auto">
<div class="my-4">
{#if login_method === "scan" && scan_state === "before_start"}
<!-- Open Scanner Button-->
<button
on:click={open_scanner}
class="mt-4 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 mb-2"
>
<Camera
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.scan.button")}
</button>
{:else if login_method === "gen" && gen_state === "before_start"}
<!-- Generate QR Button -->
<button
on:click={generate_qr}
class="mt-4 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 mb-2"
>
<QrCode
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.gen.button")}
</button>
{:else if scan_state === "success" || gen_state === "success"}
<a href="#/wallet/login">
<button
class="mt-4 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 mb-2"
>
<ArrowRightCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.success_btn")}
</button>
</a>
{/if}
<!-- Go Back -->
{#if scan_state !== "success" && gen_state !== "success"}
<button
on:click={() => window.history.go(-1)}
class="mt-8 w-full text-gray-500 dark:text-gray-400 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"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
{/if}
</div>
</div>
</div>
<!-- Scanner Open-->
<Modal
title={$t("pages.wallet_login_qr.scan.modal.title")}
placement="center"
on:hide={close_scanner_modal}
open={scan_state === "scanning"}
class="h-[85vh]"
>
<div id="scanner-div" class="h-full">
{$t("pages.wallet_login_qr.scan.modal.loading")}...
</div>
</Modal>
</div>
</CenteredLayout>

@ -9,6 +9,15 @@
// according to those terms. // according to those terms.
*/ */
/** To format paths, like Settings > Wallet > Generate Wallet QR */
.path {
font-family: monospace;
background-color: rgba(73, 114, 165, 0.1);
}
/* TODO: hide qr scanner and info button */
.logo { .logo {
padding: 1.5em; padding: 1.5em;
will-change: filter; will-change: filter;

Loading…
Cancel
Save