error notifications

This commit is contained in:
Sebastian 2021-11-04 15:18:30 -03:00
parent ae0a35df2b
commit 9ba0e8597d
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
6 changed files with 84 additions and 29 deletions

View File

@ -26,7 +26,7 @@ import { useAsync } from "../hooks/async";
type Props = {
children: ComponentChildren;
disabled: boolean;
disabled?: boolean;
onClick?: () => Promise<void>;
[rest: string]: any;
};

View File

@ -0,0 +1,59 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { h, VNode } from "preact";
export interface Notification {
message: string;
description?: string | VNode;
type: MessageType;
}
export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS'
interface Props {
notifications: Notification[];
removeNotification?: (n: Notification) => void;
}
function messageStyle(type: MessageType): string {
switch (type) {
case "INFO": return "message is-info";
case "WARN": return "message is-warning";
case "ERROR": return "message is-danger";
case "SUCCESS": return "message is-success";
default: return "message"
}
}
export function Notifications({ notifications, removeNotification }: Props): VNode {
return <div class="block">
{notifications.map((n,i) => <article key={i} class={messageStyle(n.type)}>
<div class="message-header">
<p>{n.message}</p>
<button class="delete" onClick={() => removeNotification && removeNotification(n)} />
</div>
{n.description && <div class="message-body">
{n.description}
</div>}
</article>)}
</div>
}

View File

@ -15,15 +15,18 @@ export function RecoveryFinishedScreen(): VNode {
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
return <div>invalid state</div>
}
const encodedSecret = reducer.currentReducerState.core_secret?.value
const encodedSecret = reducer.currentReducerState.core_secret
if (!encodedSecret) {
return <AnastasisClientFrame title="Recovery Problem" hideNav>
<p>
Secret not found
</p>
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
<button class="button" onClick={() => reducer.back()}>Back</button>
</div>
</AnastasisClientFrame>
}
const secret = bytesToString(decodeCrock(encodedSecret))
const secret = bytesToString(decodeCrock(encodedSecret.value))
return (
<AnastasisClientFrame title="Recovery Finished" hideNav>
<p>

View File

@ -6,6 +6,7 @@ import {
ChallengeFeedbackStatus,
ChallengeInfo,
} from "../../../../anastasis-core/lib";
import { AsyncButton } from "../../components/AsyncButton";
import { TextInput } from "../../components/fields/TextInput";
import { useAnastasisContext } from "../../context/anastasis";
@ -106,8 +107,8 @@ export function SolveScreen(): VNode {
? SolveUndefinedEntry
: dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry;
function onNext(): void {
reducer?.transition("solve_challenge", { answer });
async function onNext(): Promise<void> {
return reducer?.transition("solve_challenge", { answer });
}
function onCancel(): void {
reducer?.back();
@ -136,9 +137,9 @@ export function SolveScreen(): VNode {
<button class="button" onClick={onCancel}>
Cancel
</button>
<button class="button is-info" onClick={onNext}>
<AsyncButton onClick={onNext} disabled={false}>
Confirm
</button>
</AsyncButton>
</div>
</AnastasisClientFrame>
);

View File

@ -25,10 +25,10 @@ export function StartScreen(): VNode {
<span>Recover a secret</span>
</button>
<button class="button">
{/* <button class="button">
<div class="icon"><i class="mdi mdi-file" /></div>
<span>Restore a session</span>
</button>
</button> */}
</div>
</div>

View File

@ -15,6 +15,7 @@ import {
} from "preact/hooks";
import { AsyncButton } from "../../components/AsyncButton";
import { Menu } from "../../components/menu";
import { Notifications } from "../../components/Notifications";
import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis";
import {
AnastasisReducerApi,
@ -96,18 +97,11 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
return <p>Fatal: Reducer must be in context.</p>;
}
const next = async (): Promise<void> => {
return new Promise(async (res, rej) => {
try {
if (props.onNext) {
await props.onNext();
} else {
await reducer.transition("next", {});
}
res()
} catch {
rej()
}
})
if (props.onNext) {
await props.onNext();
} else {
await reducer.transition("next", {});
}
};
const handleKeyPress = (
e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>,
@ -120,8 +114,8 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
<Menu title="Anastasis" />
<div class="home" onKeyPress={(e) => handleKeyPress(e)}>
<h1 class="title">{props.title}</h1>
<ErrorBanner />
<section class="section is-main-section">
<ErrorBanner />
{props.children}
{!props.hideNav ? (
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
@ -242,13 +236,11 @@ const AnastasisClientImpl: FunctionalComponent = () => {
function ErrorBanner(): VNode | null {
const reducer = useAnastasisContext();
if (!reducer || !reducer.currentError) return null;
return (
<div id="error">
<p>Error: {JSON.stringify(reducer.currentError)}</p>
<button onClick={() => reducer.dismissError()}>
Dismiss Error
</button>
</div>
return (<Notifications removeNotification={reducer.dismissError} notifications={[{
type: "ERROR",
message: `Error code: ${reducer.currentError.code}`,
description: reducer.currentError.hint
}]} />
);
}