error notifications
This commit is contained in:
parent
ae0a35df2b
commit
9ba0e8597d
@ -26,7 +26,7 @@ import { useAsync } from "../hooks/async";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ComponentChildren;
|
children: ComponentChildren;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
onClick?: () => Promise<void>;
|
onClick?: () => Promise<void>;
|
||||||
[rest: string]: any;
|
[rest: string]: any;
|
||||||
};
|
};
|
||||||
|
59
packages/anastasis-webui/src/components/Notifications.tsx
Normal file
59
packages/anastasis-webui/src/components/Notifications.tsx
Normal 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>
|
||||||
|
}
|
@ -15,15 +15,18 @@ export function RecoveryFinishedScreen(): VNode {
|
|||||||
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
|
if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
|
||||||
return <div>invalid state</div>
|
return <div>invalid state</div>
|
||||||
}
|
}
|
||||||
const encodedSecret = reducer.currentReducerState.core_secret?.value
|
const encodedSecret = reducer.currentReducerState.core_secret
|
||||||
if (!encodedSecret) {
|
if (!encodedSecret) {
|
||||||
return <AnastasisClientFrame title="Recovery Problem" hideNav>
|
return <AnastasisClientFrame title="Recovery Problem" hideNav>
|
||||||
<p>
|
<p>
|
||||||
Secret not found
|
Secret not found
|
||||||
</p>
|
</p>
|
||||||
|
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<button class="button" onClick={() => reducer.back()}>Back</button>
|
||||||
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
}
|
}
|
||||||
const secret = bytesToString(decodeCrock(encodedSecret))
|
const secret = bytesToString(decodeCrock(encodedSecret.value))
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame title="Recovery Finished" hideNav>
|
<AnastasisClientFrame title="Recovery Finished" hideNav>
|
||||||
<p>
|
<p>
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
ChallengeFeedbackStatus,
|
ChallengeFeedbackStatus,
|
||||||
ChallengeInfo,
|
ChallengeInfo,
|
||||||
} from "../../../../anastasis-core/lib";
|
} from "../../../../anastasis-core/lib";
|
||||||
|
import { AsyncButton } from "../../components/AsyncButton";
|
||||||
import { TextInput } from "../../components/fields/TextInput";
|
import { TextInput } from "../../components/fields/TextInput";
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
|
|
||||||
@ -106,8 +107,8 @@ export function SolveScreen(): VNode {
|
|||||||
? SolveUndefinedEntry
|
? SolveUndefinedEntry
|
||||||
: dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry;
|
: dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry;
|
||||||
|
|
||||||
function onNext(): void {
|
async function onNext(): Promise<void> {
|
||||||
reducer?.transition("solve_challenge", { answer });
|
return reducer?.transition("solve_challenge", { answer });
|
||||||
}
|
}
|
||||||
function onCancel(): void {
|
function onCancel(): void {
|
||||||
reducer?.back();
|
reducer?.back();
|
||||||
@ -136,9 +137,9 @@ export function SolveScreen(): VNode {
|
|||||||
<button class="button" onClick={onCancel}>
|
<button class="button" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button class="button is-info" onClick={onNext}>
|
<AsyncButton onClick={onNext} disabled={false}>
|
||||||
Confirm
|
Confirm
|
||||||
</button>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</AnastasisClientFrame>
|
</AnastasisClientFrame>
|
||||||
);
|
);
|
||||||
|
@ -25,10 +25,10 @@ export function StartScreen(): VNode {
|
|||||||
<span>Recover a secret</span>
|
<span>Recover a secret</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="button">
|
{/* <button class="button">
|
||||||
<div class="icon"><i class="mdi mdi-file" /></div>
|
<div class="icon"><i class="mdi mdi-file" /></div>
|
||||||
<span>Restore a session</span>
|
<span>Restore a session</span>
|
||||||
</button>
|
</button> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
} from "preact/hooks";
|
} from "preact/hooks";
|
||||||
import { AsyncButton } from "../../components/AsyncButton";
|
import { AsyncButton } from "../../components/AsyncButton";
|
||||||
import { Menu } from "../../components/menu";
|
import { Menu } from "../../components/menu";
|
||||||
|
import { Notifications } from "../../components/Notifications";
|
||||||
import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis";
|
import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis";
|
||||||
import {
|
import {
|
||||||
AnastasisReducerApi,
|
AnastasisReducerApi,
|
||||||
@ -96,18 +97,11 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
|
|||||||
return <p>Fatal: Reducer must be in context.</p>;
|
return <p>Fatal: Reducer must be in context.</p>;
|
||||||
}
|
}
|
||||||
const next = async (): Promise<void> => {
|
const next = async (): Promise<void> => {
|
||||||
return new Promise(async (res, rej) => {
|
if (props.onNext) {
|
||||||
try {
|
await props.onNext();
|
||||||
if (props.onNext) {
|
} else {
|
||||||
await props.onNext();
|
await reducer.transition("next", {});
|
||||||
} else {
|
}
|
||||||
await reducer.transition("next", {});
|
|
||||||
}
|
|
||||||
res()
|
|
||||||
} catch {
|
|
||||||
rej()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
const handleKeyPress = (
|
const handleKeyPress = (
|
||||||
e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>,
|
e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>,
|
||||||
@ -120,8 +114,8 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
|
|||||||
<Menu title="Anastasis" />
|
<Menu title="Anastasis" />
|
||||||
<div class="home" onKeyPress={(e) => handleKeyPress(e)}>
|
<div class="home" onKeyPress={(e) => handleKeyPress(e)}>
|
||||||
<h1 class="title">{props.title}</h1>
|
<h1 class="title">{props.title}</h1>
|
||||||
|
<ErrorBanner />
|
||||||
<section class="section is-main-section">
|
<section class="section is-main-section">
|
||||||
<ErrorBanner />
|
|
||||||
{props.children}
|
{props.children}
|
||||||
{!props.hideNav ? (
|
{!props.hideNav ? (
|
||||||
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
@ -242,13 +236,11 @@ const AnastasisClientImpl: FunctionalComponent = () => {
|
|||||||
function ErrorBanner(): VNode | null {
|
function ErrorBanner(): VNode | null {
|
||||||
const reducer = useAnastasisContext();
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer || !reducer.currentError) return null;
|
if (!reducer || !reducer.currentError) return null;
|
||||||
return (
|
return (<Notifications removeNotification={reducer.dismissError} notifications={[{
|
||||||
<div id="error">
|
type: "ERROR",
|
||||||
<p>Error: {JSON.stringify(reducer.currentError)}</p>
|
message: `Error code: ${reducer.currentError.code}`,
|
||||||
<button onClick={() => reducer.dismissError()}>
|
description: reducer.currentError.hint
|
||||||
Dismiss Error
|
}]} />
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user