changes from feedback

better backup and recovery ending screen
async button on modal and solve challenges
use providers name when possible
This commit is contained in:
Sebastian 2021-11-11 13:22:14 -03:00
parent 0ac7433ea7
commit 4a83e9639d
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
14 changed files with 150 additions and 93 deletions

View File

@ -59,16 +59,16 @@ export function AttributeEntryScreen(): VNode {
<ConfirmModal
active
onCancel={() => setAskUserIfSure(false)}
description="You can not forget what you had entered"
description="The values in the form must be correct"
label="I am sure"
cancelLabel="Wait, I want to check"
onConfirm={() => reducer.transition("enter_user_attributes", {
identity_attributes: attrs,
})}
}).then(() => setAskUserIfSure(false) )}
>
You personal information is used to define the location where your
secret will be safely stored. If you forget what you have entered or
if there is a misspell you will be unable to recover your secret again.
if there is a misspell you will be unable to recover your secret.
</ConfirmModal>
) : null}

View File

@ -49,13 +49,13 @@ export const WithDetails = createExample(TestedComponent, {
...reducerStatesExample.backupFinished,
secret_name: "super_secret",
success_details: {
"http://anastasis.net": {
"https://anastasis.demo.taler.net/": {
policy_expiration: {
t_ms: "never",
},
policy_version: 0,
},
"http://taler.net": {
"https://kudos.demo.anastasis.lu/": {
policy_expiration: {
t_ms: new Date().getTime() + 60 * 60 * 24 * 1000,
},

View File

@ -1,3 +1,4 @@
import { AuthenticationProviderStatusOk } from "anastasis-core";
import { format } from "date-fns";
import { h, VNode } from "preact";
import { useAnastasisContext } from "../../context/anastasis";
@ -15,33 +16,28 @@ export function BackupFinishedScreen(): VNode {
return <div>invalid state</div>;
}
const details = reducer.currentReducerState.success_details;
const providers = reducer.currentReducerState.authentication_providers ?? {}
return (
<AnastasisClientFrame hideNav title="Backup finished">
{reducer.currentReducerState.secret_name ? (
<p>
Your backup of secret{" "}
<b>"{reducer.currentReducerState.secret_name}"</b> was successful.
</p>
) : (
<p>Your secret was successfully backed up.</p>
)}
<AnastasisClientFrame hideNav title="Backup success!">
<p>Your backup is complete.</p>
{details && (
<div class="block">
<p>The backup is stored by the following providers:</p>
{Object.keys(details).map((x, i) => {
const sd = details[x];
{Object.keys(details).map((url, i) => {
const sd = details[url];
const p = providers[url] as AuthenticationProviderStatusOk
return (
<div key={i} class="box">
{x}
<a href={url} target="_blank" rel="noreferrer">{p.business_name}</a>
<p>
version {sd.policy_version}
{sd.policy_expiration.t_ms !== "never"
? ` expires at: ${format(
sd.policy_expiration.t_ms,
"dd-MM-yyyy",
)}`
new Date(sd.policy_expiration.t_ms),
"dd-MM-yyyy",
)}`
: " without expiration date"}
</p>
</div>
@ -49,17 +45,6 @@ export function BackupFinishedScreen(): VNode {
})}
</div>
)}
<div
style={{
marginTop: "2em",
display: "flex",
justifyContent: "space-between",
}}
>
<button class="button" onClick={() => reducer.back()}>
Back
</button>
</div>
</AnastasisClientFrame>
);
}

View File

@ -3,6 +3,7 @@ import { h, VNode } from "preact";
import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
import { authMethods, KnownAuthMethods } from "./authMethod";
import { AsyncButton } from "../../components/AsyncButton";
function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) {
const { feedback } = props;
@ -150,13 +151,15 @@ export function ChallengeOverviewScreen(): VNode {
id: string;
feedback?: ChallengeFeedback;
}): VNode {
function selectChallenge(): void {
if (reducer) reducer.transition("select_challenge", { uuid: id });
async function selectChallenge(): Promise<void> {
if (reducer) {
return reducer.transition("select_challenge", { uuid: id });
}
}
if (!feedback) {
return (
<div>
<button
<AsyncButton
class="button"
disabled={
atLeastThereIsOnePolicySolved && !policy.isPolicySolved
@ -164,7 +167,7 @@ export function ChallengeOverviewScreen(): VNode {
onClick={selectChallenge}
>
Solve
</button>
</AsyncButton>
</div>
);
}
@ -178,7 +181,7 @@ export function ChallengeOverviewScreen(): VNode {
case ChallengeFeedbackStatus.Payment:
return (
<div>
<button
<AsyncButton
class="button"
disabled={
atLeastThereIsOnePolicySolved && !policy.isPolicySolved
@ -186,13 +189,13 @@ export function ChallengeOverviewScreen(): VNode {
onClick={selectChallenge}
>
Pay
</button>
</AsyncButton>
</div>
);
case ChallengeFeedbackStatus.Redirect:
return (
<div>
<button
<AsyncButton
class="button"
disabled={
atLeastThereIsOnePolicySolved && !policy.isPolicySolved
@ -200,7 +203,7 @@ export function ChallengeOverviewScreen(): VNode {
onClick={selectChallenge}
>
Go to {feedback.redirect_url}
</button>
</AsyncButton>
</div>
);
case ChallengeFeedbackStatus.Solved:
@ -212,7 +215,7 @@ export function ChallengeOverviewScreen(): VNode {
default:
return (
<div>
<button
<AsyncButton
class="button"
disabled={
atLeastThereIsOnePolicySolved && !policy.isPolicySolved
@ -220,7 +223,7 @@ export function ChallengeOverviewScreen(): VNode {
onClick={selectChallenge}
>
Solve
</button>
</AsyncButton>
</div>
);
}

View File

@ -1,10 +1,11 @@
import { ComponentChildren, h, VNode } from "preact";
import { AsyncButton } from "../../components/AsyncButton";
export interface ConfirmModelProps {
active?: boolean;
description?: string;
onCancel?: () => void;
onConfirm?: () => void;
onConfirm?: () => Promise<void>;
label?: string;
cancelLabel?: string;
children?: ComponentChildren;
@ -33,13 +34,13 @@ export function ConfirmModal({
{cancelLabel}
</button>
<div class="buttons is-right" style={{ width: "100%" }}>
<button
<AsyncButton
class={danger ? "button is-danger " : "button is-info "}
disabled={disabled}
onClick={onConfirm}
>
{label}
</button>
</AsyncButton>
</div>
</footer>
</div>

View File

@ -1,10 +1,18 @@
import { bytesToString, decodeCrock } from "@gnu-taler/taler-util";
import { h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { QR } from "../../components/QR";
import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
export function RecoveryFinishedScreen(): VNode {
const reducer = useAnastasisContext();
const [copied, setCopied] = useState(false)
useEffect(() => {
setTimeout(() => {
setCopied(false)
},1000)
}, [copied])
if (!reducer) {
return <div>no reducer in context</div>;
@ -15,6 +23,7 @@ export function RecoveryFinishedScreen(): VNode {
) {
return <div>invalid state</div>;
}
const secretName = reducer.currentReducerState.recovery_document?.secret_name;
const encodedSecret = reducer.currentReducerState.core_secret;
if (!encodedSecret) {
return (
@ -35,19 +44,33 @@ export function RecoveryFinishedScreen(): VNode {
);
}
const secret = bytesToString(decodeCrock(encodedSecret.value));
const contentURI = `data:${encodedSecret.mime},${secret}`
// const fileName = encodedSecret['filename']
// data:plain/text;base64,asdasd
return (
<AnastasisClientFrame title="Recovery Finished" hideNav>
<p>Your secret: {secret}</p>
<div
style={{
marginTop: "2em",
display: "flex",
justifyContent: "space-between",
}}
>
<button class="button" onClick={() => reducer.back()}>
Back
<AnastasisClientFrame title="Recovery Success" hideNav>
<h2 class="subtitle">Your secret was recovered</h2>
{secretName && <p class="block">
<b>Secret name:</b> {secretName}
</p>}
<div class="block buttons" disabled={copied}>
<button class="button" onClick={() => {
navigator.clipboard.writeText(secret);
setCopied(true)
}}>
{ !copied ? 'Copy' : 'Copied'}
</button>
<a class="button is-info" download="secret.txt" href={contentURI}>
<div class="icon is-small ">
<i class="mdi mdi-download" />
</div>
<span>
Save as
</span>
</a>
</div>
<div class="block">
<QR text={secret} />
</div>
</AnastasisClientFrame>
);

View File

@ -1,3 +1,4 @@
import { AuthenticationProviderStatus, AuthenticationProviderStatusOk } from "anastasis-core";
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../components/AsyncButton";
@ -38,15 +39,13 @@ export function SecretSelectionScreen(): VNode {
});
}
const providerList = Object.keys(
reducer.currentReducerState.authentication_providers ?? {},
);
const provs = reducer.currentReducerState.authentication_providers ?? {};
const recoveryDocument = reducer.currentReducerState.recovery_document;
if (!recoveryDocument) {
return (
<ChooseAnotherProviderScreen
providers={providerList}
providers={provs}
selected=""
onChange={(newProv) => doSelectVersion(newProv, 0)}
/>
@ -56,7 +55,7 @@ export function SecretSelectionScreen(): VNode {
if (selectingVersion) {
return (
<SelectOtherVersionProviderScreen
providers={providerList}
providers={provs}
provider={recoveryDocument.provider_url}
version={recoveryDocument.version}
onCancel={() => setSelectingVersion(false)}
@ -69,12 +68,15 @@ export function SecretSelectionScreen(): VNode {
return <AddingProviderScreen onCancel={() => setManageProvider(false)} />;
}
const provierInfo = provs[recoveryDocument.provider_url] as AuthenticationProviderStatusOk
return (
<AnastasisClientFrame title="Recovery: Select secret">
<div class="columns">
<div class="column">
<div class="box" style={{ border: "2px solid green" }}>
<h1 class="subtitle">{recoveryDocument.provider_url}</h1>
<h1 class="subtitle">
{provierInfo.business_name}
</h1>
<div class="block">
{currentVersion === 0 ? (
<p>Set to recover the latest version</p>
@ -111,7 +113,7 @@ function ChooseAnotherProviderScreen({
onChange,
}: {
selected: string;
providers: string[];
providers: { [url: string]: AuthenticationProviderStatus };
onChange: (prov: string) => void;
}): VNode {
return (
@ -132,11 +134,13 @@ function ChooseAnotherProviderScreen({
{" "}
Choose a provider{" "}
</option>
{providers.map((prov) => (
<option key={prov} value={prov}>
{prov}
{Object.keys(providers).map((url) => {
const p = providers[url]
if (!("methods" in p)) return null
return <option key={url} value={url}>
{p.business_name}
</option>
))}
})}
</select>
<div class="icon is-small is-left">
<i class="mdi mdi-earth" />
@ -158,20 +162,21 @@ function SelectOtherVersionProviderScreen({
onCancel: () => void;
provider: string;
version: number;
providers: string[];
providers: { [url: string]: AuthenticationProviderStatus };
onConfirm: (prov: string, v: number) => Promise<void>;
}): VNode {
const [otherProvider, setOtherProvider] = useState<string>(provider);
const [otherVersion, setOtherVersion] = useState(
version > 0 ? String(version) : "",
);
const otherProviderInfo = providers[otherProvider] as AuthenticationProviderStatusOk
return (
<AnastasisClientFrame hideNav title="Recovery: Select secret">
<div class="columns">
<div class="column">
<div class="box">
<h1 class="subtitle">Provider {otherProvider}</h1>
<h1 class="subtitle">Provider {otherProviderInfo.business_name}</h1>
<div class="block">
{version === 0 ? (
<p>Set to recover the latest version</p>
@ -193,11 +198,13 @@ function SelectOtherVersionProviderScreen({
{" "}
Choose a provider{" "}
</option>
{providers.map((prov) => (
<option key={prov} value={prov}>
{prov}
{Object.keys(providers).map((url) => {
const p = providers[url]
if (!("methods" in p)) return null
return <option key={url} value={url}>
{p.business_name}
</option>
))}
})}
</select>
<div class="icon is-small is-left">
<i class="mdi mdi-earth" />
@ -242,7 +249,6 @@ function SelectOtherVersionProviderScreen({
</div>
</div>
</div>
<div class="column">.</div>
</div>
</AnastasisClientFrame>
);

View File

@ -10,6 +10,7 @@ import { AuthMethodSolveProps } from "./index";
export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
const [answer, setAnswer] = useState("");
const [expanded, setExpanded] = useState(false)
const reducer = useAnastasisContext();
if (!reducer) {
@ -86,14 +87,30 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
return (
<AnastasisClientFrame hideNav title="Add email authentication">
<AnastasisClientFrame hideNav title="Email challenge">
<SolveOverviewFeedbackDisplay feedback={feedback} />
<p>
An email has been sent to "<b>{selectedChallenge.instructions}</b>".
Type the code below.
<b>Here we need to add the code "{selectedUuid}"</b>
An email has been sent to "<b>{selectedChallenge.instructions}</b>". The
message has and identification code and recovery code that starts with "<b>A-</b>".
Wait the message to arrive and the enter the recovery code below.
</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
{!expanded ? <p>
The identification code in the email should start with "{selectedUuid.substring(0, 10)}"
<span class="icon has-tooltip-top" data-tooltip="click to expand" onClick={() => setExpanded(e => !e)}>
<i class="mdi mdi-information" />
</span>
</p>
: <p>
The identification code in the email is "{selectedUuid}"
<span class="icon has-tooltip-top" data-tooltip="click to show less code" onClick={() => setExpanded(e => !e)}>
<i class="mdi mdi-information" />
</span>
</p>}
<TextInput label="Answer"
grabFocus
bind={[answer, setAnswer]}
placeholder="A-1234567812345678"
/>
<div
style={{

View File

@ -86,7 +86,7 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode {
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
return (
<AnastasisClientFrame hideNav title="Add email authentication">
<AnastasisClientFrame hideNav title="IBAN Challenge">
<SolveOverviewFeedbackDisplay feedback={feedback} />
<p>Send a wire transfer to the address,</p>
<button class="button">Check</button>

View File

@ -86,7 +86,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
return (
<AnastasisClientFrame hideNav title="Add email authentication">
<AnastasisClientFrame hideNav title="Postal Challenge">
<SolveOverviewFeedbackDisplay feedback={feedback} />
<p>Wait for the answer</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />

View File

@ -86,9 +86,15 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode {
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
return (
<AnastasisClientFrame hideNav title="Add email authentication">
<AnastasisClientFrame hideNav title="Question challenge">
<SolveOverviewFeedbackDisplay feedback={feedback} />
<p>Answer the question please</p>
<p>
In this challenge you need to provide the answer for the next question:
</p>
<pre>
{selectedChallenge.instructions}
</pre>
<p>Type the answer below</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
<div

View File

@ -11,6 +11,7 @@ import { AuthMethodSolveProps } from "./index";
export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
const [answer, setAnswer] = useState("");
const [expanded, setExpanded] = useState(false)
const reducer = useAnastasisContext();
if (!reducer) {
return (
@ -86,13 +87,30 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
return (
<AnastasisClientFrame hideNav title="Add email authentication">
<AnastasisClientFrame hideNav title="SMS Challenge">
<SolveOverviewFeedbackDisplay feedback={feedback} />
<p>
An sms has been sent to "<b>{selectedChallenge.instructions}</b>". Type
the code below
An sms has been sent to "<b>{selectedChallenge.instructions}</b>". The
message has and identification code and recovery code that starts with "<b>A-</b>".
Wait the message to arrive and the enter the recovery code below.
</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
{!expanded ? <p>
The identification code in the SMS should start with "{selectedUuid.substring(0, 10)}"
<span class="icon has-tooltip-top" data-tooltip="click to expand" onClick={() => setExpanded(e => !e)}>
<i class="mdi mdi-information" />
</span>
</p>
: <p>
The identification code in the SMS is "{selectedUuid}"
<span class="icon has-tooltip-top" data-tooltip="click to show less code" onClick={() => setExpanded(e => !e)}>
<i class="mdi mdi-information" />
</span>
</p>}
<TextInput label="Answer"
grabFocus
bind={[answer, setAnswer]}
placeholder="A-1234567812345678"
/>
<div
style={{

View File

@ -86,7 +86,7 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode {
feedback?.state === ChallengeFeedbackStatus.TruthUnknown;
return (
<AnastasisClientFrame hideNav title="Add email authentication">
<AnastasisClientFrame hideNav title="TOTP Challenge">
<SolveOverviewFeedbackDisplay feedback={feedback} />
<p>enter the totp solution</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
@ -110,3 +110,4 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode {
</AnastasisClientFrame>
);
}
// NKE8 VD857T X033X6RG WEGPYP6D70 Q7YE XN8D2 ZN79SCN 231B4QK0

View File

@ -101,7 +101,7 @@ const base = {
"http://localhost:8086/": {
http_status: 200,
annual_fee: "COL:0",
business_name: "ana",
business_name: "Anastasis Local",
currency: "COL",
liability_limit: "COL:10",
methods: [
@ -125,7 +125,7 @@ const base = {
"https://kudos.demo.anastasis.lu/": {
http_status: 200,
annual_fee: "COL:0",
business_name: "ana",
business_name: "Anastasis Kudo",
currency: "COL",
liability_limit: "COL:10",
methods: [
@ -145,7 +145,7 @@ const base = {
"https://anastasis.demo.taler.net/": {
http_status: 200,
annual_fee: "COL:0",
business_name: "ana",
business_name: "Anastasis Demo",
currency: "COL",
liability_limit: "COL:10",
methods: [
@ -180,9 +180,6 @@ const base = {
hint: "request to provider failed",
},
},
// expiration: {
// d_ms: 1792525051855 // check t_ms
// },
} as Partial<ReducerState>;
export const reducerStatesExample = {