From a82b5a6992fda61d6eaa0bb079e284805a394777 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Wed, 3 Nov 2021 17:30:11 -0300
Subject: feedback from meeting and editing policy

---
 .../anastasis-webui/src/components/AsyncButton.tsx | 51 ++++++++++++++++++++++
 1 file changed, 51 insertions(+)
 create mode 100644 packages/anastasis-webui/src/components/AsyncButton.tsx

(limited to 'packages/anastasis-webui/src/components/AsyncButton.tsx')

diff --git a/packages/anastasis-webui/src/components/AsyncButton.tsx b/packages/anastasis-webui/src/components/AsyncButton.tsx
new file mode 100644
index 000000000..5602715e4
--- /dev/null
+++ b/packages/anastasis-webui/src/components/AsyncButton.tsx
@@ -0,0 +1,51 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { ComponentChildren, h, VNode } from "preact";
+// import { LoadingModal } from "../modal";
+import { useAsync } from "../hooks/async";
+// import { Translate } from "../../i18n";
+
+type Props = {
+  children: ComponentChildren;
+  disabled: boolean;
+  onClick?: () => Promise<void>;
+  [rest: string]: any;
+};
+
+export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VNode {
+  const { isLoading, request } = useAsync(onClick);
+
+  // if (isSlow) {
+  //   return <LoadingModal onCancel={cancel} />;
+  // }
+  console.log(isLoading)
+  if (isLoading) {
+    
+    return <button class="button">Loading...</button>;
+  }
+
+  return <span {...rest}>
+    <button class="button is-info" onClick={request} disabled={disabled}>
+      {children}
+    </button>
+  </span>;
+}
-- 
cgit v1.2.3


From ae0a35df2b2934c517954d2a73af4cc6e1734e30 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Thu, 4 Nov 2021 15:17:57 -0300
Subject: async, onInput, and some fixes

---
 .../anastasis-webui/src/components/AsyncButton.tsx |  4 +-
 .../src/components/fields/DateInput.tsx            |  2 +-
 .../src/components/fields/EmailInput.tsx           |  2 +-
 .../src/components/fields/NumberInput.tsx          |  2 +-
 .../src/components/fields/TextInput.tsx            |  2 +-
 .../src/components/picker/DatePicker.tsx           |  2 +-
 packages/anastasis-webui/src/hooks/async.ts        |  4 +-
 .../src/pages/home/AttributeEntryScreen.tsx        |  4 +-
 .../src/pages/home/AuthenticationEditorScreen.tsx  |  4 +-
 .../src/pages/home/ChallengeOverviewScreen.tsx     |  1 -
 .../src/pages/home/ContinentSelectionScreen.tsx    |  4 +-
 .../src/pages/home/RecoveryFinishedScreen.tsx      |  3 +
 .../src/pages/home/SecretEditorScreen.tsx          | 31 ++++++----
 .../src/pages/home/SecretSelectionScreen.tsx       | 71 ++++++++++++----------
 .../anastasis-webui/src/pages/home/SolveScreen.tsx |  3 +
 packages/anastasis-webui/src/pages/home/index.tsx  |  6 +-
 packages/anastasis-webui/src/utils/index.tsx       |  8 +--
 packages/taler-wallet-webextension/package.json    |  4 +-
 pnpm-lock.yaml                                     | 49 +++++----------
 19 files changed, 101 insertions(+), 105 deletions(-)

(limited to 'packages/anastasis-webui/src/components/AsyncButton.tsx')

diff --git a/packages/anastasis-webui/src/components/AsyncButton.tsx b/packages/anastasis-webui/src/components/AsyncButton.tsx
index 5602715e4..af85016e8 100644
--- a/packages/anastasis-webui/src/components/AsyncButton.tsx
+++ b/packages/anastasis-webui/src/components/AsyncButton.tsx
@@ -37,9 +37,7 @@ export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VN
   // if (isSlow) {
   //   return <LoadingModal onCancel={cancel} />;
   // }
-  console.log(isLoading)
-  if (isLoading) {
-    
+  if (isLoading) {    
     return <button class="button">Loading...</button>;
   }
 
diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx
index c406b85d1..3148c953f 100644
--- a/packages/anastasis-webui/src/components/fields/DateInput.tsx
+++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx
@@ -41,7 +41,7 @@ export function DateInput(props: DateInputProps): VNode {
             type="text"
             class={showError ? 'input is-danger' : 'input'}
             value={value}
-            onChange={(e) => {
+            onInput={(e) => {
               const text = e.currentTarget.value
               setDirty(true)
               props.bind[1](text);
diff --git a/packages/anastasis-webui/src/components/fields/EmailInput.tsx b/packages/anastasis-webui/src/components/fields/EmailInput.tsx
index e0fca0f46..e21418fea 100644
--- a/packages/anastasis-webui/src/components/fields/EmailInput.tsx
+++ b/packages/anastasis-webui/src/components/fields/EmailInput.tsx
@@ -34,7 +34,7 @@ export function EmailInput(props: TextInputProps): VNode {
         placeholder={props.placeholder}
         type="email"
         class={showError ? 'input is-danger' : 'input'}
-        onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
+        onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
         ref={inputRef}
         style={{ display: "block" }} />
     </div>
diff --git a/packages/anastasis-webui/src/components/fields/NumberInput.tsx b/packages/anastasis-webui/src/components/fields/NumberInput.tsx
index 2b6cdcd2c..2afb242b8 100644
--- a/packages/anastasis-webui/src/components/fields/NumberInput.tsx
+++ b/packages/anastasis-webui/src/components/fields/NumberInput.tsx
@@ -33,7 +33,7 @@ export function NumberInput(props: TextInputProps): VNode {
         type="number"
         placeholder={props.placeholder}
         class={showError ? 'input is-danger' : 'input'}
-        onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
+        onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
         ref={inputRef}
         style={{ display: "block" }} />
     </div>
diff --git a/packages/anastasis-webui/src/components/fields/TextInput.tsx b/packages/anastasis-webui/src/components/fields/TextInput.tsx
index 4bb785cd3..c093689c5 100644
--- a/packages/anastasis-webui/src/components/fields/TextInput.tsx
+++ b/packages/anastasis-webui/src/components/fields/TextInput.tsx
@@ -32,7 +32,7 @@ export function TextInput(props: TextInputProps): VNode {
         value={value}
         placeholder={props.placeholder}
         class={showError ? 'input is-danger' : 'input'}
-        onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
+        onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}}
         ref={inputRef}
         style={{ display: "block" }} />
     </div>
diff --git a/packages/anastasis-webui/src/components/picker/DatePicker.tsx b/packages/anastasis-webui/src/components/picker/DatePicker.tsx
index a94b3708e..eb5d8145d 100644
--- a/packages/anastasis-webui/src/components/picker/DatePicker.tsx
+++ b/packages/anastasis-webui/src/components/picker/DatePicker.tsx
@@ -214,7 +214,7 @@ export class DatePicker extends Component<Props, State> {
     // }
   }
 
-  constructor(props) {
+  constructor(props: any) {
     super(props);
 
     this.closeDatePicker = this.closeDatePicker.bind(this);
diff --git a/packages/anastasis-webui/src/hooks/async.ts b/packages/anastasis-webui/src/hooks/async.ts
index f142a5dc5..ea3ff6acf 100644
--- a/packages/anastasis-webui/src/hooks/async.ts
+++ b/packages/anastasis-webui/src/hooks/async.ts
@@ -43,14 +43,14 @@ export function useAsync<T>(fn?: (...args: any) => Promise<T>, { slowTolerance:
   const request = async (...args: any) => {
     if (!fn) return;
     setLoading(true);
-    console.log("loading true")
     const handler = setTimeout(() => {
       setSlow(true)
     }, tooLong)
 
     try {
+      console.log("calling async", args)
       const result = await fn(...args);
-      console.log(result)
+      console.log("async back", result)
       setData(result);
     } catch (error) {
       setError(error);
diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
index 52046b216..f86994c97 100644
--- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
@@ -48,10 +48,10 @@ export function AttributeEntryScreen(): VNode {
       })}
     >
       <div class="columns" style={{ maxWidth: 'unset' }}>
-        <div class="column is-one-third">
+        <div class="column is-half">
           {fieldList}
         </div>
-        <div class="column is-two-third" >
+        <div class="column is-is-half" >
           <p>This personal information will help to locate your secret.</p>
           <h1 class="title">This stays private</h1>
           <p>The information you have entered here:</p>
diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx
index b95d3f1e3..93ca81194 100644
--- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx
@@ -135,7 +135,7 @@ export function AuthenticationEditorScreen(): VNode {
   return (
     <AnastasisClientFrame title="Backup: Configure Authentication Methods" hideNext={errors}>
       <div class="columns">
-        <div class="column one-third">
+        <div class="column is-half">
           <div>
             {getKeys(authMethods).map(method => <MethodButton key={method} method={method} />)}
           </div>
@@ -152,7 +152,7 @@ export function AuthenticationEditorScreen(): VNode {
             </p>
           </ConfirmModal>}
         </div>
-        <div class="column two-third">
+        <div class="column is-half">
           <p class="block">
             When recovering your wallet, you will be asked to verify your identity via the methods you configure here.
             The list of authentication method is defined by the backup provider list.
diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx
index 7bafbe06a..ed34bbde2 100644
--- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
 import { ChallengeFeedback, ChallengeFeedbackStatus } from "anastasis-core";
 import { h, VNode } from "preact";
 import { useAnastasisContext } from "../../context/anastasis";
diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
index 4ab0e6a9b..0e43f982d 100644
--- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
@@ -1,5 +1,4 @@
 /* eslint-disable @typescript-eslint/camelcase */
-import { BackupStates, ContinentInfo, RecoveryStates } from "anastasis-core";
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useAnastasisContext } from "../../context/anastasis";
@@ -9,7 +8,8 @@ export function ContinentSelectionScreen(): VNode {
   const reducer = useAnastasisContext()
 
   //FIXME: remove this when #7056 is fixed
-  const [countryCode, setCountryCode] = useState("")
+  const countryFromReducer = (reducer?.currentReducerState as any).selected_country || "" 
+  const [countryCode, setCountryCode] = useState( countryFromReducer )
 
   if (!reducer || !reducer.currentReducerState || !("continents" in reducer.currentReducerState)) {
     return <div />
diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx
index bbcaa10a5..3740592b1 100644
--- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx
@@ -29,6 +29,9 @@ export function RecoveryFinishedScreen(): VNode {
       <p>
         Secret: {secret}
       </p>
+      <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+        <button class="button" onClick={() => reducer.back()}>Back</button>
+      </div>
     </AnastasisClientFrame>
   );
 }
diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
index 915465c3f..1b36a1b21 100644
--- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
@@ -4,7 +4,8 @@ import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useAnastasisContext } from "../../context/anastasis";
 import {
-  AnastasisClientFrame} from "./index";
+  AnastasisClientFrame
+} from "./index";
 import { TextInput } from "../../components/fields/TextInput";
 import { FileInput } from "../../components/fields/FileInput";
 
@@ -12,12 +13,12 @@ export function SecretEditorScreen(): VNode {
   const reducer = useAnastasisContext()
   const [secretValue, setSecretValue] = useState("");
 
-  const currentSecretName = reducer?.currentReducerState 
-    && ("secret_name" in reducer.currentReducerState) 
+  const currentSecretName = reducer?.currentReducerState
+    && ("secret_name" in reducer.currentReducerState)
     && reducer.currentReducerState.secret_name;
 
   const [secretName, setSecretName] = useState(currentSecretName || "");
-  
+
   if (!reducer) {
     return <div>no reducer in context</div>
   }
@@ -25,8 +26,8 @@ export function SecretEditorScreen(): VNode {
     return <div>invalid state</div>
   }
 
-  const secretNext = (): void => {
-    reducer.runTransaction(async (tx) => {
+  const secretNext = async (): Promise<void> => {
+    return reducer.runTransaction(async (tx) => {
       await tx.transition("enter_secret_name", {
         name: secretName,
       });
@@ -44,25 +45,29 @@ export function SecretEditorScreen(): VNode {
   };
   return (
     <AnastasisClientFrame
-      title="Backup: Provide secret"
+      title="Backup: Provide secret to backup"
       onNext={() => secretNext()}
     >
       <div>
         <TextInput
-          label="Secret Name:"
+          label="Secret's name:"
           grabFocus
           bind={[secretName, setSecretName]}
         />
       </div>
       <div>
         <TextInput
-          label="Secret Value:"
-          bind={[secretValue, setSecretValue]}
-        /> or import a file
-        <FileInput 
-          label="Open file from your device"
+          label="Enter the secret as text:"
           bind={[secretValue, setSecretValue]}
         />
+        <div style={{display:'flex',}}>
+          or&nbsp; 
+          <FileInput
+            label="click here"
+            bind={[secretValue, setSecretValue]}
+          />
+          &nbsp;to import a file
+        </div>
       </div>
     </AnastasisClientFrame>
   );
diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
index d0b83bda5..e1aeb3939 100644
--- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
@@ -97,39 +97,44 @@ export function SecretSelectionScreen(): VNode {
   }
   return (
     <AnastasisClientFrame title="Recovery: Select secret">
-      <p>Secret found, you can select another version or continue to the challenges solving</p>
-      <table class="table">
-        <tr>
-          <td>
-            <b>Provider</b>
-            <span class="icon has-tooltip-right" data-tooltip="Service provider backing up your secret">
-              <i class="mdi mdi-information" />
-            </span>
-          </td>
-          <td>{recoveryDocument.provider_url}</td>
-          <td><a onClick={() => setSelectingVersion(true)}>use another provider</a></td>
-        </tr>
-        <tr>
-          <td>
-            <b>Secret version</b>
-            <span class="icon has-tooltip-right" data-tooltip="Secret version to be recovered">
-              <i class="mdi mdi-information" />
-            </span>
-          </td>
-          <td>{recoveryDocument.version}</td>
-          <td><a onClick={() => setSelectingVersion(true)}>use another version</a></td>
-        </tr>
-        <tr>
-          <td>
-            <b>Secret name</b>
-            <span class="icon has-tooltip-right" data-tooltip="Secret identifier">
-              <i class="mdi mdi-information" />
-            </span>
-          </td>
-          <td>{recoveryDocument.secret_name}</td>
-          <td> </td>
-        </tr>
-      </table>
+      <div class="columns">
+        <div class="column is-half">
+          <div class="box">
+            <h1 class="subtitle">{recoveryDocument.provider_url}</h1>
+            <table class="table">
+              <tr>
+                <td>
+                  <b>Secret version</b>
+                  <span class="icon has-tooltip-right" data-tooltip="Secret version to be recovered">
+                    <i class="mdi mdi-information" />
+                  </span>
+                </td>
+                <td>{recoveryDocument.version}</td>
+                <td><a onClick={() => setSelectingVersion(true)}>use another version</a></td>
+              </tr>
+              <tr>
+                <td>
+                  <b>Secret name</b>
+                  <span class="icon has-tooltip-right" data-tooltip="Secret identifier">
+                    <i class="mdi mdi-information" />
+                  </span>
+                </td>
+                <td>{recoveryDocument.secret_name}</td>
+                <td> </td>
+              </tr>
+            </table>
+            <div class="buttons is-right">
+              <button class="button" disabled onClick={() => reducer.reset()}>Use this provider</button>
+            </div>
+          </div>
+          <div class="buttons is-right">
+            <button class="button" disabled onClick={() => reducer.reset()}>Change provider</button>
+          </div>
+        </div>
+        <div class="column is-two-third">
+          <p>Secret found, you can select another version or continue to the challenges solving</p>
+        </div>
+      </div>
     </AnastasisClientFrame>
   );
 }
diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
index d4d9271b4..df55de6ab 100644
--- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
@@ -77,6 +77,9 @@ export function SolveScreen(): VNode {
     return (
       <AnastasisClientFrame hideNav title="Recovery problem">
         <div>invalid state</div>
+        <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+        <button class="button" onClick={() => reducer.back()}>Back</button>
+      </div>
       </AnastasisClientFrame>
     );
   }
diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx
index 415cf6e98..346738c26 100644
--- a/packages/anastasis-webui/src/pages/home/index.tsx
+++ b/packages/anastasis-webui/src/pages/home/index.tsx
@@ -96,12 +96,12 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
     return <p>Fatal: Reducer must be in context.</p>;
   }
   const next = async (): Promise<void> => {
-    return new Promise((res, rej) => {
+    return new Promise(async (res, rej) => {
       try {
         if (props.onNext) {
-          props.onNext();
+          await props.onNext();
         } else {
-          reducer.transition("next", {});
+          await reducer.transition("next", {});
         }
         res()
       } catch {
diff --git a/packages/anastasis-webui/src/utils/index.tsx b/packages/anastasis-webui/src/utils/index.tsx
index 244be8af8..9c01aa6ba 100644
--- a/packages/anastasis-webui/src/utils/index.tsx
+++ b/packages/anastasis-webui/src/utils/index.tsx
@@ -8,13 +8,13 @@ export function createExample<Props>(Component: FunctionalComponent<Props>, curr
     return <AnastasisProvider value={{
       currentReducerState,
       currentError: undefined,
-      back: () => { null },
-      dismissError: () => { null },
+      back: async () => { null },
+      dismissError: async () => { null },
       reset: () => { null },
-      runTransaction: () => { null },
+      runTransaction: async () => { null },
       startBackup: () => { null },
       startRecover: () => { null },
-      transition: () => { null },
+      transition: async () => { null },
     }}>
       <Component {...args} />
     </AnastasisProvider>
diff --git a/packages/taler-wallet-webextension/package.json b/packages/taler-wallet-webextension/package.json
index 4023e4ebd..3a43f1e76 100644
--- a/packages/taler-wallet-webextension/package.json
+++ b/packages/taler-wallet-webextension/package.json
@@ -45,7 +45,7 @@
     "@storybook/preact": "^6.2.9",
     "@testing-library/preact": "^2.0.1",
     "@types/chrome": "^0.0.128",
-    "@types/enzyme": "^3.10.8",
+    "@types/enzyme": "^3.10.10",
     "@types/history": "^4.7.8",
     "@types/jest": "^26.0.23",
     "@types/node": "^14.14.22",
@@ -80,4 +80,4 @@
       "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|po)$": "<rootDir>/tests/__mocks__/fileTransformer.js"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 100bde514..ce6296f3d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -341,7 +341,7 @@ importers:
       '@storybook/preact': ^6.2.9
       '@testing-library/preact': ^2.0.1
       '@types/chrome': ^0.0.128
-      '@types/enzyme': ^3.10.8
+      '@types/enzyme': ^3.10.10
       '@types/history': ^4.7.8
       '@types/jest': ^26.0.23
       '@types/node': ^14.14.22
@@ -397,7 +397,7 @@ importers:
       '@storybook/preact': 6.3.7_9cd0ede338ef3d2deb8dbc69bc115c66
       '@testing-library/preact': 2.0.1_preact@10.5.14
       '@types/chrome': 0.0.128
-      '@types/enzyme': 3.10.9
+      '@types/enzyme': 3.10.10
       '@types/history': 4.7.9
       '@types/jest': 26.0.24
       '@types/node': 14.17.10
@@ -9881,13 +9881,6 @@ packages:
       '@types/react': 17.0.34
     dev: true
 
-  /@types/enzyme/3.10.9:
-    resolution: {integrity: sha512-dx5UvcWe2Vtye6S9Hw2rFB7Ul9uMXOAje2FAbXvVYieQDNle9qPAo7DfvFMSztZ9NFiD3dVZ4JsRYGTrSLynJg==}
-    dependencies:
-      '@types/cheerio': 0.22.30
-      '@types/react': 17.0.19
-    dev: true
-
   /@types/estree/0.0.39:
     resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
     dev: true
@@ -10099,14 +10092,6 @@ packages:
       '@types/react': 17.0.34
     dev: true
 
-  /@types/react/17.0.19:
-    resolution: {integrity: sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A==}
-    dependencies:
-      '@types/prop-types': 15.7.4
-      '@types/scheduler': 0.16.2
-      csstype: 3.0.8
-    dev: true
-
   /@types/react/17.0.34:
     resolution: {integrity: sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==}
     dependencies:
@@ -11397,7 +11382,7 @@ packages:
   /axios/0.21.4:
     resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
     dependencies:
-      follow-redirects: 1.14.5
+      follow-redirects: 1.14.5_debug@4.3.2
     transitivePeerDependencies:
       - debug
     dev: true
@@ -13562,10 +13547,6 @@ packages:
     resolution: {integrity: sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==}
     dev: true
 
-  /csstype/3.0.8:
-    resolution: {integrity: sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==}
-    dev: true
-
   /csstype/3.0.9:
     resolution: {integrity: sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==}
     dev: true
@@ -15471,16 +15452,6 @@ packages:
         optional: true
     dev: false
 
-  /follow-redirects/1.14.5:
-    resolution: {integrity: sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==}
-    engines: {node: '>=4.0'}
-    peerDependencies:
-      debug: '*'
-    peerDependenciesMeta:
-      debug:
-        optional: true
-    dev: true
-
   /follow-redirects/1.14.5_debug@4.3.2:
     resolution: {integrity: sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==}
     engines: {node: '>=4.0'}
@@ -20713,7 +20684,7 @@ packages:
     resolution: {integrity: sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==}
     engines: {node: '>=6'}
     dependencies:
-      ts-pnp: 1.2.0_typescript@4.4.3
+      ts-pnp: 1.2.0_typescript@4.3.5
     transitivePeerDependencies:
       - typescript
     dev: true
@@ -24776,6 +24747,18 @@ packages:
       tslib: 2.3.1
     dev: true
 
+  /ts-pnp/1.2.0_typescript@4.3.5:
+    resolution: {integrity: sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
+    engines: {node: '>=6'}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      typescript: 4.3.5
+    dev: true
+
   /ts-pnp/1.2.0_typescript@4.4.3:
     resolution: {integrity: sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
     engines: {node: '>=6'}
-- 
cgit v1.2.3


From 9ba0e8597dc0cb902bec3cd8f1090fc5746f790c Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Thu, 4 Nov 2021 15:18:30 -0300
Subject: error notifications

---
 .../anastasis-webui/src/components/AsyncButton.tsx |  2 +-
 .../src/components/Notifications.tsx               | 59 ++++++++++++++++++++++
 .../src/pages/home/RecoveryFinishedScreen.tsx      |  7 ++-
 .../anastasis-webui/src/pages/home/SolveScreen.tsx |  9 ++--
 .../anastasis-webui/src/pages/home/StartScreen.tsx |  4 +-
 packages/anastasis-webui/src/pages/home/index.tsx  | 32 +++++-------
 6 files changed, 84 insertions(+), 29 deletions(-)
 create mode 100644 packages/anastasis-webui/src/components/Notifications.tsx

(limited to 'packages/anastasis-webui/src/components/AsyncButton.tsx')

diff --git a/packages/anastasis-webui/src/components/AsyncButton.tsx b/packages/anastasis-webui/src/components/AsyncButton.tsx
index af85016e8..bb2cc0fd8 100644
--- a/packages/anastasis-webui/src/components/AsyncButton.tsx
+++ b/packages/anastasis-webui/src/components/AsyncButton.tsx
@@ -26,7 +26,7 @@ import { useAsync } from "../hooks/async";
 
 type Props = {
   children: ComponentChildren;
-  disabled: boolean;
+  disabled?: boolean;
   onClick?: () => Promise<void>;
   [rest: string]: any;
 };
diff --git a/packages/anastasis-webui/src/components/Notifications.tsx b/packages/anastasis-webui/src/components/Notifications.tsx
new file mode 100644
index 000000000..c916020d7
--- /dev/null
+++ b/packages/anastasis-webui/src/components/Notifications.tsx
@@ -0,0 +1,59 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { h, VNode } from "preact";
+
+export interface Notification {
+  message: string;
+  description?: string | VNode;
+  type: MessageType;
+}
+
+export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS'
+
+interface Props {
+  notifications: Notification[];
+  removeNotification?: (n: Notification) => void;
+}
+
+function messageStyle(type: MessageType): string {
+  switch (type) {
+    case "INFO": return "message is-info";
+    case "WARN": return "message is-warning";
+    case "ERROR": return "message is-danger";
+    case "SUCCESS": return "message is-success";
+    default: return "message"
+  }
+}
+
+export function Notifications({ notifications, removeNotification }: Props): VNode {
+  return <div class="block">
+    {notifications.map((n,i) => <article key={i} class={messageStyle(n.type)}>
+      <div class="message-header">
+        <p>{n.message}</p>
+        <button class="delete" onClick={() => removeNotification && removeNotification(n)} />
+      </div>
+      {n.description && <div class="message-body">
+        {n.description}
+      </div>}
+    </article>)}
+  </div>
+}
\ No newline at end of file
diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx
index 3740592b1..a61ef9efa 100644
--- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx
@@ -15,15 +15,18 @@ export function RecoveryFinishedScreen(): VNode {
   if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) {
     return <div>invalid state</div>
   }
-  const encodedSecret = reducer.currentReducerState.core_secret?.value
+  const encodedSecret = reducer.currentReducerState.core_secret
   if (!encodedSecret) {
     return <AnastasisClientFrame title="Recovery Problem" hideNav>
       <p>
         Secret not found
       </p>
+      <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+        <button class="button" onClick={() => reducer.back()}>Back</button>
+      </div>
     </AnastasisClientFrame>
   }
-  const secret = bytesToString(decodeCrock(encodedSecret))
+  const secret = bytesToString(decodeCrock(encodedSecret.value))
   return (
     <AnastasisClientFrame title="Recovery Finished" hideNav>
       <p>
diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
index df55de6ab..fae1b5631 100644
--- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
@@ -6,6 +6,7 @@ import {
   ChallengeFeedbackStatus,
   ChallengeInfo,
 } from "../../../../anastasis-core/lib";
+import { AsyncButton } from "../../components/AsyncButton";
 import { TextInput } from "../../components/fields/TextInput";
 import { useAnastasisContext } from "../../context/anastasis";
 
@@ -106,8 +107,8 @@ export function SolveScreen(): VNode {
       ? SolveUndefinedEntry
       : dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry;
 
-  function onNext(): void {
-    reducer?.transition("solve_challenge", { answer });
+  async function onNext(): Promise<void> {
+    return reducer?.transition("solve_challenge", { answer });
   }
   function onCancel(): void {
     reducer?.back();
@@ -136,9 +137,9 @@ export function SolveScreen(): VNode {
         <button class="button" onClick={onCancel}>
           Cancel
         </button>
-        <button class="button is-info" onClick={onNext}>
+        <AsyncButton onClick={onNext} disabled={false}>
           Confirm
-        </button>
+        </AsyncButton>
       </div>
     </AnastasisClientFrame>
   );
diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.tsx
index 6e97eb586..d53df4cae 100644
--- a/packages/anastasis-webui/src/pages/home/StartScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/StartScreen.tsx
@@ -25,10 +25,10 @@ export function StartScreen(): VNode {
               <span>Recover a secret</span>
             </button>
 
-            <button class="button">
+            {/* <button class="button">
               <div class="icon"><i class="mdi mdi-file" /></div>
               <span>Restore a session</span>
-            </button>
+            </button> */}
           </div>
 
         </div>
diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx
index 346738c26..6ebc2a6e9 100644
--- a/packages/anastasis-webui/src/pages/home/index.tsx
+++ b/packages/anastasis-webui/src/pages/home/index.tsx
@@ -15,6 +15,7 @@ import {
 } from "preact/hooks";
 import { AsyncButton } from "../../components/AsyncButton";
 import { Menu } from "../../components/menu";
+import { Notifications } from "../../components/Notifications";
 import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis";
 import {
   AnastasisReducerApi,
@@ -96,18 +97,11 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
     return <p>Fatal: Reducer must be in context.</p>;
   }
   const next = async (): Promise<void> => {
-    return new Promise(async (res, rej) => {
-      try {
-        if (props.onNext) {
-          await props.onNext();
-        } else {
-          await reducer.transition("next", {});
-        }
-        res()
-      } catch {
-        rej()
-      }
-    })
+    if (props.onNext) {
+      await props.onNext();
+    } else {
+      await reducer.transition("next", {});
+    }
   };
   const handleKeyPress = (
     e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>,
@@ -120,8 +114,8 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
       <Menu title="Anastasis" />
       <div class="home" onKeyPress={(e) => handleKeyPress(e)}>
         <h1 class="title">{props.title}</h1>
+        <ErrorBanner />
         <section class="section is-main-section">
-          <ErrorBanner />
           {props.children}
           {!props.hideNav ? (
             <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
@@ -242,13 +236,11 @@ const AnastasisClientImpl: FunctionalComponent = () => {
 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>
+  return (<Notifications removeNotification={reducer.dismissError} notifications={[{
+    type: "ERROR",
+    message: `Error code: ${reducer.currentError.code}`,
+    description: reducer.currentError.hint
+  }]} />
   );
 }
 
-- 
cgit v1.2.3


From 543ed2a106f3bab28bc0f4b97ade00afb524b4ee Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Fri, 5 Nov 2021 11:17:42 -0300
Subject: selecting provider

---
 .../anastasis-webui/src/components/AsyncButton.tsx |   4 +-
 .../pages/home/SecretSelectionScreen.stories.tsx   |   3 +-
 .../src/pages/home/SecretSelectionScreen.tsx       | 180 ++++++++++++---------
 .../anastasis-webui/src/pages/home/SolveScreen.tsx |   2 +-
 packages/anastasis-webui/src/pages/home/index.tsx  |   6 +-
 5 files changed, 109 insertions(+), 86 deletions(-)

(limited to 'packages/anastasis-webui/src/components/AsyncButton.tsx')

diff --git a/packages/anastasis-webui/src/components/AsyncButton.tsx b/packages/anastasis-webui/src/components/AsyncButton.tsx
index bb2cc0fd8..92bef2219 100644
--- a/packages/anastasis-webui/src/components/AsyncButton.tsx
+++ b/packages/anastasis-webui/src/components/AsyncButton.tsx
@@ -41,8 +41,8 @@ export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VN
     return <button class="button">Loading...</button>;
   }
 
-  return <span {...rest}>
-    <button class="button is-info" onClick={request} disabled={disabled}>
+  return <span data-tooltip={rest['data-tooltip']} style={{marginLeft: 5}}>
+    <button {...rest} onClick={request} disabled={disabled}>
       {children}
     </button>
   </span>;
diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
index f56939090..6919eebad 100644
--- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
 /*
  This file is part of GNU Taler
  (C) 2021 Taler Systems S.A.
@@ -40,7 +39,7 @@ export default {
 export const Example = createExample(TestedComponent, {
   ...reducerStatesExample.secretSelection,
   recovery_document: {
-    provider_url: 'http://anastasis.url/',
+    provider_url: 'https://kudos.demo.anastasis.lu/',
     secret_name: 'secretName',
     version: 1,
   },
diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
index e8af75229..8aa5ed2f7 100644
--- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
-import { RecoveryInternalData } from "anastasis-core";
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { AsyncButton } from "../../components/AsyncButton";
@@ -9,15 +7,12 @@ import { AnastasisClientFrame } from "./index";
 
 export function SecretSelectionScreen(): VNode {
   const [selectingVersion, setSelectingVersion] = useState<boolean>(false);
-  const [otherProvider, setOtherProvider] = useState<string>("");
   const reducer = useAnastasisContext()
 
   const currentVersion = (reducer?.currentReducerState
     && ("recovery_document" in reducer.currentReducerState)
     && reducer.currentReducerState.recovery_document?.version) || 0;
 
-  const [otherVersion, setOtherVersion] = useState("");
-
   if (!reducer) {
     return <div>no reducer in context</div>
   }
@@ -25,7 +20,7 @@ export function SecretSelectionScreen(): VNode {
     return <div>invalid state</div>
   }
 
-  async function selectVersion(p: string, n: number): Promise<void> {
+  async function doSelectVersion(p: string, n: number): Promise<void> {
     if (!reducer) return Promise.resolve();
     return reducer.runTransaction(async (tx) => {
       await tx.transition("change_version", {
@@ -38,74 +33,20 @@ export function SecretSelectionScreen(): VNode {
 
   const providerList = Object.keys(reducer.currentReducerState.authentication_providers ?? {})
   const recoveryDocument = reducer.currentReducerState.recovery_document
-  if (!recoveryDocument) {
-    return (
-      <AnastasisClientFrame hideNext="Recovery document not found" title="Recovery: Problem">
-        <p>No recovery document found, try with another provider</p>
-        <table class="table">
-          <tr>
-            <td><b>Provider</b></td>
-            <td>
-              <select onChange={(e) => setOtherProvider((e.target as any).value)}>
-                <option key="none" disabled selected > Choose another provider </option>
-                {providerList.map(prov => (
-                  <option key={prov} value={prov}>
-                    {prov}
-                  </option>
-                ))}
-              </select>
-            </td>
-          </tr>
-        </table>
-      </AnastasisClientFrame>
-    )
-  }
-  if (selectingVersion) {
-    return (
-      <AnastasisClientFrame hideNav title="Recovery: Select secret">
-        <div class="columns">
-          <div class="column">
-            <div class="box">
-              <h1 class="subtitle">Provider {recoveryDocument.provider_url}</h1>
-              <div class="block">
-                {currentVersion === 0 ? <p>
-                  Set to recover the latest version
-                </p> : <p>
-                  Set to recover the version number {currentVersion}
-                </p>}
-                <p>Specify other version below or use the latest</p>
-              </div>
-              <div class="container">
-                <NumberInput
-                  label="Version"
-                  placeholder="version number to recover"
-                  grabFocus
-                  bind={[otherVersion, setOtherVersion]} />
-              </div>
-            </div>
-            <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
-              <button class="button" onClick={() => setSelectingVersion(false)}>Cancel</button>
-              <div class="buttons">
-                <button class="button " onClick={() => selectVersion(otherProvider, 0)}>Use latest</button>
-                <AsyncButton onClick={() => selectVersion(otherProvider, parseInt(otherVersion, 10))}>Confirm</AsyncButton>
-              </div>
-            </div>
-          </div>
-          <div class="column">
-            .
-          </div>
-        </div>
 
-      </AnastasisClientFrame>
-    );
+  if (!recoveryDocument) {
+    return <ChooseAnotherProviderScreen
+      providers={providerList} selected=""
+      onChange={(newProv) => doSelectVersion(newProv, 0)}
+    />
   }
 
-  function ProviderCard({ provider, selected }: { selected?: boolean; provider: string }): VNode {
-    return <button key={provider} class="button block is-fullwidth" style={{ border: selected ? '2px solid green' : undefined }}
-      onClick={(e) => selectVersion(provider, currentVersion)}
-    >
-      {provider}
-    </button>
+  if (selectingVersion) {
+    return <SelectOtherVersionProviderScreen providers={providerList}
+      provider={recoveryDocument.provider_url} version={recoveryDocument.version}
+      onCancel={() => setSelectingVersion(false)}
+      onConfirm={doSelectVersion}
+    />
   }
 
   return (
@@ -125,13 +66,6 @@ export function SecretSelectionScreen(): VNode {
               <button class="button" onClick={(e) => setSelectingVersion(true)}>Change secret's version</button>
             </div>
           </div>
-          <p class="block">
-            Or you can use another provider
-          </p>
-          {providerList.map(prov => {
-            if (recoveryDocument.provider_url === prov) return;
-            return <ProviderCard key={prov} provider={prov} />
-          })}
         </div>
         <div class="column">
           <p>Secret found, you can select another version or continue to the challenges solving</p>
@@ -140,3 +74,93 @@ export function SecretSelectionScreen(): VNode {
     </AnastasisClientFrame>
   );
 }
+
+
+function ChooseAnotherProviderScreen({ providers, selected, onChange }: { selected: string; providers: string[]; onChange: (prov: string) => void }): VNode {
+  return (
+    <AnastasisClientFrame hideNext="Recovery document not found" title="Recovery: Problem">
+      <p>No recovery document found, try with another provider</p>
+      <div class="field">
+        <label class="label">Provider</label>
+        <div class="control is-expanded has-icons-left">
+          <div class="select is-fullwidth">
+            <select onChange={(e) => onChange(e.currentTarget.value)} value={selected}>
+              <option key="none" disabled selected value=""> Choose a provider </option>
+              {providers.map(prov => (
+                <option key={prov} value={prov}>
+                  {prov}
+                </option>
+              ))}
+            </select>
+            <div class="icon is-small is-left">
+              <i class="mdi mdi-earth" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </AnastasisClientFrame>
+  );
+}
+
+function SelectOtherVersionProviderScreen({ providers, provider, version, onConfirm, onCancel }: { onCancel: () => void; provider: string; version: number; providers: string[]; onConfirm: (prov: string, v: number) => Promise<void>; }): VNode {
+  const [otherProvider, setOtherProvider] = useState<string>(provider);
+  const [otherVersion, setOtherVersion] = useState(`${version}`);
+
+  return (
+    <AnastasisClientFrame hideNav title="Recovery: Select secret">
+      <div class="columns">
+        <div class="column">
+          <div class="box">
+            <h1 class="subtitle">Provider {otherProvider}</h1>
+            <div class="block">
+              {version === 0 ? <p>
+                Set to recover the latest version
+              </p> : <p>
+                Set to recover the version number {version}
+              </p>}
+              <p>Specify other version below or use the latest</p>
+            </div>
+
+            <div class="field">
+              <label class="label">Provider</label>
+              <div class="control is-expanded has-icons-left">
+                <div class="select is-fullwidth">
+                  <select onChange={(e) => setOtherProvider(e.currentTarget.value)} value={otherProvider}>
+                    <option key="none" disabled selected value=""> Choose a provider </option>
+                    {providers.map(prov => (
+                      <option key={prov} value={prov}>
+                        {prov}
+                      </option>
+                    ))}
+                  </select>
+                  <div class="icon is-small is-left">
+                    <i class="mdi mdi-earth" />
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div class="container">
+              <NumberInput
+                label="Version"
+                placeholder="version number to recover"
+                grabFocus
+                bind={[otherVersion, setOtherVersion]} />
+            </div>
+          </div>
+          <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+            <button class="button" onClick={onCancel}>Cancel</button>
+            <div class="buttons">
+              <AsyncButton class="button" onClick={() => onConfirm(otherProvider, 0)}>Use latest</AsyncButton>
+              <AsyncButton class="button is-info" onClick={() => onConfirm(otherProvider, parseInt(otherVersion, 10))}>Confirm</AsyncButton>
+            </div>
+          </div>
+        </div>
+        <div class="column">
+          .
+        </div>
+      </div>
+
+    </AnastasisClientFrame>
+  );
+
+}
diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
index fae1b5631..bc1a88db3 100644
--- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx
@@ -137,7 +137,7 @@ export function SolveScreen(): VNode {
         <button class="button" onClick={onCancel}>
           Cancel
         </button>
-        <AsyncButton onClick={onNext} disabled={false}>
+        <AsyncButton class="button is-info" onClick={onNext}>
           Confirm
         </AsyncButton>
       </div>
diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx
index 6ebc2a6e9..07bc7c604 100644
--- a/packages/anastasis-webui/src/pages/home/index.tsx
+++ b/packages/anastasis-webui/src/pages/home/index.tsx
@@ -120,7 +120,7 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
           {!props.hideNav ? (
             <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
               <button class="button" onClick={() => reducer.back()}>Back</button>
-              <AsyncButton data-tooltip={props.hideNext} onClick={next} disabled={props.hideNext !== undefined}>Next</AsyncButton>
+              <AsyncButton class="button is-info" data-tooltip={props.hideNext} onClick={next} disabled={props.hideNext !== undefined}>Next</AsyncButton>
             </div>
           ) : null}
         </section>
@@ -140,7 +140,7 @@ const AnastasisClient: FunctionalComponent = () => {
   );
 };
 
-const AnastasisClientImpl: FunctionalComponent = () => {
+function AnastasisClientImpl(): VNode {
   const reducer = useAnastasisContext()
   if (!reducer) {
     return <p>Fatal: Reducer must be in context.</p>;
@@ -228,7 +228,7 @@ const AnastasisClientImpl: FunctionalComponent = () => {
       </div>
     </AnastasisClientFrame>
   );
-};
+}
 
 /**
  * Show a dismissible error banner if there is a current error.
-- 
cgit v1.2.3

