import { Component, ComponentChildren, createContext, Fragment, FunctionalComponent, h, VNode, } from "preact"; import { useContext, useErrorBoundary, useLayoutEffect, useRef, } from "preact/hooks"; import { Menu } from "../../components/menu"; import { BackupStates, RecoveryStates, ReducerStateBackup, ReducerStateRecovery, } from "anastasis-core"; import { AnastasisReducerApi, useAnastasisReducer, } from "../../hooks/use-anastasis-reducer"; import { AttributeEntryScreen } from "./AttributeEntryScreen"; import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen"; import { BackupFinishedScreen } from "./BackupFinishedScreen"; import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen"; import { ContinentSelectionScreen } from "./ContinentSelectionScreen"; import { CountrySelectionScreen } from "./CountrySelectionScreen"; import { PoliciesPayingScreen } from "./PoliciesPayingScreen"; import { RecoveryFinishedScreen } from "./RecoveryFinishedScreen"; import { ReviewPoliciesScreen } from "./ReviewPoliciesScreen"; import { SecretEditorScreen } from "./SecretEditorScreen"; import { SecretSelectionScreen } from "./SecretSelectionScreen"; import { SolveScreen } from "./SolveScreen"; import { StartScreen } from "./StartScreen"; import { TruthsPayingScreen } from "./TruthsPayingScreen"; import "./../home/style"; const WithReducer = createContext(undefined); function isBackup(reducer: AnastasisReducerApi): boolean { return !!reducer.currentReducerState?.backup_state; } export interface CommonReducerProps { reducer: AnastasisReducerApi; reducerState: ReducerStateBackup | ReducerStateRecovery; } export function withProcessLabel( reducer: AnastasisReducerApi, text: string, ): string { if (isBackup(reducer)) { return `Backup: ${text}`; } return `Recovery: ${text}`; } export interface BackupReducerProps { reducer: AnastasisReducerApi; backupState: ReducerStateBackup; } export interface RecoveryReducerProps { reducer: AnastasisReducerApi; recoveryState: ReducerStateRecovery; } interface AnastasisClientFrameProps { onNext?(): void; title: string; children: ComponentChildren; /** * Should back/next buttons be provided? */ hideNav?: boolean; /** * Hide only the "next" button. */ 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) { return

Fatal: Reducer must be in context.

; } const next = (): void => { if (props.onNext) { props.onNext(); } else { 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 }; return (
handleKeyPress(e)}>

{props.title}

{props.children} {!props.hideNav ? (
{!props.hideNext ? : null}
) : null}
); } const AnastasisClient: FunctionalComponent = () => { const reducer = useAnastasisReducer(); return ( ); }; const AnastasisClientImpl: FunctionalComponent = () => { const reducer = useContext(WithReducer)!; const reducerState = reducer.currentReducerState; if (!reducerState) { return ; } console.log("state", reducer.currentReducerState); if ( reducerState.backup_state === BackupStates.ContinentSelecting || reducerState.recovery_state === RecoveryStates.ContinentSelecting ) { return ( ); } if ( reducerState.backup_state === BackupStates.CountrySelecting || reducerState.recovery_state === RecoveryStates.CountrySelecting ) { return ( ); } if ( reducerState.backup_state === BackupStates.UserAttributesCollecting || reducerState.recovery_state === RecoveryStates.UserAttributesCollecting ) { return ( ); } if (reducerState.backup_state === BackupStates.AuthenticationsEditing) { return ( ); } if (reducerState.backup_state === BackupStates.PoliciesReviewing) { return ( ); } if (reducerState.backup_state === BackupStates.SecretEditing) { return ; } if (reducerState.backup_state === BackupStates.BackupFinished) { const backupState: ReducerStateBackup = reducerState; return ; } if (reducerState.backup_state === BackupStates.TruthsPaying) { return ; } if (reducerState.backup_state === BackupStates.PoliciesPaying) { const backupState: ReducerStateBackup = reducerState; return ; } if (reducerState.recovery_state === RecoveryStates.SecretSelecting) { return ( ); } if (reducerState.recovery_state === RecoveryStates.ChallengeSelecting) { return ( ); } if (reducerState.recovery_state === RecoveryStates.ChallengeSolving) { return ; } if (reducerState.recovery_state === RecoveryStates.RecoveryFinished) { return ( ); } console.log("unknown state", reducer.currentReducerState); return (

Bug: Unknown state.

); }; interface LabeledInputProps { label: string; grabFocus?: boolean; bind: [string, (x: string) => void]; } export function LabeledInput(props: LabeledInputProps): VNode { const inputRef = useRef(null); useLayoutEffect(() => { if (props.grabFocus) { inputRef.current?.focus(); } }, [props.grabFocus]); return ( ); } interface ErrorBannerProps { reducer: AnastasisReducerApi; } /** * Show a dismissable error banner if there is a current error. */ function ErrorBanner(props: ErrorBannerProps): VNode | null { const currentError = props.reducer.currentError; if (currentError) { return (

Error: {JSON.stringify(currentError)}

); } return null; } export default AnastasisClient;