diff --git a/ng-app/src/lib/components/CopyToClipboard.svelte b/ng-app/src/lib/components/CopyToClipboard.svelte index 586a158..3c057cc 100644 --- a/ng-app/src/lib/components/CopyToClipboard.svelte +++ b/ng-app/src/lib/components/CopyToClipboard.svelte @@ -11,7 +11,8 @@ <script lang="ts"> export let value: string = ""; - export let id: string; + export let id: string | undefined = undefined; + export let rows: number = 3; let has_success: boolean = false; @@ -37,10 +38,11 @@ <div class="relative"> <textarea {id} - rows="3" + {rows} style="resize: none;" {value} - 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" + class="col-span-6 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" + class:pr-11={!tauri_platform} disabled readonly /> diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json index fcf671c..d6dc7d7 100644 --- a/ng-app/src/locales/en.json +++ b/ng-app/src/locales/en.json @@ -29,19 +29,20 @@ "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.notes": "Scan the QR-Code on the device that you want to transfer your wallet to.", "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.", + "scan_qr.scan_successful": "Success!<br />Your wallet has been transferred 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.", "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": "Show QR Code", "gen_text_code.title": "Export with TextCode", - "gen_text_code.gen_btn": "Generate TextCode" + "gen_text_code.gen_btn": "Generate TextCode", + "gen_text_code.label": "Your TextCode:" }, "settings": { "title": "Settings" @@ -241,7 +242,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.", + "from_import.description": "Your wallet has been received:", + "from_import.instruction": "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", @@ -256,7 +258,7 @@ "scan.button": "Scan QR-Code", "scan.modal.title": "Scan Wallet QR-Code", "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.", + "gen.description": "To import your wallet from another device, you have to generate 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.", "gen.generated": "Scan this QR-Code from the the other device.", "success_btn": "Continue to Login" }, @@ -349,7 +351,8 @@ "InvalidTarget": "Cannot resolve target.", "ExportWalletTimeOut": "Export of wallet has expired.", "ConnectionError": "Could not connect to the server.", - "IncompatibleQrCode": "You scanned a NextGraph QR-Code that is of the wrong type" + "IncompatibleQrCode": "You scanned a NextGraph QR-Code that is of the wrong type.", + "NotARendezVous": "You scanned an invalid QR-Code." }, "connectivity": { "stopped": "Stopped", diff --git a/ng-app/src/routes/ScanQR.svelte b/ng-app/src/routes/ScanQR.svelte index b577a70..57b1a77 100644 --- a/ng-app/src/routes/ScanQR.svelte +++ b/ng-app/src/routes/ScanQR.svelte @@ -82,7 +82,7 @@ }); </script> -<div class="text-center"> +<div class="text-center max-w-4xl mx-auto"> <div> <h2 class="text-xl mb-6">{$t("pages.scan_qr.scanning")}</h2> </div> diff --git a/ng-app/src/routes/WalletInfo.svelte b/ng-app/src/routes/WalletInfo.svelte index 799147b..01fd3ff 100644 --- a/ng-app/src/routes/WalletInfo.svelte +++ b/ng-app/src/routes/WalletInfo.svelte @@ -28,6 +28,7 @@ Link, Camera, CheckBadge, + ExclamationTriangle, } from "svelte-heros-v2"; import { onDestroy, onMount, tick } from "svelte"; import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte"; @@ -43,8 +44,7 @@ } from "../store"; import { default as ng } from "../api"; - - let WebQRScannerClassPromise: Promise<typeof Html5QrcodeScanner>; + import CopyToClipboard from "../lib/components/CopyToClipboard.svelte"; let tauri_platform = import.meta.env.TAURI_PLATFORM; let error; @@ -55,12 +55,13 @@ let sub_menu: "scan_qr" | "generate_qr" | "text_code" | null = null; - let generation_state: "loading" | "generated" | null = null; - let generated_qr: string | undefined = undefined; + let generation_state: "before_start" | "loading" | "generated" = + "before_start"; + let generated_qr: string | undefined = undefined; let generated_text_code: string | null = null; - let scanner_state: null | "scanned" | "success" = null; + let scanner_state: "before_start" | "scanned" | "success" = "before_start"; async function scrollToTop() { await tick(); @@ -85,19 +86,19 @@ async function open_gen_menu() { sub_menu = "generate_qr"; - generation_state = null; + generation_state = "before_start"; } function open_textcode_menu() { sub_menu = "text_code"; - scanner_state = null; + scanner_state = "before_start"; } async function generate_qr_code() { generation_state = "loading"; generated_qr = await ng.wallet_export_get_qrcode( $active_session.session_id, - container.clientWidth + Math.ceil(container.clientWidth * 0.9) ); generation_state = "generated"; } @@ -129,7 +130,8 @@ sub_menu = null; generated_qr = undefined; generated_text_code = null; - generation_state = null; + generation_state = "before_start"; + scanner_state = "before_start"; } let downloading = false; @@ -399,7 +401,7 @@ <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" + class="mb-2 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} > @@ -410,21 +412,23 @@ <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} + {#if scanner_state === "before_start"} + <!-- NOTES ABOUT QR--> <li class="text-left"> - <Alert color="red"> - {@html $t("wallet_sync.offline_warning")} - </Alert> + {@html $t("pages.wallet_info.scan_qr.notes")} + <br /> + {@html $t("wallet_sync.server_transfer_notice")} </li> - {/if} - {#if !scanner_state} + <!-- Warning if offline --> + {#if !$online} + <li class="text-left"> + <Alert color="red"> + {@html $t("wallet_sync.offline_warning")} + </Alert> + </li> + {/if} + <Button on:click={open_scanner} disabled={false || !$online} @@ -493,17 +497,15 @@ </li> {/if} - {#if !generated_qr || generation_state === "loading"} + {#if generation_state === "before_start"} <Button 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" > - {#if generation_state === "loading"} - <Spinner class="mr-2" size="6" /> - {/if} {$t("pages.wallet_info.gen_qr.gen_button")} </Button> + {:else if generation_state === "loading"} + <Spinner class="mx-auto" size="6" /> {:else} <!-- QR Code --> <div class="w-full"> @@ -534,8 +536,8 @@ <span class="ml-3">{$t("buttons.back")}</span> </li> - <!-- Warning to better use QR codes or wallet downloads --> - <div class="text-left"> + <!-- Warning to prefer QR codes or wallet downloads --> + <div class="text-left my-4"> <Alert color="yellow"> {@html $t("wallet_sync.textcode.usage_warning")} </Alert> @@ -543,35 +545,32 @@ <!-- Warning if offline --> {#if !$online} - <li class="text-left"> + <li class="text-left my-4"> <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} + <div class="mt-4"> + {#if generation_state === "before_start"} + <Button + on:click={generate_text_code} + disabled={!$online} + class="my-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" + > + {$t("pages.wallet_info.gen_text_code.gen_btn")} + </Button> + {:else if generation_state == "loading"} + <Spinner class="mx-auto" size="6" /> + {:else} + <!-- TextCode Code --> + <label>{$t("pages.wallet_info.gen_text_code.label")}</label> + <div> + <CopyToClipboard rows={8} value={generated_text_code} /> + </div> + {/if} + </div> </SidebarGroup> {/if} </SidebarWrapper> @@ -579,48 +578,13 @@ </div> {#if error} <div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> - <svg - class="animate-bounce mt-10 h-16 w-16 mx-auto" - fill="none" - stroke="currentColor" - stroke-width="1.5" - viewBox="0 0 24 24" - xmlns="http://www.w3.org/2000/svg" - aria-hidden="true" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" - /> - </svg> - {#if error == "AlreadyExists"} - <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> - {@html $t("errors.AlreadyExists")} - </p> - <a use:link href="/"> - <button - 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("buttons.login")} - </button> - </a> - {:else} - <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> - {@html $t("errors.error_occurred", { - values: { message: display_error(error) }, - })} - </p> - <a use:link href="/"> - <button - 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("buttons.back_to_homepage")} - </button> - </a> - {/if} + <ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" /> + + <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> + {@html $t("errors.error_occurred", { + values: { message: display_error(error) }, + })} + </p> </div> {/if} </div> diff --git a/ng-app/src/routes/WalletLogin.svelte b/ng-app/src/routes/WalletLogin.svelte index abacd7f..5ddaae7 100644 --- a/ng-app/src/routes/WalletLogin.svelte +++ b/ng-app/src/routes/WalletLogin.svelte @@ -36,7 +36,7 @@ display_error, wallet_from_import, } from "../store"; - import { CheckBadge, QrCode } from "svelte-heros-v2"; + import { CheckBadge, ExclamationTriangle, QrCode } from "svelte-heros-v2"; let tauri_platform = import.meta.env.TAURI_PLATFORM; @@ -113,8 +113,8 @@ } function start_login_from_import() { - // Login button clicked, `wallet` is was set onMount. - // Unset, to show login screen. + // Login button was clicked and `wallet` was set in `onMount`. + // Unset variable from store, to show login screen. wallet_from_import.set(null); } @@ -218,21 +218,7 @@ <CenteredLayout displayFooter={!wallet}> {#if error} <div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> - <svg - class="animate-bounce mt-10 h-16 w-16 mx-auto" - fill="none" - stroke="currentColor" - stroke-width="1.5" - viewBox="0 0 24 24" - xmlns="http://www.w3.org/2000/svg" - aria-hidden="true" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" - /> - </svg> + <ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" /> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> {@html $t("errors.error_occurred", { @@ -260,35 +246,40 @@ </h2> </div> - <CheckBadge class="w-full h-6" /> + <span class="text-green-800"> + <CheckBadge class="w-full mt-4" size="3em" /> - <div> - {@html $t("pages.wallet_login.from_import.description")} - </div> + <div class="mt-4"> + {@html $t("pages.wallet_login.from_import.description")} + </div> + </span> <!-- Show wallet security image and phrase. --> - <!-- <div - class="wallet-box" + class="wallet-box mt-4 mx-auto" role="button" tabindex="0" - title={$wallet_from_import[0]} + on:click={start_login_from_import} + on:keypress={start_login_from_import} > <span class="securitytxt" - >{$wallet_from_import[1].wallet.V0.content.security_txt} + >{$wallet_from_import.V0.content.security_txt} </span> <img - alt={$wallet_from_import[1].wallet.V0.content.security_txt} + alt={$wallet_from_import.V0.content.security_txt} class="securityimg" - src={convert_img_to_url( - $wallet_from_import[1].wallet.V0.content.security_img - )} + src={convert_img_to_url($wallet_from_import.V0.content.security_img)} /> </div> ---> + + <!-- Login to finish import instructions--> + <div class="my-4"> + {@html $t("pages.wallet_login.from_import.instruction")} + </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" + class="mt-4 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")} @@ -313,7 +304,6 @@ class="wallet-box" role="button" tabindex="0" - title={wallet_entry[0]} on:click={() => { select(wallet_entry[0]); }} diff --git a/ng-app/src/routes/WalletLoginQr.svelte b/ng-app/src/routes/WalletLoginQr.svelte index 1e9d361..f3513b2 100644 --- a/ng-app/src/routes/WalletLoginQr.svelte +++ b/ng-app/src/routes/WalletLoginQr.svelte @@ -1,7 +1,12 @@ <script lang="ts"> import { t } from "svelte-i18n"; import { Alert, Modal, Spinner } from "flowbite-svelte"; - import { ArrowLeft, Camera, QrCode } from "svelte-heros-v2"; + import { + ArrowLeft, + Camera, + ExclamationTriangle, + QrCode, + } from "svelte-heros-v2"; import { onDestroy, onMount } from "svelte"; import { push } from "svelte-spa-router"; import CenteredLayout from "../lib/CenteredLayout.svelte"; @@ -41,7 +46,7 @@ } catch { has_camera = false; } - has_camera = false; + login_method = has_camera ? "scan" : "gen"; } else { // TODO: There does not seem to be an API for checking, if the native device @@ -68,7 +73,7 @@ gen_state = "generating"; try { const [qr_code_el, code] = await ng.wallet_import_rendezvous( - top.clientWidth + Math.ceil(top.clientWidth * 0.9) ); rendezvous_code = code; qr_code_html = qr_code_el; @@ -127,11 +132,15 @@ </Alert> </div> {:else if error} - <Alert color="red"> - {@html $t("wallet_sync.error", { - values: { error: display_error(error) }, - })} - </Alert> + <div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> + <ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" /> + + <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> + {@html $t("errors.error_occurred", { + values: { message: display_error(error) }, + })} + </p> + </div> {:else if login_method === "scan"} {#if scan_state === "before_start"} <!-- Scan Mode --> @@ -168,7 +177,7 @@ </div> <!-- Generated QR Code --> - <div> + <div class="my-4 my-auto"> {@html qr_code_html} </div> {/if} diff --git a/ng-app/src/routes/WalletLoginTextCode.svelte b/ng-app/src/routes/WalletLoginTextCode.svelte index a55a4b1..c2f650d 100644 --- a/ng-app/src/routes/WalletLoginTextCode.svelte +++ b/ng-app/src/routes/WalletLoginTextCode.svelte @@ -4,31 +4,33 @@ import { ArrowLeft, ArrowRightCircle, - Camera, - CheckBadge, - QrCode, + CheckCircle, + ExclamationTriangle, } from "svelte-heros-v2"; import CenteredLayout from "../lib/CenteredLayout.svelte"; - import { onMount } from "svelte"; + import { display_error, wallet_from_import } from "../store"; import { push } from "svelte-spa-router"; - import { online, scanned_qr_code } from "../store"; + import ng from "../api"; let top; - let gen_state: - | "before_start" - | "generating" - | "generated" - | "success" - | Error = "before_start"; + let error; + let state: "importing" | null = null; let textcode: string | undefined = undefined; // TODO: Check connectivity to sync service. let connected = true; - const textcode_submit = () => { - scanned_qr_code.set(textcode); - window.history.go(-1); + const textcode_submit = async () => { + state = "importing"; + try { + const imported_wallet = await ng.wallet_import_from_code(textcode); + wallet_from_import.set(imported_wallet); + // Login in with imported wallet. + push("#/wallet/login"); + } catch (e) { + error = e; + } }; </script> @@ -42,7 +44,7 @@ <h2 class="text-xl mb-6">{$t("pages.wallet_login_textcode.title")}</h2> </div> - <div class="text-left"> + <div class="text-left my-4"> <Alert color="yellow"> {@html $t("wallet_sync.textcode.usage_warning")} </Alert> @@ -58,7 +60,7 @@ {/if} <!-- Notes about TextCode entering --> - <div class="text-left text-sm mb-4"> + <div class="text-left text-sm mt-4"> {@html $t("pages.wallet_login_textcode.description")} <br /> {@html $t("wallet_sync.server_transfer_notice")} @@ -68,9 +70,24 @@ <textarea rows="6" bind:value={textcode} - 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" + disabled={state === "importing"} + class="my-4 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" /> + {#if error} + <div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> + <ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" /> + + <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> + {@html $t("errors.error_occurred", { + values: { message: display_error(error) }, + })} + </p> + </div> + {:else if state === "importing"} + <Spinner class="mx-auto" /> + {/if} + <div class="mx-auto"> <!-- Submit Button--> <div class="my-4 mx-1"> @@ -78,8 +95,9 @@ class="mt-4 w-full text-white bg-primary-700 disabled:bg-primary-700/50 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={textcode_submit} disabled={!connected || !textcode} + class:hidden={state === "importing" || error} > - <ArrowRightCircle + <CheckCircle 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" />