From 5883d42d800c7b444c59d626bcaa5abca7dc83d0 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 19 Oct 2021 10:56:52 -0300 Subject: add template from merchant backoffice --- packages/anastasis-webui/src/routes/home/index.tsx | 1025 -------------------- 1 file changed, 1025 deletions(-) delete mode 100644 packages/anastasis-webui/src/routes/home/index.tsx (limited to 'packages/anastasis-webui/src/routes/home/index.tsx') diff --git a/packages/anastasis-webui/src/routes/home/index.tsx b/packages/anastasis-webui/src/routes/home/index.tsx deleted file mode 100644 index 1351775b5..000000000 --- a/packages/anastasis-webui/src/routes/home/index.tsx +++ /dev/null @@ -1,1025 +0,0 @@ -import { - bytesToString, - canonicalJson, - decodeCrock, - encodeCrock, - stringToBytes, -} from "@gnu-taler/taler-util"; -import { - AuthMethod, - BackupStates, - ChallengeFeedback, - ChallengeInfo, - RecoveryStates, - ReducerStateBackup, - ReducerStateRecovery, -} from "anastasis-core"; -import { - FunctionalComponent, - ComponentChildren, - h, - createContext, -} from "preact"; -import { useState, useContext, useRef, useLayoutEffect } from "preact/hooks"; -import { - AnastasisReducerApi, - useAnastasisReducer, -} from "../../hooks/use-anastasis-reducer"; -import style from "./style.css"; - -const WithReducer = createContext(undefined); - -function isBackup(reducer: AnastasisReducerApi) { - return !!reducer.currentReducerState?.backup_state; -} - -interface CommonReducerProps { - reducer: AnastasisReducerApi; - reducerState: ReducerStateBackup | ReducerStateRecovery; -} - -function withProcessLabel(reducer: AnastasisReducerApi, text: string): string { - if (isBackup(reducer)) { - return "Backup: " + text; - } - return "Recovery: " + text; -} - -function ContinentSelection(props: CommonReducerProps) { - const { reducer, reducerState } = props; - const sel = (x: string) => - reducer.transition("select_continent", { continent: x }); - return ( - - {reducerState.continents.map((x: any) => ( - - ))} - - ); -} - -function CountrySelection(props: CommonReducerProps) { - const { reducer, reducerState } = props; - const sel = (x: any) => - reducer.transition("select_country", { - country_code: x.code, - currencies: [x.currency], - }); - return ( - - {reducerState.countries.map((x: any) => ( - - ))} - - ); -} - -interface SolveEntryProps { - reducer: AnastasisReducerApi; - challenge: ChallengeInfo; - feedback?: ChallengeFeedback; -} - -function SolveQuestionEntry(props: SolveEntryProps) { - const [answer, setAnswer] = useState(""); - const { reducer, challenge, feedback } = props; - const next = () => - reducer.transition("solve_challenge", { - answer, - }); - return ( - next()} - > -

Feedback: {JSON.stringify(feedback)}

-

Question: {challenge.instructions}

- -
- ); -} - -function SolveSmsEntry(props: SolveEntryProps) { - const [answer, setAnswer] = useState(""); - const { reducer, challenge, feedback } = props; - const next = () => - reducer.transition("solve_challenge", { - answer, - }); - return ( - next()} - > -

Feedback: {JSON.stringify(feedback)}

-

{challenge.instructions}

- -
- ); -} - -function SolvePostEntry(props: SolveEntryProps) { - const [answer, setAnswer] = useState(""); - const { reducer, challenge, feedback } = props; - const next = () => - reducer.transition("solve_challenge", { - answer, - }); - return ( - next()} - > -

Feedback: {JSON.stringify(feedback)}

-

{challenge.instructions}

- -
- ); -} - -function SolveEmailEntry(props: SolveEntryProps) { - const [answer, setAnswer] = useState(""); - const { reducer, challenge, feedback } = props; - const next = () => - reducer.transition("solve_challenge", { - answer, - }); - return ( - next()} - > -

Feedback: {JSON.stringify(feedback)}

-

{challenge.instructions}

- -
- ); -} - -function SolveUnsupportedEntry(props: SolveEntryProps) { - return ( - -

{JSON.stringify(props.challenge)}

-

Challenge not supported.

-
- ); -} - -function SecretEditor(props: BackupReducerProps) { - const { reducer } = props; - const [secretName, setSecretName] = useState( - props.backupState.secret_name ?? "", - ); - const [secretValue, setSecretValue] = useState( - props.backupState.core_secret?.value ?? "" ?? "", - ); - const secretNext = () => { - reducer.runTransaction(async (tx) => { - await tx.transition("enter_secret_name", { - name: secretName, - }); - await tx.transition("enter_secret", { - secret: { - value: encodeCrock(stringToBytes(secretValue)), - mime: "text/plain", - }, - expiration: { - t_ms: new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 5, - }, - }); - await tx.transition("next", {}); - }); - }; - return ( - secretNext()} - > -
- -
-
- -
-
- ); -} - -export interface BackupReducerProps { - reducer: AnastasisReducerApi; - backupState: ReducerStateBackup; -} - -function ReviewPolicies(props: BackupReducerProps) { - const { reducer, backupState } = props; - const authMethods = backupState.authentication_methods!; - return ( - - {backupState.policies?.map((p, i) => { - const policyName = p.methods - .map((x) => authMethods[x.authentication_method].type) - .join(" + "); - return ( -
-

- Policy #{i + 1}: {policyName} -

- Required Authentications: -
    - {p.methods.map((x) => { - const m = authMethods[x.authentication_method]; - return ( -
  • - {m.type} ({m.instructions}) at provider {x.provider} -
  • - ); - })} -
-
- -
-
- ); - })} -
- ); -} - -export interface RecoveryReducerProps { - reducer: AnastasisReducerApi; - recoveryState: ReducerStateRecovery; -} - -function SecretSelection(props: RecoveryReducerProps) { - const { reducer, recoveryState } = props; - const [selectingVersion, setSelectingVersion] = useState(false); - const [otherVersion, setOtherVersion] = useState( - recoveryState.recovery_document?.version ?? 0, - ); - const recoveryDocument = recoveryState.recovery_document!; - const [otherProvider, setOtherProvider] = useState(""); - function selectVersion(p: string, n: number) { - reducer.runTransaction(async (tx) => { - await tx.transition("change_version", { - version: n, - provider_url: p, - }); - setSelectingVersion(false); - }); - } - if (selectingVersion) { - return ( - -

Select a different version of the secret

- -
- - setOtherVersion(Number((e.target as HTMLInputElement).value)) - } - type="number" - /> - -
-
- -
-
- -
-
- ); - } - return ( - -

Provider: {recoveryDocument.provider_url}

-

Secret version: {recoveryDocument.version}

-

Secret name: {recoveryDocument.version}

- -
- ); -} - -interface AnastasisClientFrameProps { - onNext?(): void; - title: string; - children: ComponentChildren; - /** - * Should back/next buttons be provided? - */ - hideNav?: boolean; - /** - * Hide only the "next" button. - */ - hideNext?: boolean; -} - -function AnastasisClientFrame(props: AnastasisClientFrameProps) { - const reducer = useContext(WithReducer); - if (!reducer) { - return

Fatal: Reducer must be in context.

; - } - const next = () => { - if (props.onNext) { - props.onNext(); - } else { - reducer.transition("next", {}); - } - }; - const handleKeyPress = (e: h.JSX.TargetedKeyboardEvent) => { - 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} -
- ); -} - -function ChallengeOverview(props: RecoveryReducerProps) { - const { recoveryState, reducer } = props; - const policies = recoveryState.recovery_information!.policies; - const chArr = recoveryState.recovery_information!.challenges; - const challenges: { - [uuid: string]: { - type: string; - instructions: string; - cost: string; - }; - } = {}; - for (const ch of chArr) { - challenges[ch.uuid] = { - type: ch.type, - cost: ch.cost, - instructions: ch.instructions, - }; - } - return ( - -

Policies

- {policies.map((x, i) => { - return ( -
-

Policy #{i + 1}

- {x.map((x) => { - const ch = challenges[x.uuid]; - const feedback = recoveryState.challenge_feedback?.[x.uuid]; - return ( -
-

- {ch.type} ({ch.instructions}) -

-

Status: {feedback?.state ?? "unknown"}

- {feedback?.state !== "solved" ? ( - - ) : 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 ( - -

- Your backup of secret "{backupState.secret_name ?? "??"}" was - successful. -

-

The backup is stored by the following providers:

-
    - {Object.keys(backupState.success_details!).map((x, i) => { - const sd = backupState.success_details![x]; - return ( -
  • - {x} (Policy version {sd.policy_version}) -
  • - ); - })} -
- -
- ); - } - - if (reducerState.backup_state === BackupStates.TruthsPaying) { - const backupState: ReducerStateBackup = reducerState; - const payments = backupState.payments ?? []; - return ( - -

- Some of the providers require a payment to store the encrypted - authentication information. -

-
    - {payments.map((x) => { - return
  • {x}
  • ; - })} -
- -
- ); - } - - if (reducerState.backup_state === BackupStates.PoliciesPaying) { - const backupState: ReducerStateBackup = reducerState; - const payments = backupState.policy_payment_requests ?? []; - - return ( - -

- Some of the providers require a payment to store the encrypted - recovery document. -

-
    - {payments.map((x) => { - return ( -
  • - {x.provider}: {x.payto} -
  • - ); - })} -
- -
- ); - } - - if (reducerState.recovery_state === RecoveryStates.SecretSelecting) { - return ; - } - - if (reducerState.recovery_state === RecoveryStates.ChallengeSelecting) { - return ; - } - - if (reducerState.recovery_state === RecoveryStates.ChallengeSolving) { - const chArr = reducerState.recovery_information!.challenges; - const challengeFeedback = reducerState.challenge_feedback ?? {}; - const selectedUuid = reducerState.selected_challenge_uuid!; - const challenges: { - [uuid: string]: ChallengeInfo; - } = {}; - for (const ch of chArr) { - challenges[ch.uuid] = ch; - } - const selectedChallenge = challenges[selectedUuid]; - const dialogMap: Record h.JSX.Element> = { - question: SolveQuestionEntry, - sms: SolveSmsEntry, - email: SolveEmailEntry, - post: SolvePostEntry, - }; - const SolveDialog = - dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry; - return ( - - ); - } - - if (reducerState.recovery_state === RecoveryStates.RecoveryFinished) { - return ( - -

Recovery Finished

-

- Secret: {bytesToString(decodeCrock(reducerState.core_secret?.value!))} -

-
- ); - } - - console.log("unknown state", reducer.currentReducerState); - return ( - -

Bug: Unknown state.

- -
- ); -}; - -interface AuthMethodSetupProps { - method: string; - addAuthMethod: (x: any) => void; - cancel: () => void; -} - -function AuthMethodSmsSetup(props: AuthMethodSetupProps) { - const [mobileNumber, setMobileNumber] = useState(""); - const addSmsAuth = () => { - props.addAuthMethod({ - authentication_method: { - type: "sms", - instructions: `SMS to ${mobileNumber}`, - challenge: encodeCrock(stringToBytes(mobileNumber)), - }, - }); - }; - const inputRef = useRef(null); - useLayoutEffect(() => { - inputRef.current?.focus(); - }, []); - return ( - -
-

- For SMS authentication, you need to provide a mobile number. When - recovering your secret, you will be asked to enter the code you - receive via SMS. -

- -
- - -
-
-
- ); -} - -function AuthMethodQuestionSetup(props: AuthMethodSetupProps) { - const [questionText, setQuestionText] = useState(""); - const [answerText, setAnswerText] = useState(""); - const addQuestionAuth = () => - props.addAuthMethod({ - authentication_method: { - type: "question", - instructions: questionText, - challenge: encodeCrock(stringToBytes(answerText)), - }, - }); - return ( - -
-

- For security question authentication, you need to provide a question - and its answer. When recovering your secret, you will be shown the - question and you will need to type the answer exactly as you typed it - here. -

-
- -
-
- -
-
- - -
-
-
- ); -} - -function AuthMethodEmailSetup(props: AuthMethodSetupProps) { - const [email, setEmail] = useState(""); - return ( - -

- For email authentication, you need to provide an email address. When - recovering your secret, you will need to enter the code you receive by - email. -

-
- -
-
- - -
-
- ); -} - -function AuthMethodPostSetup(props: AuthMethodSetupProps) { - const [fullName, setFullName] = useState(""); - const [street, setStreet] = useState(""); - const [city, setCity] = useState(""); - const [postcode, setPostcode] = useState(""); - const [country, setCountry] = useState(""); - - const addPostAuth = () => { - const challengeJson = { - full_name: fullName, - street, - city, - postcode, - country, - }; - props.addAuthMethod({ - authentication_method: { - type: "email", - instructions: `Letter to address in postal code ${postcode}`, - challenge: encodeCrock(stringToBytes(canonicalJson(challengeJson))), - }, - }); - }; - - return ( -
-

Add {props.method} authentication

-
-

- For postal letter authentication, you need to provide a postal - address. When recovering your secret, you will be asked to enter a - code that you will receive in a letter to that address. -

-
- -
-
- -
-
- -
-
- -
-
- -
-
- - -
-
-
- ); -} - -function AuthMethodNotImplemented(props: AuthMethodSetupProps) { - return ( - -

This auth method is not implemented yet, please choose another one.

- -
- ); -} - -export interface AuthenticationEditorProps { - reducer: AnastasisReducerApi; - backupState: ReducerStateBackup; -} - -function AuthenticationEditor(props: AuthenticationEditorProps) { - const [selectedMethod, setSelectedMethod] = useState( - undefined, - ); - const { reducer, backupState } = props; - const providers = backupState.authentication_providers!; - const authAvailableSet = new Set(); - for (const provKey of Object.keys(providers)) { - const p = providers[provKey]; - if ("http_status" in p && (!("error_code" in p)) && p.methods) { - for (const meth of p.methods) { - authAvailableSet.add(meth.type); - } - } - } - if (selectedMethod) { - const cancel = () => setSelectedMethod(undefined); - const addMethod = (args: any) => { - reducer.transition("add_authentication", args); - setSelectedMethod(undefined); - }; - const methodMap: Record< - string, - (props: AuthMethodSetupProps) => h.JSX.Element - > = { - sms: AuthMethodSmsSetup, - question: AuthMethodQuestionSetup, - email: AuthMethodEmailSetup, - post: AuthMethodPostSetup, - }; - const AuthSetup = methodMap[selectedMethod] ?? AuthMethodNotImplemented; - return ( - - ); - } - function MethodButton(props: { method: string; label: String }) { - return ( - - ); - } - const configuredAuthMethods: AuthMethod[] = - backupState.authentication_methods ?? []; - const haveMethodsConfigured = configuredAuthMethods.length; - return ( - -
- - - - - - -
-

Configured authentication methods

- {haveMethodsConfigured ? ( - configuredAuthMethods.map((x, i) => { - return ( -

- {x.type} ({x.instructions}){" "} - -

- ); - }) - ) : ( -

No authentication methods configured yet.

- )} -
- ); -} - -export interface AttributeEntryProps { - reducer: AnastasisReducerApi; - reducerState: ReducerStateRecovery | ReducerStateBackup; -} - -function AttributeEntry(props: AttributeEntryProps) { - const { reducer, reducerState: backupState } = props; - const [attrs, setAttrs] = useState>( - props.reducerState.identity_attributes ?? {}, - ); - return ( - - reducer.transition("enter_user_attributes", { - identity_attributes: attrs, - }) - } - > - {backupState.required_attributes.map((x: any, i: number) => { - return ( - setAttrs({ ...attrs, [x.name]: v })} - spec={x} - value={attrs[x.name]} - /> - ); - })} - - ); -} - -interface LabeledInputProps { - label: string; - grabFocus?: boolean; - bind: [string, (x: string) => void]; -} - -function LabeledInput(props: LabeledInputProps) { - const inputRef = useRef(null); - useLayoutEffect(() => { - if (props.grabFocus) { - inputRef.current?.focus(); - } - }, []); - return ( - - ); -} - -export interface AttributeEntryFieldProps { - isFirst: boolean; - value: string; - setValue: (newValue: string) => void; - spec: any; -} - -function AttributeEntryField(props: AttributeEntryFieldProps) { - return ( -
- -
- ); -} - -interface ErrorBannerProps { - reducer: AnastasisReducerApi; -} - -/** - * Show a dismissable error banner if there is a current error. - */ -function ErrorBanner(props: ErrorBannerProps) { - const currentError = props.reducer.currentError; - if (currentError) { - return ( -
-

Error: {JSON.stringify(currentError)}

- -
- ); - } - return null; -} - -export default AnastasisClient; -- cgit v1.2.3