wallet-core/packages/anastasis-webui/src/pages/home/index.tsx

254 lines
6.7 KiB
TypeScript
Raw Normal View History

2021-10-19 15:56:52 +02:00
import {
2021-10-22 06:31:46 +02:00
BackupStates,
RecoveryStates,
ReducerStateBackup,
ReducerStateRecovery
} from "anastasis-core";
import {
ComponentChildren, Fragment,
FunctionalComponent,
h,
2021-10-22 06:31:46 +02:00
VNode
2021-10-19 15:56:52 +02:00
} from "preact";
import {
useErrorBoundary} from "preact/hooks";
2021-10-19 15:56:52 +02:00
import { Menu } from "../../components/menu";
2021-10-22 06:31:46 +02:00
import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis";
2021-10-19 15:56:52 +02:00
import {
AnastasisReducerApi,
2021-10-22 06:31:46 +02:00
useAnastasisReducer
2021-10-19 15:56:52 +02:00
} from "../../hooks/use-anastasis-reducer";
import { AttributeEntryScreen } from "./AttributeEntryScreen";
import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen";
import { BackupFinishedScreen } from "./BackupFinishedScreen";
import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen";
import { ChallengePayingScreen } from "./ChallengePayingScreen";
2021-10-19 15:56:52 +02:00
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";
function isBackup(reducer: AnastasisReducerApi): boolean {
return !!reducer.currentReducerState?.backup_state;
}
export function withProcessLabel(
reducer: AnastasisReducerApi,
text: string,
): string {
2021-10-19 15:56:52 +02:00
if (isBackup(reducer)) {
return `Backup: ${text}`;
}
return `Recovery: ${text}`;
}
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;
2021-10-22 06:31:46 +02:00
}): VNode {
const [error, resetError] = useErrorBoundary((error) =>
console.log("got error", error),
);
if (error) {
return (
<div>
<button
onClick={() => {
props.reducer.reset();
resetError();
}}
>
Reset
</button>
<p>
Error: <pre>{error.stack}</pre>
</p>
</div>
);
}
return <div>{props.children}</div>;
}
2021-10-19 15:56:52 +02:00
export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
2021-10-22 06:31:46 +02:00
const reducer = useAnastasisContext();
2021-10-19 15:56:52 +02:00
if (!reducer) {
return <p>Fatal: Reducer must be in context.</p>;
}
const next = (): void => {
if (props.onNext) {
props.onNext();
} else {
reducer.transition("next", {});
}
};
const handleKeyPress = (
e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>,
): void => {
2021-10-19 15:56:52 +02:00
console.log("Got key press", e.key);
// FIXME: By default, "next" action should be executed here
};
return (
<Fragment>
<Menu title="Anastasis" />
<div>
<div class="home" onKeyPress={(e) => handleKeyPress(e)}>
<h1>{props.title}</h1>
2021-10-22 06:31:46 +02:00
<ErrorBanner />
{props.children}
{!props.hideNav ? (
<div style={{marginTop: '2em', display:'flex', justifyContent:'space-between'}}>
<button class="button" onClick={() => reducer.back()}>Back</button>
{!props.hideNext ? <button class="button is-info"onClick={next}>Next</button> : null}
</div>
) : null}
</div>
2021-10-19 15:56:52 +02:00
</div>
</Fragment>
2021-10-19 15:56:52 +02:00
);
}
const AnastasisClient: FunctionalComponent = () => {
const reducer = useAnastasisReducer();
return (
2021-10-22 06:31:46 +02:00
<AnastasisProvider value={reducer}>
<ErrorBoundary reducer={reducer}>
<AnastasisClientImpl />
</ErrorBoundary>
2021-10-22 06:31:46 +02:00
</AnastasisProvider>
2021-10-19 15:56:52 +02:00
);
};
const AnastasisClientImpl: FunctionalComponent = () => {
2021-10-22 06:31:46 +02:00
const reducer = useAnastasisContext()
if (!reducer) {
return <p>Fatal: Reducer must be in context.</p>;
}
const state = reducer.currentReducerState;
if (!state) {
return <StartScreen />;
2021-10-19 15:56:52 +02:00
}
console.log("state", reducer.currentReducerState);
if (
2021-10-22 06:31:46 +02:00
state.backup_state === BackupStates.ContinentSelecting ||
state.recovery_state === RecoveryStates.ContinentSelecting
2021-10-19 15:56:52 +02:00
) {
return (
2021-10-22 06:31:46 +02:00
<ContinentSelectionScreen />
);
2021-10-19 15:56:52 +02:00
}
if (
2021-10-22 06:31:46 +02:00
state.backup_state === BackupStates.CountrySelecting ||
state.recovery_state === RecoveryStates.CountrySelecting
2021-10-19 15:56:52 +02:00
) {
return (
2021-10-22 06:31:46 +02:00
<CountrySelectionScreen />
);
2021-10-19 15:56:52 +02:00
}
if (
2021-10-22 06:31:46 +02:00
state.backup_state === BackupStates.UserAttributesCollecting ||
state.recovery_state === RecoveryStates.UserAttributesCollecting
2021-10-19 15:56:52 +02:00
) {
return (
2021-10-22 06:31:46 +02:00
<AttributeEntryScreen />
);
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.backup_state === BackupStates.AuthenticationsEditing) {
2021-10-19 15:56:52 +02:00
return (
2021-10-22 06:31:46 +02:00
<AuthenticationEditorScreen />
2021-10-19 15:56:52 +02:00
);
}
2021-10-22 06:31:46 +02:00
if (state.backup_state === BackupStates.PoliciesReviewing) {
return (
2021-10-22 06:31:46 +02:00
<ReviewPoliciesScreen />
);
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.backup_state === BackupStates.SecretEditing) {
return <SecretEditorScreen />;
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.backup_state === BackupStates.BackupFinished) {
return <BackupFinishedScreen />;
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.backup_state === BackupStates.TruthsPaying) {
return <TruthsPayingScreen />;
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.backup_state === BackupStates.PoliciesPaying) {
return <PoliciesPayingScreen />;
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.recovery_state === RecoveryStates.SecretSelecting) {
return (
2021-10-22 06:31:46 +02:00
<SecretSelectionScreen />
);
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.recovery_state === RecoveryStates.ChallengeSelecting) {
return (
2021-10-22 06:31:46 +02:00
<ChallengeOverviewScreen />
);
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.recovery_state === RecoveryStates.ChallengeSolving) {
return <SolveScreen />;
2021-10-19 15:56:52 +02:00
}
2021-10-22 06:31:46 +02:00
if (state.recovery_state === RecoveryStates.RecoveryFinished) {
return (
2021-10-22 06:31:46 +02:00
<RecoveryFinishedScreen />
);
2021-10-19 15:56:52 +02:00
}
if (state.recovery_state === RecoveryStates.ChallengePaying) {
return <ChallengePayingScreen />;
}
2021-10-19 15:56:52 +02:00
console.log("unknown state", reducer.currentReducerState);
return (
<AnastasisClientFrame hideNav title="Bug">
<p>Bug: Unknown state.</p>
2021-10-22 06:31:46 +02:00
<div class="buttons is-right">
<button class="button" onClick={() => reducer.reset()}>Reset</button>
</div>
2021-10-19 15:56:52 +02:00
</AnastasisClientFrame>
);
};
/**
2021-10-22 06:31:46 +02:00
* Show a dismissible error banner if there is a current error.
2021-10-19 15:56:52 +02:00
*/
2021-10-22 06:31:46 +02:00
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>
);
2021-10-19 15:56:52 +02:00
}
export default AnastasisClient;