2022-01-24 18:39:27 +01:00
|
|
|
import { BackupStates, RecoveryStates } from "@gnu-taler/anastasis-core";
|
2021-10-19 15:56:52 +02:00
|
|
|
import {
|
2021-11-08 16:10:22 +01:00
|
|
|
ComponentChildren,
|
|
|
|
Fragment,
|
2021-10-21 13:11:17 +02:00
|
|
|
FunctionalComponent,
|
|
|
|
h,
|
2021-11-08 16:10:22 +01:00
|
|
|
VNode,
|
2021-10-19 15:56:52 +02:00
|
|
|
} from "preact";
|
2021-11-08 16:10:22 +01:00
|
|
|
import { useErrorBoundary } from "preact/hooks";
|
2021-11-03 21:30:11 +01:00
|
|
|
import { AsyncButton } from "../../components/AsyncButton";
|
2021-10-19 15:56:52 +02:00
|
|
|
import { Menu } from "../../components/menu";
|
2021-11-04 19:18:30 +01:00
|
|
|
import { Notifications } from "../../components/Notifications";
|
2021-11-08 16:10:22 +01:00
|
|
|
import {
|
|
|
|
AnastasisProvider,
|
|
|
|
useAnastasisContext,
|
|
|
|
} from "../../context/anastasis";
|
2021-10-19 15:56:52 +02:00
|
|
|
import {
|
|
|
|
AnastasisReducerApi,
|
2021-11-08 16:10:22 +01: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";
|
2021-10-26 17:08:03 +02:00
|
|
|
import { ChallengePayingScreen } from "./ChallengePayingScreen";
|
2021-10-19 15:56:52 +02:00
|
|
|
import { ContinentSelectionScreen } from "./ContinentSelectionScreen";
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-10-21 13:11:17 +02:00
|
|
|
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 {
|
2021-11-08 17:09:26 +01:00
|
|
|
onNext?(): Promise<void>;
|
2021-11-08 16:10:22 +01:00
|
|
|
/**
|
|
|
|
* Override for the "back" functionality.
|
|
|
|
*/
|
|
|
|
onBack?(): Promise<void>;
|
2021-10-19 15:56:52 +02:00
|
|
|
title: string;
|
|
|
|
children: ComponentChildren;
|
|
|
|
/**
|
|
|
|
* Should back/next buttons be provided?
|
|
|
|
*/
|
|
|
|
hideNav?: boolean;
|
|
|
|
/**
|
|
|
|
* Hide only the "next" button.
|
|
|
|
*/
|
2021-11-01 20:10:49 +01:00
|
|
|
hideNext?: string;
|
2021-10-19 15:56:52 +02:00
|
|
|
}
|
|
|
|
|
2021-10-21 13:11:17 +02:00
|
|
|
function ErrorBoundary(props: {
|
|
|
|
reducer: AnastasisReducerApi;
|
|
|
|
children: ComponentChildren;
|
2021-10-22 06:31:46 +02:00
|
|
|
}): VNode {
|
2021-10-21 13:11:17 +02:00
|
|
|
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>;
|
|
|
|
}
|
2021-11-03 21:30:11 +01:00
|
|
|
const next = async (): Promise<void> => {
|
2021-11-04 19:18:30 +01:00
|
|
|
if (props.onNext) {
|
|
|
|
await props.onNext();
|
|
|
|
} else {
|
|
|
|
await reducer.transition("next", {});
|
|
|
|
}
|
2021-10-19 15:56:52 +02:00
|
|
|
};
|
2021-10-21 13:11:17 +02:00
|
|
|
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
|
|
|
|
};
|
2021-10-21 13:11:17 +02:00
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<Menu title="Anastasis" />
|
2021-11-03 21:30:11 +01:00
|
|
|
<div class="home" onKeyPress={(e) => handleKeyPress(e)}>
|
|
|
|
<h1 class="title">{props.title}</h1>
|
2021-11-04 19:18:30 +01:00
|
|
|
<ErrorBanner />
|
2021-11-03 21:30:11 +01:00
|
|
|
<section class="section is-main-section">
|
2021-10-21 13:11:17 +02:00
|
|
|
{props.children}
|
|
|
|
{!props.hideNav ? (
|
2021-11-08 16:10:22 +01:00
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
marginTop: "2em",
|
|
|
|
display: "flex",
|
|
|
|
justifyContent: "space-between",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
class="button"
|
|
|
|
onClick={() => (props.onBack ?? reducer.back)()}
|
|
|
|
>
|
|
|
|
Back
|
|
|
|
</button>
|
|
|
|
<AsyncButton
|
|
|
|
class="button is-info"
|
|
|
|
data-tooltip={props.hideNext}
|
|
|
|
onClick={next}
|
|
|
|
disabled={props.hideNext !== undefined}
|
|
|
|
>
|
|
|
|
Next
|
|
|
|
</AsyncButton>
|
2021-10-21 13:11:17 +02:00
|
|
|
</div>
|
|
|
|
) : null}
|
2021-11-03 21:30:11 +01:00
|
|
|
</section>
|
2021-10-19 15:56:52 +02:00
|
|
|
</div>
|
2021-10-21 13:11:17 +02:00
|
|
|
</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}>
|
2021-10-21 13:11:17 +02:00
|
|
|
<ErrorBoundary reducer={reducer}>
|
|
|
|
<AnastasisClientImpl />
|
|
|
|
</ErrorBoundary>
|
2021-10-22 06:31:46 +02:00
|
|
|
</AnastasisProvider>
|
2021-10-19 15:56:52 +02:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2021-11-05 15:17:42 +01:00
|
|
|
function AnastasisClientImpl(): VNode {
|
2021-11-08 16:10:22 +01:00
|
|
|
const reducer = useAnastasisContext();
|
2021-10-22 06:31:46 +02:00
|
|
|
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 ||
|
2021-11-01 20:10:49 +01:00
|
|
|
state.recovery_state === RecoveryStates.ContinentSelecting ||
|
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
|
|
|
) {
|
2021-11-08 16:10:22 +01:00
|
|
|
return <ContinentSelectionScreen />;
|
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
|
|
|
) {
|
2021-11-08 16:10:22 +01:00
|
|
|
return <AttributeEntryScreen />;
|
2021-10-19 15:56:52 +02:00
|
|
|
}
|
2021-10-22 06:31:46 +02:00
|
|
|
if (state.backup_state === BackupStates.AuthenticationsEditing) {
|
2021-11-08 16:10:22 +01:00
|
|
|
return <AuthenticationEditorScreen />;
|
2021-10-19 15:56:52 +02:00
|
|
|
}
|
2021-10-22 06:31:46 +02:00
|
|
|
if (state.backup_state === BackupStates.PoliciesReviewing) {
|
2021-11-08 16:10:22 +01:00
|
|
|
return <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) {
|
2021-11-08 16:10:22 +01:00
|
|
|
return <SecretSelectionScreen />;
|
2021-10-19 15:56:52 +02:00
|
|
|
}
|
|
|
|
|
2021-10-22 06:31:46 +02:00
|
|
|
if (state.recovery_state === RecoveryStates.ChallengeSelecting) {
|
2021-11-08 16:10:22 +01:00
|
|
|
return <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) {
|
2021-11-08 16:10:22 +01:00
|
|
|
return <RecoveryFinishedScreen />;
|
2021-10-19 15:56:52 +02:00
|
|
|
}
|
2021-10-26 17:08:03 +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">
|
2021-11-08 16:10:22 +01:00
|
|
|
<button class="button" onClick={() => reducer.reset()}>
|
|
|
|
Reset
|
|
|
|
</button>
|
2021-10-22 06:31:46 +02:00
|
|
|
</div>
|
2021-10-19 15:56:52 +02:00
|
|
|
</AnastasisClientFrame>
|
|
|
|
);
|
2021-11-05 15:17:42 +01:00
|
|
|
}
|
2021-10-19 15:56:52 +02:00
|
|
|
|
|
|
|
/**
|
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;
|
2021-11-08 16:10:22 +01:00
|
|
|
return (
|
|
|
|
<Notifications
|
|
|
|
removeNotification={reducer.dismissError}
|
|
|
|
notifications={[
|
|
|
|
{
|
|
|
|
type: "ERROR",
|
|
|
|
message: `Error code: ${reducer.currentError.code}`,
|
|
|
|
description: reducer.currentError.hint,
|
|
|
|
},
|
|
|
|
]}
|
|
|
|
/>
|
2021-10-22 06:31:46 +02:00
|
|
|
);
|
2021-10-19 15:56:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export default AnastasisClient;
|