anastasis-webui: ui tweaks
This commit is contained in:
parent
6a0c5263bb
commit
4dd5b75cfa
@ -1,53 +1,62 @@
|
|||||||
import { AuthMethod } from "anastasis-core";
|
import { AuthMethod, ReducerStateBackup } from "anastasis-core";
|
||||||
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import { authMethods, AuthMethodSetupProps, AuthMethodWithRemove, KnownAuthMethods } from "./authMethod";
|
import {
|
||||||
|
authMethods,
|
||||||
|
AuthMethodSetupProps,
|
||||||
|
AuthMethodWithRemove,
|
||||||
|
KnownAuthMethods,
|
||||||
|
} from "./authMethod";
|
||||||
import { AnastasisClientFrame } from "./index";
|
import { AnastasisClientFrame } from "./index";
|
||||||
|
|
||||||
|
const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;
|
||||||
|
|
||||||
const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>
|
|
||||||
|
|
||||||
export function AuthenticationEditorScreen(): VNode {
|
export function AuthenticationEditorScreen(): VNode {
|
||||||
const [noProvidersAck, setNoProvidersAck] = useState(false)
|
const [noProvidersAck, setNoProvidersAck] = useState(false);
|
||||||
const [selectedMethod, setSelectedMethod] = useState<KnownAuthMethods | undefined>(undefined);
|
const [selectedMethod, setSelectedMethod] = useState<
|
||||||
|
KnownAuthMethods | undefined
|
||||||
|
>(undefined);
|
||||||
|
const [tooFewAuths, setTooFewAuths] = useState(false);
|
||||||
// const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined)
|
// const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined)
|
||||||
|
const reducer = useAnastasisContext();
|
||||||
const reducer = useAnastasisContext()
|
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return <div>no reducer in context</div>
|
return <div>no reducer in context</div>;
|
||||||
}
|
}
|
||||||
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
|
if (
|
||||||
return <div>invalid state</div>
|
!reducer.currentReducerState ||
|
||||||
|
reducer.currentReducerState.backup_state === undefined
|
||||||
|
) {
|
||||||
|
return <div>invalid state</div>;
|
||||||
}
|
}
|
||||||
const configuredAuthMethods: AuthMethod[] = reducer.currentReducerState.authentication_methods ?? [];
|
const configuredAuthMethods: AuthMethod[] =
|
||||||
|
reducer.currentReducerState.authentication_methods ?? [];
|
||||||
const haveMethodsConfigured = configuredAuthMethods.length > 0;
|
const haveMethodsConfigured = configuredAuthMethods.length > 0;
|
||||||
|
|
||||||
function removeByIndex(index: number): void {
|
function removeByIndex(index: number): void {
|
||||||
if (reducer) reducer.transition("delete_authentication", {
|
if (reducer)
|
||||||
authentication_method: index,
|
reducer.transition("delete_authentication", {
|
||||||
})
|
authentication_method: index,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const camByType: { [s: string]: AuthMethodWithRemove[] } = {}
|
const camByType: { [s: string]: AuthMethodWithRemove[] } = {};
|
||||||
for (let index = 0; index < configuredAuthMethods.length; index++) {
|
for (let index = 0; index < configuredAuthMethods.length; index++) {
|
||||||
const cam = {
|
const cam = {
|
||||||
...configuredAuthMethods[index],
|
...configuredAuthMethods[index],
|
||||||
remove: () => removeByIndex(index)
|
remove: () => removeByIndex(index),
|
||||||
}
|
};
|
||||||
const prevValue = camByType[cam.type] || []
|
const prevValue = camByType[cam.type] || [];
|
||||||
prevValue.push(cam)
|
prevValue.push(cam);
|
||||||
camByType[cam.type] = prevValue;
|
camByType[cam.type] = prevValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const providers = reducer.currentReducerState.authentication_providers!;
|
const providers = reducer.currentReducerState.authentication_providers!;
|
||||||
|
|
||||||
const authAvailableSet = new Set<string>();
|
const authAvailableSet = new Set<string>();
|
||||||
for (const provKey of Object.keys(providers)) {
|
for (const provKey of Object.keys(providers)) {
|
||||||
const p = providers[provKey];
|
const p = providers[provKey];
|
||||||
if ("http_status" in p && (!("error_code" in p)) && p.methods) {
|
if ("http_status" in p && !("error_code" in p) && p.methods) {
|
||||||
for (const meth of p.methods) {
|
for (const meth of p.methods) {
|
||||||
authAvailableSet.add(meth.type);
|
authAvailableSet.add(meth.type);
|
||||||
}
|
}
|
||||||
@ -61,102 +70,147 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
setSelectedMethod(undefined);
|
setSelectedMethod(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AuthSetup = authMethods[selectedMethod].setup ?? AuthMethodNotImplemented;
|
const AuthSetup =
|
||||||
return (<Fragment>
|
authMethods[selectedMethod].setup ?? AuthMethodNotImplemented;
|
||||||
<AuthSetup
|
return (
|
||||||
cancel={cancel}
|
<Fragment>
|
||||||
configured={camByType[selectedMethod] || []}
|
<AuthSetup
|
||||||
addAuthMethod={addMethod}
|
cancel={cancel}
|
||||||
method={selectedMethod} />
|
configured={camByType[selectedMethod] || []}
|
||||||
|
addAuthMethod={addMethod}
|
||||||
|
method={selectedMethod}
|
||||||
|
/>
|
||||||
|
|
||||||
{!authAvailableSet.has(selectedMethod) && <ConfirmModal active
|
{!authAvailableSet.has(selectedMethod) && (
|
||||||
onCancel={cancel} description="No providers founds" label="Add a provider manually"
|
<ConfirmModal
|
||||||
onConfirm={() => {
|
active
|
||||||
null
|
onCancel={cancel}
|
||||||
}}
|
description="No providers founds"
|
||||||
>
|
label="Add a provider manually"
|
||||||
We have found no trusted cloud providers for your recovery secret. You can add a provider manually.
|
onConfirm={() => {
|
||||||
To add a provider you must know the provider URL (e.g. https://provider.com)
|
null;
|
||||||
<p>
|
}}
|
||||||
<a>More about cloud providers</a>
|
>
|
||||||
</p>
|
<p>
|
||||||
</ConfirmModal>}
|
We have found no Anastasis providers that support this
|
||||||
|
authentication method. You can add a provider manually. To add a
|
||||||
</Fragment>
|
provider you must know the provider URL (e.g.
|
||||||
|
https://provider.com)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a>Learn more about Anastasis providers</a>
|
||||||
|
</p>
|
||||||
|
</ConfirmModal>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MethodButton(props: { method: KnownAuthMethods }): VNode {
|
function MethodButton(props: { method: KnownAuthMethods }): VNode {
|
||||||
if (authMethods[props.method].skip) return <div />
|
if (authMethods[props.method].skip) return <div />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<button
|
<button
|
||||||
style={{ justifyContent: 'space-between' }}
|
style={{ justifyContent: "space-between" }}
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedMethod(props.method);
|
setSelectedMethod(props.method);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: "flex" }}>
|
||||||
<span class="icon ">
|
<span class="icon ">{authMethods[props.method].icon}</span>
|
||||||
{authMethods[props.method].icon}
|
{authAvailableSet.has(props.method) ? (
|
||||||
</span>
|
<span>Add a {authMethods[props.method].label} challenge</span>
|
||||||
{authAvailableSet.has(props.method) ?
|
) : (
|
||||||
<span>
|
<span>Add a {authMethods[props.method].label} provider</span>
|
||||||
Add a {authMethods[props.method].label} challenge
|
)}
|
||||||
</span> :
|
|
||||||
<span>
|
|
||||||
Add a {authMethods[props.method].label} provider
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
{!authAvailableSet.has(props.method) &&
|
{!authAvailableSet.has(props.method) && (
|
||||||
<span class="icon has-text-danger" >
|
<span class="icon has-text-danger">
|
||||||
<i class="mdi mdi-exclamation-thick" />
|
<i class="mdi mdi-exclamation-thick" />
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
{camByType[props.method] &&
|
{camByType[props.method] && (
|
||||||
<span class="tag is-info" >
|
<span class="tag is-info">{camByType[props.method].length}</span>
|
||||||
{camByType[props.method].length}
|
)}
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const errors = !haveMethodsConfigured ? "There is not enough authentication methods." : undefined;
|
const errors = !haveMethodsConfigured
|
||||||
|
? "There is not enough authentication methods."
|
||||||
|
: undefined;
|
||||||
|
const handleNext = async () => {
|
||||||
|
const st = reducer.currentReducerState as ReducerStateBackup;
|
||||||
|
if ((st.authentication_methods ?? []).length <= 2) {
|
||||||
|
setTooFewAuths(true);
|
||||||
|
} else {
|
||||||
|
await reducer.transition("next", {});
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame title="Backup: Configure Authentication Methods" hideNext={errors}>
|
<AnastasisClientFrame
|
||||||
|
title="Backup: Configure Authentication Methods"
|
||||||
|
hideNext={errors}
|
||||||
|
onNext={handleNext}
|
||||||
|
>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<div>
|
<div>
|
||||||
{getKeys(authMethods).map(method => <MethodButton key={method} method={method} />)}
|
{getKeys(authMethods).map((method) => (
|
||||||
|
<MethodButton key={method} method={method} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
{authAvailableSet.size === 0 && <ConfirmModal active={!noProvidersAck}
|
{tooFewAuths ? (
|
||||||
onCancel={() => setNoProvidersAck(true)} description="No providers founds" label="Add a provider manually"
|
<ConfirmModal
|
||||||
onConfirm={() => {
|
active={tooFewAuths}
|
||||||
null
|
onCancel={() => setTooFewAuths(false)}
|
||||||
}}
|
description="Too few auth methods configured"
|
||||||
>
|
label="Proceed anyway"
|
||||||
We have found no trusted cloud providers for your recovery secret. You can add a provider manually.
|
onConfirm={() => reducer.transition("next", {})}
|
||||||
To add a provider you must know the provider URL (e.g. https://provider.com)
|
>
|
||||||
<p>
|
You have selected fewer than three authentication methods. We
|
||||||
<a>More about cloud providers</a>
|
recommend that you add at least three.
|
||||||
</p>
|
</ConfirmModal>
|
||||||
</ConfirmModal>}
|
) : null}
|
||||||
|
{authAvailableSet.size === 0 && (
|
||||||
|
<ConfirmModal
|
||||||
|
active={!noProvidersAck}
|
||||||
|
onCancel={() => setNoProvidersAck(true)}
|
||||||
|
description="No providers founds"
|
||||||
|
label="Add a provider manually"
|
||||||
|
onConfirm={() => {
|
||||||
|
null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
We have found no Anastasis providers for your chosen country /
|
||||||
|
currency. You can add a providers manually. To add a provider
|
||||||
|
you must know the provider URL (e.g. https://provider.com)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a>Learn more about Anastasis providers</a>
|
||||||
|
</p>
|
||||||
|
</ConfirmModal>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<p class="block">
|
<p class="block">
|
||||||
When recovering your wallet, you will be asked to verify your identity via the methods you configure here.
|
When recovering your wallet, you will be asked to verify your
|
||||||
The list of authentication method is defined by the backup provider list.
|
identity via the methods you configure here. The list of
|
||||||
|
authentication method is defined by the backup provider list.
|
||||||
</p>
|
</p>
|
||||||
<p class="block">
|
<p class="block">
|
||||||
<button class="button is-info">Manage the backup provider's list</button>
|
<button class="button is-info">
|
||||||
|
Manage the backup provider's list
|
||||||
|
</button>
|
||||||
</p>
|
</p>
|
||||||
{authAvailableSet.size > 0 && <p class="block">
|
{authAvailableSet.size > 0 && (
|
||||||
We couldn't find provider for some of the authentication methods.
|
<p class="block">
|
||||||
</p>}
|
We couldn't find provider for some of the authentication methods.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
@ -172,30 +226,54 @@ function AuthMethodNotImplemented(props: AuthMethodSetupProps): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ConfirmModal({
|
||||||
function ConfirmModal({ active, description, onCancel, onConfirm, children, danger, disabled, label = 'Confirm' }: Props): VNode {
|
active,
|
||||||
return <div class={active ? "modal is-active" : "modal"}>
|
description,
|
||||||
<div class="modal-background " onClick={onCancel} />
|
onCancel,
|
||||||
<div class="modal-card" style={{ maxWidth: 700 }}>
|
onConfirm,
|
||||||
<header class="modal-card-head">
|
children,
|
||||||
{!description ? null : <p class="modal-card-title"><b>{description}</b></p>}
|
danger,
|
||||||
<button class="delete " aria-label="close" onClick={onCancel} />
|
disabled,
|
||||||
</header>
|
label = "Confirm",
|
||||||
<section class="modal-card-body">
|
}: ConfirmModelProps): VNode {
|
||||||
{children}
|
return (
|
||||||
</section>
|
<div class={active ? "modal is-active" : "modal"}>
|
||||||
<footer class="modal-card-foot">
|
<div class="modal-background " onClick={onCancel} />
|
||||||
<button class="button" onClick={onCancel} >Dismiss</button>
|
<div class="modal-card" style={{ maxWidth: 700 }}>
|
||||||
<div class="buttons is-right" style={{ width: '100%' }}>
|
<header class="modal-card-head">
|
||||||
<button class={danger ? "button is-danger " : "button is-info "} disabled={disabled} onClick={onConfirm} >{label}</button>
|
{!description ? null : (
|
||||||
</div>
|
<p class="modal-card-title">
|
||||||
</footer>
|
<b>{description}</b>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<button class="delete " aria-label="close" onClick={onCancel} />
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body">{children}</section>
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<button class="button" onClick={onCancel}>
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
<div class="buttons is-right" style={{ width: "100%" }}>
|
||||||
|
<button
|
||||||
|
class={danger ? "button is-danger " : "button is-info "}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="modal-close is-large "
|
||||||
|
aria-label="close"
|
||||||
|
onClick={onCancel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button class="modal-close is-large " aria-label="close" onClick={onCancel} />
|
);
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface ConfirmModelProps {
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
description?: string;
|
description?: string;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
|
@ -32,7 +32,7 @@ export function ContinentSelectionScreen(): VNode {
|
|||||||
const theContinent = reducer.currentReducerState.selected_continent || "";
|
const theContinent = reducer.currentReducerState.selected_continent || "";
|
||||||
// const cc = reducer.currentReducerState.selected_country || "";
|
// const cc = reducer.currentReducerState.selected_country || "";
|
||||||
const theCountry = countryList.find((c) => c.code === countryCode);
|
const theCountry = countryList.find((c) => c.code === countryCode);
|
||||||
const selectCountryAction = () => {
|
const selectCountryAction = async () => {
|
||||||
//selection should be when the select box changes it value
|
//selection should be when the select box changes it value
|
||||||
if (!theCountry) return;
|
if (!theCountry) return;
|
||||||
reducer.transition("select_country", {
|
reducer.transition("select_country", {
|
||||||
@ -123,8 +123,8 @@ export function ContinentSelectionScreen(): VNode {
|
|||||||
</div>
|
</div>
|
||||||
<div class="column is-two-third">
|
<div class="column is-two-third">
|
||||||
<p>
|
<p>
|
||||||
Your choice will help us with asking the right information to unique
|
Your selection will help us ask right information to uniquely
|
||||||
identify you when you want to recover your backed up secrets.
|
identify you when you want to recover your secret again.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Choose the country that issued most of your long-term legal
|
Choose the country that issued most of your long-term legal
|
||||||
|
@ -48,7 +48,7 @@ export function withProcessLabel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AnastasisClientFrameProps {
|
interface AnastasisClientFrameProps {
|
||||||
onNext?(): void;
|
onNext?(): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Override for the "back" functionality.
|
* Override for the "back" functionality.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user