diff options
Diffstat (limited to 'packages/anastasis-webui/src/pages/home')
15 files changed, 406 insertions, 167 deletions
diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx index 32d7817e3..549686616 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx @@ -52,8 +52,8 @@ export const Backup = createExample(TestedComponent, { uuid: 'asdasdsa2', widget: 'wid', }, { - name: 'date', - label: 'third', + name: 'birthdate', + label: 'birthdate', type: 'date', uuid: 'asdasdsa3', widget: 'calendar', diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index 2c7f54c5b..52046b216 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -7,6 +7,7 @@ import { AnastasisClientFrame, withProcessLabel } from "./index"; import { TextInput } from "../../components/fields/TextInput"; import { DateInput } from "../../components/fields/DateInput"; import { NumberInput } from "../../components/fields/NumberInput"; +import { isAfter, parse } from "date-fns"; export function AttributeEntryScreen(): VNode { const reducer = useAnastasisContext() @@ -46,15 +47,14 @@ export function AttributeEntryScreen(): VNode { identity_attributes: attrs, })} > - <div class="columns"> - <div class="column is-half"> + <div class="columns" style={{ maxWidth: 'unset' }}> + <div class="column is-one-third"> {fieldList} </div> - <div class="column is-half" > + <div class="column is-two-third" > <p>This personal information will help to locate your secret.</p> - <h1><b>This stay private</b></h1> - <p>The information you have entered here: - </p> + <h1 class="title">This stays private</h1> + <p>The information you have entered here:</p> <ul> <li> <span class="icon is-right"> @@ -111,15 +111,17 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode { bind={[props.value, props.setValue]} /> } - <span> + <div class="block"> + This stays private <span class="icon is-right"> <i class="mdi mdi-eye-off" /> </span> - This stay private - </span> + </div> </div> ); } +const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/ + function checkIfValid(value: string, spec: UserAttributeSpec): string | undefined { const pattern = spec['validation-regex'] @@ -136,5 +138,22 @@ function checkIfValid(value: string, spec: UserAttributeSpec): string | undefine if (!optional && !value) { return 'This value is required' } + if ("date" === spec.type) { + if (!YEAR_REGEX.test(value)) { + return "The date doesn't follow the format" + } + + try { + const v = parse(value, 'yyyy-MM-dd', new Date()); + if (Number.isNaN(v.getTime())) { + return "Some numeric values seems out of range for a date" + } + if ("birthdate" === spec.name && isAfter(v, new Date())) { + return "A birthdate cannot be in the future" + } + } catch (e) { + return "Could not parse the date" + } + } return undefined } diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index 4e7819a77..ab482044f 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -142,6 +142,10 @@ export function AuthenticationEditorScreen(): VNode { </div> <div class="column is-half"> When recovering your wallet, you will be asked to verify your identity via the methods you configure here. + + <b>Explain the exclamation marks</b> + + <a>Explain how to add providers</a> </div> </div> </AnastasisClientFrame> diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx index 70ac8157d..7938baca4 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx @@ -23,7 +23,7 @@ export function BackupFinishedScreen(): VNode { </p>} {details && <div class="block"> - <p>The backup is stored by the following providers:</p> + <p>The backup is stored by the following providers:</p> {Object.keys(details).map((x, i) => { const sd = details[x]; return ( @@ -31,11 +31,14 @@ export function BackupFinishedScreen(): VNode { {x} <p> version {sd.policy_version} - {sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd/MM/yyyy')}` : ' without expiration date'} + {sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd-MM-yyyy')}` : ' without expiration date'} </p> </div> ); })} </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/ContinentSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx index 2186eb42d..6bdb3515d 100644 --- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -19,12 +20,13 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { ReducerState } from 'anastasis-core'; import { createExample, reducerStatesExample } from '../../utils'; import { ContinentSelectionScreen as TestedComponent } from './ContinentSelectionScreen'; export default { - title: 'Pages/ContinentSelectionScreen', + title: 'Pages/Location', component: TestedComponent, args: { order: 2, @@ -35,6 +37,16 @@ export default { }, }; -export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectContinent); +export const BackupSelectContinent = createExample(TestedComponent, reducerStatesExample.backupSelectContinent); -export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent); +export const BackupSelectCountry = createExample(TestedComponent, { + ...reducerStatesExample.backupSelectContinent, + selected_continent: 'Testcontinent', +} as ReducerState); + +export const RecoverySelectContinent = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent); + +export const RecoverySelectCountry = createExample(TestedComponent, { + ...reducerStatesExample.recoverySelectContinent, + selected_continent: 'Testcontinent', +} as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx index 713655625..4ab0e6a9b 100644 --- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx @@ -36,20 +36,21 @@ export function ContinentSelectionScreen(): VNode { }) } - const step1 = reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting || - reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting; + // const step1 = reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting || + // reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting; const errors = !theCountry ? "Select a country" : undefined return ( - <AnastasisClientFrame hideNext={errors} title={withProcessLabel(reducer, "Select location")} onNext={selectCountryAction}> - <div class="columns"> - <div class="column is-half"> + <AnastasisClientFrame hideNext={errors} title={withProcessLabel(reducer, "Where do you live?")} onNext={selectCountryAction}> + + <div class="columns" > + <div class="column is-one-third"> <div class="field"> <label class="label">Continent</label> - <div class="control has-icons-left"> - <div class="select " > - <select onChange={(e) => selectContinent(e.currentTarget.value)} value={theContinent} disabled={!step1}> + <div class="control is-expanded has-icons-left"> + <div class="select is-fullwidth" > + <select onChange={(e) => selectContinent(e.currentTarget.value)} value={theContinent} > <option key="none" disabled selected value=""> Choose a continent </option> {continentList.map(prov => ( <option key={prov.name} value={prov.name}> @@ -61,18 +62,13 @@ export function ContinentSelectionScreen(): VNode { <i class="mdi mdi-earth" /> </div> </div> - {!step1 && <span class="control"> - <a class="button is-danger" onClick={() => reducer.back()}> - X - </a> - </span>} </div> </div> <div class="field"> <label class="label">Country</label> - <div class="control has-icons-left"> - <div class="select" > + <div class="control is-expanded has-icons-left"> + <div class="select is-fullwidth" > <select onChange={(e) => selectCountry((e.target as any).value)} disabled={!theContinent} value={theCountry?.code || ""}> <option key="none" disabled selected value=""> Choose a country </option> {countryList.map(prov => ( @@ -88,17 +84,17 @@ export function ContinentSelectionScreen(): VNode { </div> </div> - {theCountry && <div class="field"> + {/* {theCountry && <div class="field"> <label class="label">Available currencies:</label> <div class="control"> <input class="input is-small" type="text" readonly value={theCountry.currency} /> </div> - </div>} + </div>} */} </div> - <div class="column is-half"> + <div class="column is-two-third"> <p> - A location will help to define a common information that will be use to locate your secret and a currency - for payments if needed. + Your location will help us to determine which personal information + ask you for the next step. </p> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx deleted file mode 100644 index 3a642748a..000000000 --- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - 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 { createExample, reducerStatesExample } from '../../utils'; -import { CountrySelectionScreen as TestedComponent } from './CountrySelectionScreen'; - - -export default { - title: 'Pages/CountrySelectionScreen', - component: TestedComponent, - args: { - order: 3, - }, - argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, - }, -}; - -export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectCountry); -export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectCountry); diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx deleted file mode 100644 index b64e1a096..000000000 --- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { h, VNode } from "preact"; -import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame, withProcessLabel } from "./index"; - -export function CountrySelectionScreen(): VNode { - const reducer = useAnastasisContext() - if (!reducer) { - return <div>no reducer in context</div> - } - if (!reducer.currentReducerState || !("countries" in reducer.currentReducerState)) { - return <div>invalid state</div> - } - const sel = (x: any): void => reducer.transition("select_country", { - country_code: x.code, - currencies: [x.currency], - }); - return ( - <AnastasisClientFrame hideNext={"FIXME"} title={withProcessLabel(reducer, "Select Country")} > - <div style={{ display: 'flex', flexDirection: 'column' }}> - {reducer.currentReducerState.countries!.map((x: any) => ( - <div key={x.name}> - <button class="button" onClick={() => sel(x)} > - {x.name} ({x.currency}) - </button> - </div> - ))} - </div> - </AnastasisClientFrame> - ); -} diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx new file mode 100644 index 000000000..fc339e48e --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx @@ -0,0 +1,109 @@ +/* eslint-disable @typescript-eslint/camelcase */ +/* + 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 { ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../utils'; +import { EditPoliciesScreen as TestedComponent } from './EditPoliciesScreen'; + + +export default { + title: 'Pages/backup/ReviewPoliciesScreen/EditPoliciesScreen', + args: { + order: 6, + }, + component: TestedComponent, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +export const EditingAPolicy = createExample(TestedComponent, { + ...reducerStatesExample.policyReview, + policies: [{ + methods: [{ + authentication_method: 1, + provider: 'https://anastasis.demo.taler.net/' + }, { + authentication_method: 2, + provider: 'http://localhost:8086/' + }] + }, { + methods: [{ + authentication_method: 1, + provider: 'http://localhost:8086/' + }] + }], + authentication_methods: [{ + type: "email", + instructions: "Email to qwe@asd.com", + challenge: "E5VPA" + }, { + type: "totp", + instructions: "Response code for 'Anastasis'", + challenge: "E5VPA" + }, { + type: "sms", + instructions: "SMS to 6666-6666", + challenge: "" + }, { + type: "question", + instructions: "How did the chicken cross the road?", + challenge: "C5SP8" + }] +} as ReducerState, { index : 0}); + +export const CreatingAPolicy = createExample(TestedComponent, { + ...reducerStatesExample.policyReview, + policies: [{ + methods: [{ + authentication_method: 1, + provider: 'https://anastasis.demo.taler.net/' + }, { + authentication_method: 2, + provider: 'http://localhost:8086/' + }] + }, { + methods: [{ + authentication_method: 1, + provider: 'http://localhost:8086/' + }] + }], + authentication_methods: [{ + type: "email", + instructions: "Email to qwe@asd.com", + challenge: "E5VPA" + }, { + type: "totp", + instructions: "Response code for 'Anastasis'", + challenge: "E5VPA" + }, { + type: "sms", + instructions: "SMS to 6666-6666", + challenge: "" + }, { + type: "question", + instructions: "How did the chicken cross the road?", + challenge: "C5SP8" + }] +} as ReducerState, { index : 3}); + diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx new file mode 100644 index 000000000..85cc96c46 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx @@ -0,0 +1,133 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { AuthMethod, Policy } from "anastasis-core"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { useAnastasisContext } from "../../context/anastasis"; +import { authMethods, KnownAuthMethods } from "./authMethod"; +import { AnastasisClientFrame } from "./index"; + +export interface ProviderInfo { + url: string; + cost: string; + isFree: boolean; +} + +export type ProviderInfoByType = { + [type in KnownAuthMethods]?: ProviderInfo[]; +}; + +interface Props { + index: number; + cancel: () => void; + confirm: (changes: MethodProvider[]) => void; + +} + +export interface MethodProvider { + authentication_method: number; + provider: string; +} + +export function EditPoliciesScreen({ index: policy_index, cancel, confirm }: Props): VNode { + const [changedProvider, setChangedProvider] = useState<Array<string>>([]) + + const reducer = useAnastasisContext() + if (!reducer) { + return <div>no reducer in context</div> + } + if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { + return <div>invalid state</div> + } + + const selectableProviders: ProviderInfoByType = {} + const allProviders = Object.entries(reducer.currentReducerState.authentication_providers || {}) + for (let index = 0; index < allProviders.length; index++) { + const [url, status] = allProviders[index] + if ("methods" in status) { + status.methods.map(m => { + const type: KnownAuthMethods = m.type as KnownAuthMethods + const values = selectableProviders[type] || [] + const isFree = !m.usage_fee || m.usage_fee.endsWith(":0") + values.push({ url, cost: m.usage_fee, isFree }) + selectableProviders[type] = values + }) + } + } + + const allAuthMethods = reducer.currentReducerState.authentication_methods ?? []; + const policies = reducer.currentReducerState.policies ?? []; + const policy = policies[policy_index] + + for(let method_index = 0; method_index < allAuthMethods.length; method_index++ ) { + policy?.methods.find(m => m.authentication_method === method_index)?.provider + } + + function sendChanges(): void { + const newMethods: MethodProvider[] = [] + allAuthMethods.forEach((method, index) => { + const oldValue = policy?.methods.find(m => m.authentication_method === index) + if (changedProvider[index] === undefined && oldValue !== undefined) { + newMethods.push(oldValue) + } + if (changedProvider[index] !== undefined && changedProvider[index] !== "") { + newMethods.push({ + authentication_method: index, + provider: changedProvider[index] + }) + } + }) + confirm(newMethods) + } + + return <AnastasisClientFrame hideNav title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"}> + <section class="section"> + {!policy ? <p> + Creating a new policy #{policy_index} + </p> : <p> + Editing policy #{policy_index} + </p>} + {allAuthMethods.map((method, index) => { + //take the url from the updated change or from the policy + const providerURL = changedProvider[index] === undefined ? + policy?.methods.find(m => m.authentication_method === index)?.provider : + changedProvider[index]; + + const type: KnownAuthMethods = method.type as KnownAuthMethods + function changeProviderTo(url: string): void { + const copy = [...changedProvider] + copy[index] = url + setChangedProvider(copy) + } + return ( + <div key={index} class="block" style={{ display: 'flex', alignItems: 'center' }}> + <span class="icon"> + {authMethods[type]?.icon} + </span> + <span> + {method.instructions} + </span> + <span> + <span class="select " > + <select onChange={(e) => changeProviderTo(e.currentTarget.value)} value={providerURL ?? ""}> + <option key="none" value=""> << off >> </option> + {selectableProviders[type]?.map(prov => ( + <option key={prov.url} value={prov.url}> + {prov.url} + </option> + ))} + </select> + </span> + </span> + </div> + ); + })} + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={cancel}>Cancel</button> + <span class="buttons"> + <button class="button" onClick={() => setChangedProvider([])}>Reset</button> + <button class="button is-info" onClick={sendChanges}>Confirm</button> + </span> + </div> + </section> + </AnastasisClientFrame> +} diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx index 5ba0c937d..9f7e26c16 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx @@ -233,16 +233,16 @@ export const SomePoliciesWithMethods = createExample(TestedComponent, { instructions: "Does P equal NP?", challenge: "C5SP8" },{ - type: "email", - instructions: "Email to qwe@asd.com", + type: "totp", + instructions: "Response code for 'Anastasis'", challenge: "E5VPA" }, { type: "sms", - instructions: "SMS to 555-555", + instructions: "SMS to 6666-6666", challenge: "" }, { type: "question", - instructions: "Does P equal NP?", + instructions: "How did the chicken cross the road?", challenge: "C5SP8" }] } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx index 673f215e2..b8beb7b47 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx @@ -1,10 +1,14 @@ /* eslint-disable @typescript-eslint/camelcase */ +import { AuthMethod } from "anastasis-core"; import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame } from "./index"; import { authMethods, KnownAuthMethods } from "./authMethod"; +import { EditPoliciesScreen } from "./EditPoliciesScreen"; +import { AnastasisClientFrame } from "./index"; export function ReviewPoliciesScreen(): VNode { + const [editingPolicy, setEditingPolicy] = useState<number | undefined>() const reducer = useAnastasisContext() if (!reducer) { return <div>no reducer in context</div> @@ -12,20 +16,44 @@ export function ReviewPoliciesScreen(): VNode { if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { return <div>invalid state</div> } + const configuredAuthMethods = reducer.currentReducerState.authentication_methods ?? []; const policies = reducer.currentReducerState.policies ?? []; + if (editingPolicy !== undefined) { + return ( + <EditPoliciesScreen + index={editingPolicy} + cancel={() => setEditingPolicy(undefined)} + confirm={(newMethods) => { + reducer.runTransaction(async (tx) => { + await tx.transition("delete_policy", { + policy_index: editingPolicy + }); + await tx.transition("add_policy", { + policy: newMethods + }); + }); + setEditingPolicy(undefined) + }} + /> + ) + } + const errors = policies.length < 1 ? 'Need more policies' : undefined return ( <AnastasisClientFrame hideNext={errors} title="Backup: Review Recovery Policies"> {policies.length > 0 && <p class="block"> Based on your configured authentication method you have created, some policies - have been configured. In order to recover your secret you have to solve all the + have been configured. In order to recover your secret you have to solve all the challenges of at least one policy. - </p> } + </p>} {policies.length < 1 && <p class="block"> No policies had been created. Go back and add more authentication methods. - </p> } + </p>} + <div class="block" onClick={() => setEditingPolicy(policies.length + 1)}> + <button class="button is-success">Add new policy</button> + </div> {policies.map((p, policy_index) => { const methods = p.methods .map(x => configuredAuthMethods[x.authentication_method] && ({ ...configuredAuthMethods[x.authentication_method], provider: x.provider })) @@ -44,18 +72,21 @@ export function ReviewPoliciesScreen(): VNode { </p>} {methods.map((m, i) => { return ( - <p key={i} class="block" style={{display:'flex', alignItems:'center'}}> - <span class="icon"> - {authMethods[m.type as KnownAuthMethods]?.icon} - </span> - <span> - {m.instructions} recovery provided by <a href={m.provider}>{m.provider}</a> - </span> - </p> + <p key={i} class="block" style={{ display: 'flex', alignItems: 'center' }}> + <span class="icon"> + {authMethods[m.type as KnownAuthMethods]?.icon} + </span> + <span> + {m.instructions} recovery provided by <a href={m.provider}>{m.provider}</a> + </span> + </p> ); })} </div> - <div style={{ marginTop: 'auto', marginBottom: 'auto' }}><button class="button is-danger" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button></div> + <div style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}> + <button class="button is-info block" onClick={() => setEditingPolicy(policy_index)}>Edit</button> + <button class="button is-danger block" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button> + </div> </div> ); })} diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.tsx index c751ad9e4..6e97eb586 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.tsx @@ -10,33 +10,29 @@ export function StartScreen(): VNode { } return ( <AnastasisClientFrame hideNav title="Home"> - <div> - <section class="section is-main-section"> - <div class="columns"> - <div class="column" /> - <div class="column is-four-fifths"> + <div class="columns"> + <div class="column" /> + <div class="column is-four-fifths"> - <div class="buttons"> - <button class="button is-success" autoFocus onClick={() => reducer.startBackup()}> - <div class="icon"><i class="mdi mdi-arrow-up" /></div> - <span>Backup a secret</span> - </button> + <div class="buttons"> + <button class="button is-success" autoFocus onClick={() => reducer.startBackup()}> + <div class="icon"><i class="mdi mdi-arrow-up" /></div> + <span>Backup a secret</span> + </button> - <button class="button is-info" onClick={() => reducer.startRecover()}> - <div class="icon"><i class="mdi mdi-arrow-down" /></div> - <span>Recover a secret</span> - </button> + <button class="button is-info" onClick={() => reducer.startRecover()}> + <div class="icon"><i class="mdi mdi-arrow-down" /></div> + <span>Recover a secret</span> + </button> - <button class="button"> - <div class="icon"><i class="mdi mdi-file" /></div> - <span>Restore a session</span> - </button> - </div> - - </div> - <div class="column" /> + <button class="button"> + <div class="icon"><i class="mdi mdi-file" /></div> + <span>Restore a session</span> + </button> </div> - </section> + + </div> + <div class="column" /> </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx index eab800e35..04fa00d59 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx @@ -27,7 +27,7 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A <AnastasisClientFrame hideNav title="Add Security Question"> <div> <p> - For security question authentication, you need to provide a question + For2 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. @@ -47,6 +47,13 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A /> </div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={cancel}>Cancel</button> + <span data-tooltip={errors}> + <button class="button is-info" disabled={errors !== undefined} onClick={addQuestionAuth}>Add</button> + </span> + </div> + {configured.length > 0 && <section class="section"> <div class="block"> Your security questions: @@ -58,12 +65,6 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A </div> })} </div></section>} - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> - <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addQuestionAuth}>Add</button> - </span> - </div> </div> </AnastasisClientFrame > ); diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx index fefaa184c..415cf6e98 100644 --- a/packages/anastasis-webui/src/pages/home/index.tsx +++ b/packages/anastasis-webui/src/pages/home/index.tsx @@ -13,6 +13,7 @@ import { import { useErrorBoundary } from "preact/hooks"; +import { AsyncButton } from "../../components/AsyncButton"; import { Menu } from "../../components/menu"; import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis"; import { @@ -25,7 +26,6 @@ import { BackupFinishedScreen } from "./BackupFinishedScreen"; import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen"; import { ChallengePayingScreen } from "./ChallengePayingScreen"; import { ContinentSelectionScreen } from "./ContinentSelectionScreen"; -import { CountrySelectionScreen } from "./CountrySelectionScreen"; import { PoliciesPayingScreen } from "./PoliciesPayingScreen"; import { RecoveryFinishedScreen } from "./RecoveryFinishedScreen"; import { ReviewPoliciesScreen } from "./ReviewPoliciesScreen"; @@ -95,12 +95,19 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { if (!reducer) { return <p>Fatal: Reducer must be in context.</p>; } - const next = (): void => { - if (props.onNext) { - props.onNext(); - } else { - reducer.transition("next", {}); - } + const next = async (): Promise<void> => { + return new Promise((res, rej) => { + try { + if (props.onNext) { + props.onNext(); + } else { + reducer.transition("next", {}); + } + res() + } catch { + rej() + } + }) }; const handleKeyPress = ( e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>, @@ -111,20 +118,18 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { return ( <Fragment> <Menu title="Anastasis" /> - <div> - <div class="home" onKeyPress={(e) => handleKeyPress(e)}> - <h1 class="title">{props.title}</h1> + <div class="home" onKeyPress={(e) => handleKeyPress(e)}> + <h1 class="title">{props.title}</h1> + <section class="section is-main-section"> <ErrorBanner /> {props.children} {!props.hideNav ? ( <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> <button class="button" onClick={() => reducer.back()}>Back</button> - <span data-tooltip={props.hideNext}> - <button class="button is-info" onClick={next} disabled={props.hideNext !== undefined}>Next</button> - </span> + <AsyncButton data-tooltip={props.hideNext} onClick={next} disabled={props.hideNext !== undefined}>Next</AsyncButton> </div> ) : null} - </div> + </section> </div> </Fragment> ); |