/* This file is part of GNU Anastasis (C) 2021-2022 Anastasis SARL GNU Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Anastasis 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with GNU Anastasis; see the file COPYING. If not, see */ import { BackupStates, RecoveryStates } from "@gnu-taler/anastasis-core"; import { ComponentChildren, Fragment, FunctionalComponent, h, VNode, } from "preact"; import { useCallback, useEffect, useErrorBoundary } from "preact/hooks"; import { AsyncButton } from "../../components/AsyncButton.js"; import { Menu } from "../../components/menu/index.js"; import { Notifications } from "../../components/Notifications.js"; import { AnastasisProvider, useAnastasisContext, } from "../../context/anastasis.js"; import { AnastasisReducerApi, useAnastasisReducer, } from "../../hooks/use-anastasis-reducer.js"; import { AttributeEntryScreen } from "./AttributeEntryScreen.js"; import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen.js"; import { BackupFinishedScreen } from "./BackupFinishedScreen.js"; import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen.js"; import { ChallengePayingScreen } from "./ChallengePayingScreen.js"; import { ContinentSelectionScreen } from "./ContinentSelectionScreen.js"; import { PoliciesPayingScreen } from "./PoliciesPayingScreen.js"; import { RecoveryFinishedScreen } from "./RecoveryFinishedScreen.js"; import { ReviewPoliciesScreen } from "./ReviewPoliciesScreen.js"; import { SecretEditorScreen } from "./SecretEditorScreen.js"; import { SecretSelectionScreen } from "./SecretSelectionScreen.js"; import { SolveScreen } from "./SolveScreen.js"; import { StartScreen } from "./StartScreen.js"; import { TruthsPayingScreen } from "./TruthsPayingScreen.js"; function isBackup(reducer: AnastasisReducerApi): boolean { return reducer.currentReducerState?.reducer_type === "backup"; } export function withProcessLabel( reducer: AnastasisReducerApi, text: string, ): string { if (isBackup(reducer)) { return `Backup: ${text}`; } return `Recovery: ${text}`; } interface AnastasisClientFrameProps { onNext?(): Promise; /** * Override for the "back" functionality. */ onBack?(): Promise; title: string; children: ComponentChildren; /** * Should back/next buttons be provided? */ hideNav?: boolean; /** * Hide only the "next" button. */ hideNext?: string; } function ErrorBoundary(props: { reducer: AnastasisReducerApi; children: ComponentChildren; }): VNode { const [error, resetError] = useErrorBoundary((error) => console.log("ErrorBoundary got error", error), ); if (error) { return (

Error:

{error.stack}

); } return
{props.children}
; } let currentHistoryId = 0; export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { const reducer = useAnastasisContext(); const doBack = async (): Promise => { if (props.onBack) { await props.onBack(); } else { if (!reducer) return; await reducer.back(); } }; const doNext = async (fromPopstate?: boolean): Promise => { if (!fromPopstate) { try { const nextId: number = (history.state && typeof history.state.id === "number" ? history.state.id : 0) + 1; currentHistoryId = nextId; history.pushState({ id: nextId }, "unused", `#${nextId}`); } catch (e) { console.log("ERROR doNext ", e); } } if (props.onNext) { await props.onNext(); } else { if (!reducer) return; await reducer.transition("next", {}); } }; const handleKeyPress = ( e: h.JSX.TargetedKeyboardEvent, ): void => { // console.log("Got key press", e.key); // FIXME: By default, "next" action should be executed here }; const browserOnBackButton = useCallback(async (ev: PopStateEvent) => { //check if we are going back or forward if (!ev.state || ev.state.id === 0 || ev.state.id < currentHistoryId) { await doBack(); } else { await doNext(true); } // reducer return false; }, []); useEffect(() => { window.addEventListener("popstate", browserOnBackButton); return () => { window.removeEventListener("popstate", browserOnBackButton); }; }, []); // if (!reducer) { // return

Fatal: Reducer must be in context.

; // } return (
handleKeyPress(e)}>

{props.title}

{props.children} {!props.hideNav ? (
doNext()} disabled={props.hideNext !== undefined} > Next
) : null}
); } const AnastasisClient: FunctionalComponent = () => { const reducer = useAnastasisReducer(); return ( ); }; function AnastasisClientImpl(): VNode { const reducer = useAnastasisContext(); if (!reducer) { return

Fatal: Reducer must be in context.

; } const state = reducer.currentReducerState; if (!state) { return ; } // FIXME: Use switch statements here! if ( (state.reducer_type === "backup" && state.backup_state === BackupStates.ContinentSelecting) || (state.reducer_type === "recovery" && state.recovery_state === RecoveryStates.ContinentSelecting) || (state.reducer_type === "backup" && state.backup_state === BackupStates.CountrySelecting) || (state.reducer_type === "recovery" && state.recovery_state === RecoveryStates.CountrySelecting) ) { return ; } if ( (state.reducer_type === "backup" && state.backup_state === BackupStates.UserAttributesCollecting) || (state.reducer_type === "recovery" && state.recovery_state === RecoveryStates.UserAttributesCollecting) ) { return ; } if ( state.reducer_type === "backup" && state.backup_state === BackupStates.AuthenticationsEditing ) { return ; } if ( state.reducer_type === "backup" && state.backup_state === BackupStates.PoliciesReviewing ) { return ; } if ( state.reducer_type === "backup" && state.backup_state === BackupStates.SecretEditing ) { return ; } if ( state.reducer_type === "backup" && state.backup_state === BackupStates.BackupFinished ) { return ; } if ( state.reducer_type === "backup" && state.backup_state === BackupStates.TruthsPaying ) { return ; } if ( state.reducer_type === "backup" && state.backup_state === BackupStates.PoliciesPaying ) { return ; } if ( state.reducer_type === "recovery" && state.recovery_state === RecoveryStates.SecretSelecting ) { return ; } if ( state.reducer_type === "recovery" && state.recovery_state === RecoveryStates.ChallengeSelecting ) { return ; } if ( state.reducer_type === "recovery" && state.recovery_state === RecoveryStates.ChallengeSolving ) { return ; } if ( state.reducer_type === "recovery" && state.recovery_state === RecoveryStates.RecoveryFinished ) { return ; } if ( state.reducer_type === "recovery" && state.recovery_state === RecoveryStates.ChallengePaying ) { return ; } console.log("unknown state", reducer.currentReducerState); return (

Bug: Unknown state.

); } /** * Show a dismissible error banner if there is a current error. */ function ErrorBanner(): VNode | null { const reducer = useAnastasisContext(); if (!reducer || !reducer.currentError) return null; return ( ); } export default AnastasisClient;