From 0ee669f52341a8331394a1e9892264c0ef0bb7d7 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 21 Oct 2021 13:11:17 +0200 Subject: reducer WIP, user error boundaries in UI --- .../src/hooks/use-anastasis-reducer.ts | 4 +- .../src/pages/home/SecretSelectionScreen.tsx | 2 +- packages/anastasis-webui/src/pages/home/index.tsx | 142 +++++++++++++++------ packages/anastasis-webui/src/scss/main.scss | 6 + 4 files changed, 110 insertions(+), 44 deletions(-) (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts index 4a242a2e5..72594749d 100644 --- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts +++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts @@ -164,10 +164,12 @@ export function useAnastasisReducer(): AnastasisReducerApi { } else { s = await reduceAction(anastasisState.reducerState!, action, args); } - console.log("got new state from reducer", s); + console.log("got response from reducer", s); if (s.code) { + console.log("response is an error"); setAnastasisState({ ...anastasisState, currentError: s }); } else { + console.log("response is a new state"); setAnastasisState({ ...anastasisState, currentError: undefined, diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index bbdcf8c2e..7cb7fdf20 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -57,7 +57,7 @@ export function SecretSelectionScreen(props: RecoveryReducerProps): VNode {

Provider: {recoveryDocument.provider_url}

Secret version: {recoveryDocument.version}

-

Secret name: {recoveryDocument.version}

+

Secret name: {recoveryDocument.secret_name}

diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx index 6e9ea07fc..5001d1ee4 100644 --- a/packages/anastasis-webui/src/pages/home/index.tsx +++ b/packages/anastasis-webui/src/pages/home/index.tsx @@ -1,17 +1,28 @@ import { - ComponentChildren, createContext, - Fragment, FunctionalComponent, h, VNode + Component, + ComponentChildren, + createContext, + Fragment, + FunctionalComponent, + h, + VNode, } from "preact"; -import { useContext, useLayoutEffect, useRef } from "preact/hooks"; +import { + useContext, + useErrorBoundary, + useLayoutEffect, + useRef, +} from "preact/hooks"; import { Menu } from "../../components/menu"; import { - BackupStates, RecoveryStates, + BackupStates, + RecoveryStates, ReducerStateBackup, ReducerStateRecovery, } from "anastasis-core"; import { AnastasisReducerApi, - useAnastasisReducer + useAnastasisReducer, } from "../../hooks/use-anastasis-reducer"; import { AttributeEntryScreen } from "./AttributeEntryScreen"; import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen"; @@ -27,7 +38,7 @@ import { SecretSelectionScreen } from "./SecretSelectionScreen"; import { SolveScreen } from "./SolveScreen"; import { StartScreen } from "./StartScreen"; import { TruthsPayingScreen } from "./TruthsPayingScreen"; -import "./../home/style" +import "./../home/style"; const WithReducer = createContext(undefined); @@ -40,7 +51,10 @@ export interface CommonReducerProps { reducerState: ReducerStateBackup | ReducerStateRecovery; } -export function withProcessLabel(reducer: AnastasisReducerApi, text: string): string { +export function withProcessLabel( + reducer: AnastasisReducerApi, + text: string, +): string { if (isBackup(reducer)) { return `Backup: ${text}`; } @@ -71,6 +85,33 @@ interface AnastasisClientFrameProps { hideNext?: boolean; } +function ErrorBoundary(props: { + reducer: AnastasisReducerApi; + children: ComponentChildren; +}) { + const [error, resetError] = useErrorBoundary((error) => + console.log("got error", error), + ); + if (error) { + return ( +
+ +

+ Error:

{error.stack}
+

+
+ ); + } + return
{props.children}
; +} + export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { const reducer = useContext(WithReducer); if (!reducer) { @@ -83,29 +124,30 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { reducer.transition("next", {}); } }; - const handleKeyPress = (e: h.JSX.TargetedKeyboardEvent): void => { + const handleKeyPress = ( + e: h.JSX.TargetedKeyboardEvent, + ): void => { console.log("Got key press", e.key); // FIXME: By default, "next" action should be executed here }; - return ( - -
-
handleKeyPress(e)}> - -

{props.title}

- - {props.children} - {!props.hideNav ? ( -
- - {!props.hideNext ? ( - - ) : null} -
- ) : null} + return ( + + +
+
handleKeyPress(e)}> + +

{props.title}

+ + {props.children} + {!props.hideNav ? ( +
+ + {!props.hideNext ? : null} +
+ ) : null} +
-
- + ); } @@ -113,7 +155,9 @@ const AnastasisClient: FunctionalComponent = () => { const reducer = useAnastasisReducer(); return ( - + + + ); }; @@ -130,27 +174,38 @@ const AnastasisClientImpl: FunctionalComponent = () => { reducerState.backup_state === BackupStates.ContinentSelecting || reducerState.recovery_state === RecoveryStates.ContinentSelecting ) { - return ; + return ( + + ); } if ( reducerState.backup_state === BackupStates.CountrySelecting || reducerState.recovery_state === RecoveryStates.CountrySelecting ) { - return ; + return ( + + ); } if ( reducerState.backup_state === BackupStates.UserAttributesCollecting || reducerState.recovery_state === RecoveryStates.UserAttributesCollecting ) { - return ; + return ( + + ); } if (reducerState.backup_state === BackupStates.AuthenticationsEditing) { return ( - + ); } if (reducerState.backup_state === BackupStates.PoliciesReviewing) { - return ; + return ( + + ); } if (reducerState.backup_state === BackupStates.SecretEditing) { return ; @@ -162,29 +217,34 @@ const AnastasisClientImpl: FunctionalComponent = () => { } if (reducerState.backup_state === BackupStates.TruthsPaying) { - return - + return ; } if (reducerState.backup_state === BackupStates.PoliciesPaying) { const backupState: ReducerStateBackup = reducerState; - return + return ; } if (reducerState.recovery_state === RecoveryStates.SecretSelecting) { - return ; + return ( + + ); } if (reducerState.recovery_state === RecoveryStates.ChallengeSelecting) { - return ; + return ( + + ); } if (reducerState.recovery_state === RecoveryStates.ChallengeSolving) { - return + return ; } if (reducerState.recovery_state === RecoveryStates.RecoveryFinished) { - return + return ( + + ); } console.log("unknown state", reducer.currentReducerState); @@ -196,7 +256,6 @@ const AnastasisClientImpl: FunctionalComponent = () => { ); }; - interface LabeledInputProps { label: string; grabFocus?: boolean; @@ -223,7 +282,6 @@ export function LabeledInput(props: LabeledInputProps): VNode { ); } - interface ErrorBannerProps { reducer: AnastasisReducerApi; } @@ -235,7 +293,7 @@ function ErrorBanner(props: ErrorBannerProps): VNode | null { const currentError = props.reducer.currentError; if (currentError) { return ( -
+

Error: {JSON.stringify(currentError)}