2021-10-19 15:56:52 +02:00
|
|
|
import {
|
2021-10-22 06:31:46 +02:00
|
|
|
BackupStates,
|
2021-11-08 13:56:06 +01:00
|
|
|
RecoveryStates
|
2021-10-22 06:31:46 +02:00
|
|
|
} from "anastasis-core";
|
|
|
|
import {
|
|
|
|
ComponentChildren, Fragment,
|
2021-10-21 13:11:17 +02:00
|
|
|
FunctionalComponent,
|
|
|
|
h,
|
2021-10-22 06:31:46 +02:00
|
|
|
VNode
|
2021-10-19 15:56:52 +02:00
|
|
|
} from "preact";
|
2021-10-21 13:11:17 +02:00
|
|
|
import {
|
2021-11-01 20:10:49 +01:00
|
|
|
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-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";
|
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 {
|
|
|
|
onNext?(): void;
|
|
|
|
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-01 20:10:49 +01:00
|
|
|
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
|
2021-10-26 17:08:03 +02:00
|
|
|
<button class="button" onClick={() => reducer.back()}>Back</button>
|
2021-11-05 15:17:42 +01:00
|
|
|
<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-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 ||
|
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-10-21 13:11:17 +02:00
|
|
|
return (
|
2021-11-01 20:10:49 +01:00
|
|
|
<ContinentSelectionScreen />
|
2021-10-21 13:11:17 +02:00
|
|
|
);
|
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-10-21 13:11:17 +02:00
|
|
|
return (
|
2021-10-22 06:31:46 +02:00
|
|
|
<AttributeEntryScreen />
|
2021-10-21 13:11:17 +02:00
|
|
|
);
|
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) {
|
2021-10-21 13:11:17 +02:00
|
|
|
return (
|
2021-10-22 06:31:46 +02:00
|
|
|
<ReviewPoliciesScreen />
|
2021-10-21 13:11:17 +02:00
|
|
|
);
|
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-10-21 13:11:17 +02:00
|
|
|
return (
|
2021-10-22 06:31:46 +02:00
|
|
|
<SecretSelectionScreen />
|
2021-10-21 13:11:17 +02:00
|
|
|
);
|
2021-10-19 15:56:52 +02:00
|
|
|
}
|
|
|
|
|
2021-10-22 06:31:46 +02:00
|
|
|
if (state.recovery_state === RecoveryStates.ChallengeSelecting) {
|
2021-10-21 13:11:17 +02:00
|
|
|
return (
|
2021-10-22 06:31:46 +02:00
|
|
|
<ChallengeOverviewScreen />
|
2021-10-21 13:11:17 +02:00
|
|
|
);
|
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-10-21 13:11:17 +02:00
|
|
|
return (
|
2021-10-22 06:31:46 +02:00
|
|
|
<RecoveryFinishedScreen />
|
2021-10-21 13:11:17 +02:00
|
|
|
);
|
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">
|
|
|
|
<button class="button" onClick={() => reducer.reset()}>Reset</button>
|
|
|
|
</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-04 19:18:30 +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;
|