user panel part

some design things left
pull/33/head
Laurin Weger 5 months ago
parent a62c0cc342
commit 46c6228de2
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 11
      ng-app/src/locales/en.json
  2. 9
      ng-app/src/routes/WalletCreate.svelte
  3. 314
      ng-app/src/routes/WalletInfo.svelte
  4. 71
      ng-app/src/routes/WalletLogin.svelte
  5. 27
      ng-app/src/routes/WalletLoginQr.svelte
  6. 2
      ng-app/src/routes/WalletLoginTextCode.svelte

@ -12,7 +12,6 @@
"personal": "Personal"
},
"user_registered": {
"back_to_homepage": "Go Back to Homepage",
"success": "You have been successfully registered.",
"success_with_invitation": "You have been successfully registered to {invitation_name}."
},
@ -28,6 +27,7 @@
"remove_wallet": "Remove wallet from Device",
"remove_wallet_modal.title": "Remove wallet?",
"remove_wallet_modal.confirm": "Are you sure you want to remove this wallet from your device?",
"create_text_code": "Generate TextCode to export",
"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 />.",
"scan_qr.scan_btn": "Scan QR Code ",
@ -39,7 +39,9 @@
"gen_qr.notes": "Use the following QR-Code to scan with the device that you want to transfer your wallet to.",
"gen_qr.img_title": "Your Export QR Code to Scan",
"gen_qr.img_alt": "Your Export QR Code to Scan",
"gen_qr.gen_button": "Display QR Code"
"gen_qr.gen_button": "Display QR Code",
"gen_text_code.title": "Export with TextCode",
"gen_text_code.gen_btn": "Generate TextCode"
},
"settings": {
"title": "Settings"
@ -115,7 +117,6 @@
"qr_code": "Wallet QRCode",
"qr_modal_title": "My Wallet QRCode",
"qr_modal_description": "Use this QRCode to log in with your wallet on new devices.",
"copy_wallet_link": "Copy Wallet Link",
"keep_wallet": "Save to Device for Future Logins"
},
"account_info": {
@ -239,6 +240,8 @@
},
"wallet_login": {
"select_wallet": "Select a wallet to login with",
"from_import.title": "Your wallet has been transferred",
"from_import.description": "Your wallet has been transferred!<br />To finish the import, please log in.",
"with_another_wallet": "Log in with another wallet",
"import_wallet": "Import your wallet",
"import_file": "Import a Wallet File",
@ -366,7 +369,7 @@
},
"wallet_sync": {
"offline_warning": "You cannot transfer your wallet when offline.<br />Please make sure, you are connected first.",
"textcode.usage_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.",
"textcode.usage_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 the \"Import with QRCode\" option instead, 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.",
"server_transfer_notice": "Both devices need to be online.<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.",
"importing": "Importing wallet",
"error": "An error occurred while synchronizing your wallet:<br />{error}"

@ -44,7 +44,7 @@
} from "../wallet_emojis";
import { onMount, onDestroy, tick } from "svelte";
import { wallets, set_active_session, has_wallets, display_error } from "../store";
import { wallets, has_wallets, display_error } from "../store";
import Spinner from "../lib/components/Spinner.svelte";
const param = new URLSearchParams($querystring);
@ -223,7 +223,6 @@
}
async function save_security() {
device_name = await ng.get_device_name();
options = {
trusted: true,
@ -583,7 +582,7 @@
tabindex="-1"
class="text-white bg-primary-700 hover:bg-primary-700/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-700/55 mb-2"
>
{$t("bottons.back_to_homepage")}
{$t("buttons.back_to_homepage")}
</button>
</a>
{/if}
@ -1442,9 +1441,7 @@
id="device-name-input"
class="mt-2 bg-gray-50 border border-gray-300 text-xs rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
bind:value={device_name}
placeholder={$t(
"pages.login.device_name_placeholder"
)}
placeholder={$t("pages.login.device_name_placeholder")}
type="text"
/>
{/if}

@ -26,17 +26,12 @@
NoSymbol,
QrCode,
Link,
ArrowDownOnSquare,
Camera,
CheckBadge,
} from "svelte-heros-v2";
import { onMount, tick } from "svelte";
import { onDestroy, onMount, tick } from "svelte";
import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte";
import { t } from "svelte-i18n";
import {
type Html5QrcodeResult,
type Html5QrcodeScanner,
} from "html5-qrcode";
import {
close_active_wallet,
@ -44,6 +39,7 @@
active_wallet,
display_error,
online,
scanned_qr_code,
} from "../store";
import { default as ng } from "../api";
@ -55,47 +51,36 @@
let nonActiveClass =
"flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700";
let top;
let container: HTMLElement;
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;
// TODO: do that only when needed // generated_text_code = await ng.wallet_export_get_textcode($active_session.session_id);
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?
}
let scanner_state: null | "scanned" | "success" = null;
async function scrollToTop() {
await tick();
top.scrollIntoView();
container.scrollIntoView();
}
onMount(async () => {
if (!$active_session) {
push("#/");
} else {
await scrollToTop();
return;
}
if ($scanned_qr_code) {
sub_menu = "scan_qr";
on_qr_scanned($scanned_qr_code);
scanned_qr_code.set("");
}
await scrollToTop();
});
function open_scan_menu() {
sub_menu = "scan_qr";
load_qr_scanner_lib();
}
async function open_gen_menu() {
@ -103,70 +88,48 @@
generation_state = null;
}
async function gen_qr() {
generation_state = "loading"; // TODO: @niko = await ng.generate_export_qr();
// ToRemove:
setTimeout(() => {
function open_textcode_menu() {
sub_menu = "text_code";
scanner_state = null;
}
async function generate_qr_code() {
generation_state = "loading";
generated_qr = await ng.wallet_export_get_qrcode(
$active_session.session_id,
container.clientWidth
);
generation_state = "generated";
generated_qr = "dummy";
// TODO: generated_qr = await ng.wallet_export_get_qrcode($active_session.session_id, 250);
}, 3000);
}
async function on_qr_scanned(text: string) {
scanned_qr = text;
// TODO: API calls for synchronization @niko
//
// example :
// try {
// await ng.wallet_export_rendezvous($active_session.session_id, text);
// } catch (e) {
// console.error(e);
// }
// ToRemove:
setTimeout(() => {
scan_successful = true;
}, 2_000);
try {
await ng.wallet_export_rendezvous($active_session.session_id, text);
scanner_state = "success";
} catch (e) {
error = e;
}
}
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] },
false
);
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);
push("#/wallet/scanqr");
}
function close_scanner() {
scanner_open = false;
if (html5QrcodeScanner) html5QrcodeScanner.clear();
html5QrcodeScanner = null;
async function generate_text_code() {
generation_state = "loading";
generated_text_code = await ng.wallet_export_get_textcode(
$active_session.session_id
);
generation_state = "generated";
}
function to_main_menu() {
cancel_wallet_transfers();
sub_menu = null;
generated_qr = "loading";
generated_qr = undefined;
generated_text_code = null;
generation_state = null;
}
let downloading = false;
@ -208,11 +171,19 @@
// await ng.wallet_remove($active_wallet.id);
close_active_wallet();
}
async function cancel_wallet_transfers() {
// TODO
}
onDestroy(() => {
cancel_wallet_transfers();
});
</script>
<CenteredLayout>
<div class="container3" bind:this={top}>
<div class="row mb-20">
<div class="container3" bind:this={container}>
<div class="row mb-10">
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
@ -358,6 +329,25 @@
</li>
{/if}
<!-- Copy Wallet TextCode -->
<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={open_textcode_menu}
on:click={open_textcode_menu}
>
<div>
<Link
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.create_text_code")}</span
>
</li>
<!-- Remove Wallet -->
<li
tabindex="0"
@ -397,39 +387,6 @@
</button>
</div>
</Modal>
<!-- TODO: Copy Wallet Link -->
{#if false}
<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"
>
<div>
<Link
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.login.copy_wallet_link")}</span>
</li>
{/if}
<!-- TODO: Save to Device -->
{#if false}
<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"
>
<!-- TODO: Same as with the trash icon, this is not same-sized as the others. -->
<ArrowDownOnSquare
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("pages.login.keep_wallet")}</span>
</li>
{/if}
</SidebarGroup>
{:else if sub_menu === "scan_qr"}
<SidebarGroup ulClass="space-y-2" role="menu">
@ -467,49 +424,33 @@
</li>
{/if}
{#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}
{#if !scanner_state}
<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"
>
{$t("pages.wallet_info.scan_qr.scan_btn")}
</Button>
{:else if scanner_state === "scanned"}
<li class="">
<Spinner class="mt-4 mb-2" />
<div>
{@html $t("pages.wallet_info.scan_qr.syncing")}...
<br />
<br />
{scanned_qr}
{scanned_qr_code}
</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}
<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")}...
{:else if scanner_state === "success"}
<li class="text-green-800 flex flex-col items-center">
<div class="mt-4">
<CheckBadge color="green" size="3em" />
</div>
</Modal>
<div class="mt-4">
{@html $t("pages.wallet_info.scan_qr.scan_successful")}
</div>
</li>
{/if}
</SidebarGroup>
<!-- Generate QR-Code screen -->
@ -540,7 +481,7 @@
<li class="text-left">
{@html $t("pages.wallet_info.gen_qr.notes")}
<br />
{@html $t("wallet_sync.transfer_notice")}
{@html $t("wallet_sync.server_transfer_notice")}
</li>
<!-- Warning if offline -->
@ -554,7 +495,7 @@
{#if !generated_qr || generation_state === "loading"}
<Button
on:click={gen_qr}
on:click={generate_qr_code}
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"
>
@ -565,26 +506,73 @@
</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}
<div class="w-full">
{@html generated_qr}
</div>
{/if}
</SidebarGroup>
{:else if sub_menu === "text_code"}
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">
{$t("pages.wallet_info.gen_text_code.title")}
</h2>
</li>
<!--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"
/-->
<!-- 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>
<!-- Warning to better use QR codes or wallet downloads -->
<div class="text-left">
<Alert color="yellow">
{@html $t("wallet_sync.textcode.usage_warning")}
</Alert>
</div>
<!-- Warning if offline -->
{#if !$online}
<li class="text-left">
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
</li>
{/if}
{#if generation_state !== "generated"}
<Button
on:click={generate_text_code}
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_text_code.gen_btn")}
</Button>
{:else}
<!-- TextCode Code -->
<div>
<textarea
rows="6"
value={generated_text_code}
class="col-span-6 pr-11 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500"
readonly
/>
</div>
{/if}
</SidebarGroup>
{:else if sub_menu === "text_code"}
TODO: Export with text code
{/if}
</SidebarWrapper>
</Sidebar>

@ -36,7 +36,7 @@
display_error,
wallet_from_import,
} from "../store";
import { QrCode } from "svelte-heros-v2";
import { CheckBadge, QrCode } from "svelte-heros-v2";
let tauri_platform = import.meta.env.TAURI_PLATFORM;
@ -99,37 +99,30 @@
// Coming from the import Wallet with QR / TextCode ...
if ($wallet_from_import) {
wallet = wallet_from_import;
wallet = $wallet_from_import;
importing = true;
wallet_from_import.set(null);
// TODO: Show component: "We got wallet from other device. Please log in to your wallet, to import the device."
}
// <!-- TODO: QR / TextCode 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>
// Sample textcode AABAOAAAAHNb4y7hdWADqFWDgER3J0xvD3K5D9pZ1wd7Bja4c9cWAOFNpmUIZOFRro0UIpZWr5Ah8U7PlRFe1GFZSKuIextFAA8A45zZUJmUPhfdBrcho1vYPfgda0BAgIT1qjzgEkBQAA"
// TODO: display with QRcode with :
// {#if qrcode}
// {@html qrcode[0]}
// {/if}
});
function loggedin() {
step = "loggedin";
push("#/");
}
function start_login_from_import() {
// Login button clicked, `wallet` is was set onMount.
// Unset, to show login screen.
wallet_from_import.set(null);
}
onDestroy(() => {
if (wallets_unsub) wallets_unsub();
if (opened_wallets_unsub) opened_wallets_unsub();
if (active_wallet_unsub) active_wallet_unsub();
wallet_from_import.set(null);
});
async function gotError(event) {
importing = false;
@ -257,6 +250,50 @@
{$t("buttons.start_over")}
</button>
</div>
{:else if $wallet_from_import}
<!-- Imported a wallet -->
<!-- Title -->
<div>
<h2 class="text-xl mb-6">
{$t("pages.wallet_login.from_import.title")}
</h2>
</div>
<CheckBadge class="w-full h-6" />
<div>
{@html $t("pages.wallet_login.from_import.description")}
</div>
<!-- Show wallet security image and phrase. -->
<!--
<div
class="wallet-box"
role="button"
tabindex="0"
title={$wallet_from_import[0]}
>
<span class="securitytxt"
>{$wallet_from_import[1].wallet.V0.content.security_txt}
</span>
<img
alt={$wallet_from_import[1].wallet.V0.content.security_txt}
class="securityimg"
src={convert_img_to_url(
$wallet_from_import[1].wallet.V0.content.security_img
)}
/>
</div>
-->
<div>
<button
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"
on:click={start_login_from_import}
>
{$t("buttons.login")}
</button>
</div>
{:else if wallet}
<Login
{wallet}

@ -1,14 +1,8 @@
<script lang="ts">
import { t } from "svelte-i18n";
import { Alert, Modal, Spinner } from "flowbite-svelte";
import {
ArrowLeft,
ArrowRightCircle,
Camera,
CheckBadge,
QrCode,
} from "svelte-heros-v2";
import { onMount } from "svelte";
import { ArrowLeft, Camera, QrCode } from "svelte-heros-v2";
import { onDestroy, onMount } from "svelte";
import { push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { wallet_from_import, scanned_qr_code, display_error } from "../store";
@ -16,7 +10,7 @@
// <a href="/wallet/scanqr" use:link>
let top;
let top: HTMLElement;
const tauri_platform: string | undefined = import.meta.env.TAURI_PLATFORM;
const use_native_cam =
tauri_platform === "ios" || tauri_platform === "android";
@ -31,6 +25,7 @@
let gen_state: "before_start" | "generating" | "generated" = "before_start";
let qr_code_html: string | undefined = undefined;
let rendezvous_code;
const open_scanner = () => {
push("#/wallet/scanqr");
@ -72,12 +67,15 @@
async function generate_qr() {
gen_state = "generating";
try {
const [qr_code_el, code] = await ng.wallet_import_rendezvous(300);
const [qr_code_el, code] = await ng.wallet_import_rendezvous(
top.clientWidth
);
rendezvous_code = code;
qr_code_html = qr_code_el;
gen_state = "generated";
const imported_wallet = await ng.wallet_import_from_code(code);
// Login with imported wallet.
wallet_from_import.set(imported_wallet);
// Login in with imported wallet.
push("#/wallet/login");
} catch (e) {
error = e;
@ -99,6 +97,11 @@
}
scrollToTop();
});
onDestroy(() => {
if (rendezvous_code) {
// TODO: Destroy
}
});
</script>
<CenteredLayout>
@ -152,7 +155,7 @@
<div class="text-left text-sm">
{@html $t("pages.wallet_login_qr.gen.description")}
<br />
{@html $t("wallet_sync.transfer_notice")}
{@html $t("wallet_sync.server_transfer_notice")}
</div>
{:else if gen_state === "generating"}
<div>

@ -61,7 +61,7 @@
<div class="text-left text-sm mb-4">
{@html $t("pages.wallet_login_textcode.description")}
<br />
{@html $t("wallet_sync.transfer_notice")}
{@html $t("wallet_sync.server_transfer_notice")}
</div>
<!-- TextCode Input -->

Loading…
Cancel
Save