parent
7331289e0f
commit
6aa87f1467
@ -0,0 +1,319 @@ |
||||
<!-- |
||||
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
// All rights reserved. |
||||
// Licensed under the Apache License, Version 2.0 |
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> |
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
||||
// at your option. All files in the project carrying such |
||||
// notice may not be copied, modified, or distributed except |
||||
// according to those terms. |
||||
--> |
||||
|
||||
<script lang="ts"> |
||||
import { t, format } from "svelte-i18n"; |
||||
import { Alert, Spinner } from "flowbite-svelte"; |
||||
import { |
||||
ArrowLeft, |
||||
ExclamationTriangle, |
||||
Cloud, |
||||
ChevronDoubleRight, |
||||
} from "svelte-heros-v2"; |
||||
import { onDestroy, onMount, tick } from "svelte"; |
||||
import { push } from "svelte-spa-router"; |
||||
import CenteredLayout from "../lib/CenteredLayout.svelte"; |
||||
import PasswordInput from "../lib/components/PasswordInput.svelte"; |
||||
import { wallet_from_import, display_error } from "../store"; |
||||
import ng from "../api"; |
||||
|
||||
let top: HTMLElement; |
||||
|
||||
const set_online = () => { connected = true; }; |
||||
const set_offline = () => { connected = false; }; |
||||
|
||||
let error; |
||||
let connected = true; |
||||
let tauri_platform = import.meta.env.TAURI_PLATFORM; |
||||
let pre_invitation = false; |
||||
let domain = undefined; |
||||
let for_opaque = undefined ; |
||||
let state: "username" | "password" | "connecting" = "username"; |
||||
|
||||
function scrollToTop() { |
||||
top.scrollIntoView(); |
||||
} |
||||
|
||||
onMount(async () => { |
||||
connected = window.navigator.onLine; |
||||
window.addEventListener("offline", set_offline); |
||||
window.addEventListener("online", set_online); |
||||
state = "username"; |
||||
username = ""; |
||||
if (!tauri_platform) { |
||||
let res = await ng.get_local_bootstrap_and_domain( |
||||
import.meta.env.PROD ? location.href : "http://localhost:14400" |
||||
); |
||||
pre_invitation = res[0]; |
||||
domain = res[1]; |
||||
console.log("pre_invitation", pre_invitation, domain); |
||||
} |
||||
scrollToTop(); |
||||
await tick(); |
||||
username_input.focus(); |
||||
}); |
||||
onDestroy(() => { |
||||
window.removeEventListener("offline", set_offline); |
||||
window.removeEventListener("online", set_online); |
||||
}); |
||||
|
||||
let password = ""; |
||||
const validate_password = async () => { |
||||
|
||||
console.log(password, for_opaque); |
||||
|
||||
} |
||||
let username_input; |
||||
let username = ""; |
||||
let redirect = undefined; |
||||
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/i; |
||||
const usernameRegex = /^[a-zA-Z_]+[a-zA-Z0-9_-]*$/; |
||||
const validate_username = async (e: any) => { |
||||
if (!e || e.key == "Enter" || e.keyCode == 13) { |
||||
username_input.blur(); |
||||
if (pre_invitation) { |
||||
if (!domain) { |
||||
let u = username.trim(); |
||||
if (u.includes("@")) { |
||||
syntax_error = $t("pages.wallet_login_username.error.nodomainplease"); |
||||
} else if (!usernameRegex.test(u)) { |
||||
syntax_error = $t("pages.wallet_login_username.error.username"); |
||||
} else { |
||||
for_opaque = pre_invitation.V0.bootstrap; |
||||
for_opaque.username = u; |
||||
next(); |
||||
} |
||||
} else { |
||||
let parts = username.trim().split("@"); |
||||
if (!usernameRegex.test(parts[0])) { |
||||
syntax_error = $t("pages.wallet_login_username.error.username"); |
||||
} |
||||
else if ( parts[1] === domain || !parts[1] ) { |
||||
username = parts[0]; |
||||
for_opaque = pre_invitation.V0.bootstrap; |
||||
for_opaque.username = username; |
||||
next(); |
||||
} else { |
||||
// testing that domain is valid |
||||
if (!domainRegex.test(parts[1])) { |
||||
syntax_error = $t("pages.wallet_login_username.error.invalid_domain"); |
||||
} else { |
||||
redirect = `https://${parts[1]}/#/wallet/username?u=${parts[0]}`; |
||||
syntax_error = $t("pages.wallet_login_username.error.need_redirect"); |
||||
// TODO: when receiving a ?u=... after fetching it with opaque, if the wallet is already present locally, dont show an error, just log in with the username/password. |
||||
} |
||||
} |
||||
} |
||||
} else if (tauri_platform) { |
||||
let parts = username.trim().split("@"); |
||||
if (!usernameRegex.test(parts[0])) { |
||||
syntax_error = $t("pages.wallet_login_username.error.username"); |
||||
} |
||||
else if (!parts[1]) { |
||||
syntax_error = $t("pages.wallet_login_username.error.mandatory_domain"); |
||||
} else { |
||||
// testing that domain is valid |
||||
if (!domainRegex.test(parts[1])) { |
||||
syntax_error = $t("pages.wallet_login_username.error.invalid_domain"); |
||||
} else { |
||||
// fetching the .ng_bootstrap of the domain |
||||
state = "connecting"; |
||||
try { |
||||
let bootstrap_info = await ng.retrieve_ng_bootstrap(`https://${parts[1]}`); |
||||
for_opaque = bootstrap_info.V0.bootstrap; |
||||
for_opaque.username = parts[0]; |
||||
// do opaque with that |
||||
next(); |
||||
} catch (e) { |
||||
error = e; |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
syntax_error = "your local broker cannot be found (unexpected error)"; |
||||
} |
||||
} |
||||
}; |
||||
let placeholder = ""; |
||||
$: placeholder = pre_invitation ? domain ? $format("pages.wallet_login_username.username_placeholder_without_domain", { |
||||
values: { domain }}) : $t("pages.wallet_login_username.username_placeholder_without_at") : |
||||
$t("pages.wallet_login_username.username_placeholder_domain"); |
||||
let warning = ""; |
||||
$: warning = domain && username.trim().endsWith("@"+domain) && $format("pages.wallet_login_username.warning.nospecificdomainplease", { |
||||
values: { domain }}) || pre_invitation && !domain && username.includes("@") |
||||
&& $t("pages.wallet_login_username.warning.nodomainplease") || ""; |
||||
const next = () => { |
||||
for_opaque.username = for_opaque.username.toLowerCase(); |
||||
state = "password"; |
||||
} |
||||
|
||||
let syntax_error = ""; |
||||
|
||||
</script> |
||||
|
||||
<CenteredLayout> |
||||
<div class="container3" bind:this={top}> |
||||
<div |
||||
class="flex flex-col justify-center max-w-md mb-5 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800" |
||||
> |
||||
<!-- Title --> |
||||
<div class="mx-6"> |
||||
<h2 class="text-xl mb-6">{$t("pages.wallet_login_username.title")}</h2> |
||||
</div> |
||||
|
||||
{#if !connected} |
||||
<!-- Warning, if offline --> |
||||
<div class="text-left mx-6"> |
||||
<Alert color="red"> |
||||
{@html $t("wallet_sync.offline_warning")} |
||||
</Alert> |
||||
<Alert color="blue" class="mt-4"> |
||||
{@html $t("pages.wallet_login.offline_advice")} |
||||
</Alert> |
||||
<!-- Go Back --> |
||||
<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 |
||||
> |
||||
</div> |
||||
{:else 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> |
||||
<button |
||||
on:click={() => window.history.go(-1)} |
||||
class="mt-8 mr-2 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 |
||||
> |
||||
</div> |
||||
{:else} |
||||
{#if state == "username"} |
||||
<div class="mx-6"> |
||||
<div class="mx-auto"> |
||||
<div class="my-4 mx-1 mt-4"> |
||||
{#if syntax_error} |
||||
<Alert color="red" class="mb-3"> |
||||
{syntax_error} |
||||
</Alert> |
||||
{/if} |
||||
{#if warning} |
||||
<Alert color="blue" class="mb-3"> |
||||
{warning} |
||||
</Alert> |
||||
{/if} |
||||
{$t("pages.wallet_login_username.username")} : |
||||
<input |
||||
bind:this={username_input} |
||||
class="w-[240px] mr-0" |
||||
id="username_input" |
||||
placeholder={placeholder} |
||||
bind:value={username} |
||||
on:keypress={validate_username} |
||||
on:focus={()=>{syntax_error="";redirect=undefined;}} |
||||
/> |
||||
<!-- Go Back --> |
||||
<button |
||||
on:click={() => {if (redirect) {username_input.focus();} else {window.history.go(-1)}}} |
||||
class="mt-8 mr-2 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 redirect} |
||||
<button |
||||
on:click={() => {window.location.href = redirect;}} |
||||
class="mt-4 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" |
||||
> |
||||
<ChevronDoubleRight |
||||
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_username.redirect")} |
||||
</button> |
||||
{:else} |
||||
<button |
||||
on:click={() => validate_username(null)} |
||||
class="mt-4 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" |
||||
> |
||||
<ChevronDoubleRight |
||||
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_username.next")} |
||||
</button> |
||||
{/if} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{:else if state === "password"} |
||||
<div class="mx-6"> |
||||
<div class="mx-auto"> |
||||
<div class="my-4 mx-1 mt-4"> |
||||
{$t("pages.wallet_login_username.password")} : |
||||
<!-- <input |
||||
bind:this={password_input} |
||||
class="w-[240px] mr-0" |
||||
id="password_input" |
||||
bind:value={password} |
||||
on:keypress={validate_password} |
||||
/> --> |
||||
<PasswordInput |
||||
id="password_input" |
||||
placeholder={$t("pages.wallet_login_username.password_placeholder")} |
||||
bind:value={password} |
||||
on:enter={validate_password} |
||||
classNameToggle="right-[-26px]" |
||||
className="w-[240px] bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block 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" |
||||
/> |
||||
<!-- Go Back --> |
||||
<button |
||||
on:click={async () => {state = "username";for_opaque = undefined; await tick(); username_input.focus();}} |
||||
class="mt-8 mr-1 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 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white" |
||||
/>{$t("buttons.back")}</button |
||||
> |
||||
<button |
||||
on:click={validate_password} |
||||
class="mt-4 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" |
||||
> |
||||
<ChevronDoubleRight |
||||
tabindex="-1" |
||||
class="w-8 h-8 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white" |
||||
/> |
||||
{$t("pages.wallet_login_username.connect")} |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{:else if state === "connecting"} |
||||
<div> |
||||
<Spinner class="w-full" /> |
||||
</div> |
||||
{/if} |
||||
{/if} |
||||
</div> |
||||
</div> |
||||
</CenteredLayout> |
Loading…
Reference in new issue