This commit is contained in:
Sebastian 2021-11-12 13:12:27 -03:00
parent 377e78e854
commit 38b84bb805
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
20 changed files with 225 additions and 67 deletions

View File

@ -20,6 +20,7 @@
*/ */
import { ComponentChildren, h, VNode } from "preact"; import { ComponentChildren, h, VNode } from "preact";
import { useLayoutEffect, useRef } from "preact/hooks";
// import { LoadingModal } from "../modal"; // import { LoadingModal } from "../modal";
import { useAsync } from "../hooks/async"; import { useAsync } from "../hooks/async";
// import { Translate } from "../../i18n"; // import { Translate } from "../../i18n";
@ -28,17 +29,26 @@ type Props = {
children: ComponentChildren; children: ComponentChildren;
disabled?: boolean; disabled?: boolean;
onClick?: () => Promise<void>; onClick?: () => Promise<void>;
grabFocus?: boolean;
[rest: string]: any; [rest: string]: any;
}; };
export function AsyncButton({ export function AsyncButton({
onClick, onClick,
grabFocus,
disabled, disabled,
children, children,
...rest ...rest
}: Props): VNode { }: Props): VNode {
const { isLoading, request } = useAsync(onClick); const { isLoading, request } = useAsync(onClick);
const buttonRef = useRef<HTMLButtonElement>(null);
useLayoutEffect(() => {
if (grabFocus) {
buttonRef.current?.focus();
}
}, [grabFocus]);
// if (isSlow) { // if (isSlow) {
// return <LoadingModal onCancel={cancel} />; // return <LoadingModal onCancel={cancel} />;
// } // }
@ -48,7 +58,7 @@ export function AsyncButton({
return ( return (
<span data-tooltip={rest["data-tooltip"]} style={{ marginLeft: 5 }}> <span data-tooltip={rest["data-tooltip"]} style={{ marginLeft: 5 }}>
<button {...rest} onClick={request} disabled={disabled}> <button {...rest} ref={buttonRef} onClick={request} disabled={disabled}>
{children} {children}
</button> </button>
</span> </span>

View File

@ -1,4 +1,4 @@
import { format, isAfter, parse, sub, subYears } from "date-fns"; import { format, subYears } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useLayoutEffect, useRef, useState } from "preact/hooks"; import { useLayoutEffect, useRef, useState } from "preact/hooks";
import { DatePicker } from "../picker/DatePicker"; import { DatePicker } from "../picker/DatePicker";
@ -9,6 +9,7 @@ export interface DateInputProps {
tooltip?: string; tooltip?: string;
error?: string; error?: string;
years?: Array<number>; years?: Array<number>;
onConfirm?: () => void;
bind: [string, (x: string) => void]; bind: [string, (x: string) => void];
} }
@ -44,7 +45,12 @@ export function DateInput(props: DateInputProps): VNode {
type="text" type="text"
class={showError ? "input is-danger" : "input"} class={showError ? "input is-danger" : "input"}
value={value} value={value}
onInput={(e) => { onKeyPress={(e) => {
if (e.key === 'Enter' && props.onConfirm) {
props.onConfirm()
}
}}
onInput={(e) => {
const text = e.currentTarget.value; const text = e.currentTarget.value;
setDirty(true); setDirty(true);
props.bind[1](text); props.bind[1](text);

View File

@ -7,6 +7,7 @@ export interface TextInputProps {
error?: string; error?: string;
placeholder?: string; placeholder?: string;
tooltip?: string; tooltip?: string;
onConfirm?: () => void;
bind: [string, (x: string) => void]; bind: [string, (x: string) => void];
} }
@ -37,6 +38,11 @@ export function EmailInput(props: TextInputProps): VNode {
placeholder={props.placeholder} placeholder={props.placeholder}
type="email" type="email"
class={showError ? "input is-danger" : "input"} class={showError ? "input is-danger" : "input"}
onKeyPress={(e) => {
if (e.key === 'Enter' && props.onConfirm) {
props.onConfirm()
}
}}
onInput={(e) => { onInput={(e) => {
setDirty(true); setDirty(true);
props.bind[1]((e.target as HTMLInputElement).value); props.bind[1]((e.target as HTMLInputElement).value);

View File

@ -7,6 +7,7 @@ export interface TextInputProps {
error?: string; error?: string;
placeholder?: string; placeholder?: string;
tooltip?: string; tooltip?: string;
onConfirm?: () => void;
bind: [string, (x: string) => void]; bind: [string, (x: string) => void];
} }
@ -36,6 +37,11 @@ export function PhoneNumberInput(props: TextInputProps): VNode {
type="tel" type="tel"
placeholder={props.placeholder} placeholder={props.placeholder}
class={showError ? "input is-danger" : "input"} class={showError ? "input is-danger" : "input"}
onKeyPress={(e) => {
if (e.key === 'Enter' && props.onConfirm) {
props.onConfirm()
}
}}
onInput={(e) => { onInput={(e) => {
setDirty(true); setDirty(true);
props.bind[1]((e.target as HTMLInputElement).value); props.bind[1]((e.target as HTMLInputElement).value);

View File

@ -8,6 +8,7 @@ export interface TextInputProps {
error?: string; error?: string;
placeholder?: string; placeholder?: string;
tooltip?: string; tooltip?: string;
onConfirm?: () => void;
bind: [string, (x: string) => void]; bind: [string, (x: string) => void];
} }
@ -37,6 +38,11 @@ export function TextInput(props: TextInputProps): VNode {
disabled={props.disabled} disabled={props.disabled}
placeholder={props.placeholder} placeholder={props.placeholder}
class={showError ? "input is-danger" : "input"} class={showError ? "input is-danger" : "input"}
onKeyPress={(e) => {
if (e.key === 'Enter' && props.onConfirm) {
props.onConfirm()
}
}}
onInput={(e) => { onInput={(e) => {
setDirty(true); setDirty(true);
props.bind[1]((e.target as HTMLInputElement).value); props.bind[1]((e.target as HTMLInputElement).value);

View File

@ -44,6 +44,11 @@ export function AttributeEntryScreen(): VNode {
setValue={(v: string) => setAttrs({ ...attrs, [spec.name]: v })} setValue={(v: string) => setAttrs({ ...attrs, [spec.name]: v })}
spec={spec} spec={spec}
errorMessage={error} errorMessage={error}
onConfirm={() => {
if (!hasErrors) {
setAskUserIfSure(true)
}
}}
value={value} value={value}
/> />
); );
@ -104,6 +109,7 @@ interface AttributeEntryFieldProps {
setValue: (newValue: string) => void; setValue: (newValue: string) => void;
spec: UserAttributeSpec; spec: UserAttributeSpec;
errorMessage: string | undefined; errorMessage: string | undefined;
onConfirm: () => void;
} }
const possibleBirthdayYear: Array<number> = []; const possibleBirthdayYear: Array<number> = [];
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
@ -117,6 +123,7 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
grabFocus={props.isFirst} grabFocus={props.isFirst}
label={props.spec.label} label={props.spec.label}
years={possibleBirthdayYear} years={possibleBirthdayYear}
onConfirm={props.onConfirm}
error={props.errorMessage} error={props.errorMessage}
bind={[props.value, props.setValue]} bind={[props.value, props.setValue]}
/> />
@ -125,6 +132,7 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
<PhoneNumberInput <PhoneNumberInput
grabFocus={props.isFirst} grabFocus={props.isFirst}
label={props.spec.label} label={props.spec.label}
onConfirm={props.onConfirm}
error={props.errorMessage} error={props.errorMessage}
bind={[props.value, props.setValue]} bind={[props.value, props.setValue]}
/> />
@ -133,6 +141,7 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
<TextInput <TextInput
grabFocus={props.isFirst} grabFocus={props.isFirst}
label={props.spec.label} label={props.spec.label}
onConfirm={props.onConfirm}
error={props.errorMessage} error={props.errorMessage}
bind={[props.value, props.setValue]} bind={[props.value, props.setValue]}
/> />

View File

@ -1,4 +1,6 @@
import { differenceInBusinessDays } from "date-fns";
import { ComponentChildren, h, VNode } from "preact"; import { ComponentChildren, h, VNode } from "preact";
import { useLayoutEffect, useRef } from "preact/hooks";
import { AsyncButton } from "../../components/AsyncButton"; import { AsyncButton } from "../../components/AsyncButton";
export interface ConfirmModelProps { export interface ConfirmModelProps {
@ -17,7 +19,7 @@ export function ConfirmModal({
active, description, onCancel, onConfirm, children, danger, disabled, label = "Confirm", cancelLabel = "Dismiss" active, description, onCancel, onConfirm, children, danger, disabled, label = "Confirm", cancelLabel = "Dismiss"
}: ConfirmModelProps): VNode { }: ConfirmModelProps): VNode {
return ( return (
<div class={active ? "modal is-active" : "modal"}> <div class={active ? "modal is-active" : "modal"} >
<div class="modal-background " onClick={onCancel} /> <div class="modal-background " onClick={onCancel} />
<div class="modal-card" style={{ maxWidth: 700 }}> <div class="modal-card" style={{ maxWidth: 700 }}>
<header class="modal-card-head"> <header class="modal-card-head">
@ -33,8 +35,11 @@ export function ConfirmModal({
<button class="button" onClick={onCancel}> <button class="button" onClick={onCancel}>
{cancelLabel} {cancelLabel}
</button> </button>
<div class="buttons is-right" style={{ width: "100%" }}> <div class="buttons is-right" style={{ width: "100%" }} onKeyDown={(e) => {
if (e.key === 'Escape' && onCancel) onCancel()
}}>
<AsyncButton <AsyncButton
grabFocus
class={danger ? "button is-danger " : "button is-info "} class={danger ? "button is-danger " : "button is-info "}
disabled={disabled} disabled={disabled}
onClick={onConfirm} onClick={onConfirm}

View File

@ -10,10 +10,12 @@ import { FileInput, FileTypeContent } from "../../components/fields/FileInput";
export function SecretEditorScreen(): VNode { export function SecretEditorScreen(): VNode {
const reducer = useAnastasisContext(); const reducer = useAnastasisContext();
const [secretValue, setSecretValue] = useState(""); const [secretValue, setSecretValue] = useState("");
const [secretFile, _setSecretFile] = useState<FileTypeContent | undefined>(undefined); const [secretFile, _setSecretFile] = useState<FileTypeContent | undefined>(
undefined,
);
function setSecretFile(v) { function setSecretFile(v) {
setSecretValue("") // reset secret value when uploading a file setSecretValue(""); // reset secret value when uploading a file
_setSecretFile(v) _setSecretFile(v);
} }
const currentSecretName = const currentSecretName =
@ -34,14 +36,16 @@ export function SecretEditorScreen(): VNode {
} }
const secretNext = async (): Promise<void> => { const secretNext = async (): Promise<void> => {
const secret = secretFile ? { const secret = secretFile
value: encodeCrock(stringToBytes(secretValue)), ? {
filename: secretFile.name, value: encodeCrock(stringToBytes(secretValue)),
mime: secretFile.type, filename: secretFile.name,
} : { mime: secretFile.type,
value: encodeCrock(stringToBytes(secretValue)), }
mime: "text/plain", : {
} value: encodeCrock(stringToBytes(secretValue)),
mime: "text/plain",
};
return reducer.runTransaction(async (tx) => { return reducer.runTransaction(async (tx) => {
await tx.transition("enter_secret_name", { await tx.transition("enter_secret_name", {
name: secretName, name: secretName,
@ -55,9 +59,14 @@ export function SecretEditorScreen(): VNode {
await tx.transition("next", {}); await tx.transition("next", {});
}); });
}; };
const errors = !secretName ? 'Add a secret name' : ( const errors = !secretName
(!secretValue && !secretFile) ? 'Add a secret value or a choose a file to upload' : undefined ? "Add a secret name"
) : !secretValue && !secretFile
? "Add a secret value or a choose a file to upload"
: undefined;
function goNextIfNoErrors(): void {
if (!errors) secretNext();
}
return ( return (
<AnastasisClientFrame <AnastasisClientFrame
hideNext={errors} hideNext={errors}
@ -69,12 +78,14 @@ export function SecretEditorScreen(): VNode {
label="Secret name:" label="Secret name:"
tooltip="The secret name allows you to identify your secret when restoring it. It is a label that you can choose freely." tooltip="The secret name allows you to identify your secret when restoring it. It is a label that you can choose freely."
grabFocus grabFocus
onConfirm={goNextIfNoErrors}
bind={[secretName, setSecretName]} bind={[secretName, setSecretName]}
/> />
</div> </div>
<div class="block"> <div class="block">
<TextInput <TextInput
disabled={!!secretFile} disabled={!!secretFile}
onConfirm={goNextIfNoErrors}
label="Enter the secret as text:" label="Enter the secret as text:"
bind={[secretValue, setSecretValue]} bind={[secretValue, setSecretValue]}
/> />
@ -82,10 +93,12 @@ export function SecretEditorScreen(): VNode {
<div class="block"> <div class="block">
Or upload a secret file Or upload a secret file
<FileInput label="Choose file" onChange={setSecretFile} /> <FileInput label="Choose file" onChange={setSecretFile} />
{secretFile && <div> {secretFile && (
Uploading secret file <b>{secretFile.name}</b> <a onClick={() => setSecretFile(undefined)}>cancel</a> <div>
</div> Uploading secret file <b>{secretFile.name}</b>{" "}
} <a onClick={() => setSecretFile(undefined)}>cancel</a>
</div>
)}
</div> </div>
</AnastasisClientFrame> </AnastasisClientFrame>
); );

View File

@ -26,6 +26,9 @@ export function AuthMethodEmailSetup({
: undefined; : undefined;
const errors = !email ? "Add your email" : emailError; const errors = !email ? "Add your email" : emailError;
function goNextIfNoErrors(): void {
if (!errors) addEmailAuth();
}
return ( return (
<AnastasisClientFrame hideNav title="Add email authentication"> <AnastasisClientFrame hideNav title="Add email authentication">
<p> <p>
@ -37,6 +40,7 @@ export function AuthMethodEmailSetup({
<EmailInput <EmailInput
label="Email address" label="Email address"
error={emailError} error={emailError}
onConfirm={goNextIfNoErrors}
placeholder="email@domain.com" placeholder="email@domain.com"
bind={[email, setEmail]} bind={[email, setEmail]}
/> />

View File

@ -10,7 +10,7 @@ import { AuthMethodSolveProps } from "./index";
export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
const [answer, setAnswer] = useState(""); const [answer, setAnswer] = useState("");
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false);
const reducer = useAnastasisContext(); const reducer = useAnastasisContext();
if (!reducer) { if (!reducer) {
@ -91,23 +91,38 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
<SolveOverviewFeedbackDisplay feedback={feedback} /> <SolveOverviewFeedbackDisplay feedback={feedback} />
<p> <p>
An email has been sent to "<b>{selectedChallenge.instructions}</b>". The An email has been sent to "<b>{selectedChallenge.instructions}</b>". The
message has and identification code and recovery code that starts with "<b>A-</b>". message has and identification code and recovery code that starts with "
Wait the message to arrive and the enter the recovery code below. <b>A-</b>". Wait the message to arrive and the enter the recovery code
below.
</p> </p>
{!expanded ? <p> {!expanded ? (
The identification code in the email should start with "{selectedUuid.substring(0, 10)}" <p>
<span class="icon has-tooltip-top" data-tooltip="click to expand" onClick={() => setExpanded(e => !e)}> The identification code in the email should start with "
<i class="mdi mdi-information" /> {selectedUuid.substring(0, 10)}"
</span> <span
</p> class="icon has-tooltip-top"
: <p> data-tooltip="click to expand"
The identification code in the email is "{selectedUuid}" onClick={() => setExpanded((e) => !e)}
<span class="icon has-tooltip-top" data-tooltip="click to show less code" onClick={() => setExpanded(e => !e)}> >
<i class="mdi mdi-information" /> <i class="mdi mdi-information" />
</span> </span>
</p>} </p>
<TextInput label="Answer" ) : (
<p>
The identification code in the email is "{selectedUuid}"
<span
class="icon has-tooltip-top"
data-tooltip="click to show less code"
onClick={() => setExpanded((e) => !e)}
>
<i class="mdi mdi-information" />
</span>
</p>
)}
<TextInput
label="Answer"
grabFocus grabFocus
onConfirm={onNext}
bind={[answer, setAnswer]} bind={[answer, setAnswer]}
placeholder="A-1234567812345678" placeholder="A-1234567812345678"
/> />

View File

@ -36,6 +36,9 @@ export function AuthMethodIbanSetup({
: !account : !account
? "Add an account IBAN number" ? "Add an account IBAN number"
: undefined; : undefined;
function goNextIfNoErrors(): void {
if (!errors) addIbanAuth();
}
return ( return (
<AnastasisClientFrame hideNav title="Add bank transfer authentication"> <AnastasisClientFrame hideNav title="Add bank transfer authentication">
<p> <p>
@ -49,11 +52,13 @@ export function AuthMethodIbanSetup({
label="Bank account holder name" label="Bank account holder name"
grabFocus grabFocus
placeholder="John Smith" placeholder="John Smith"
onConfirm={goNextIfNoErrors}
bind={[name, setName]} bind={[name, setName]}
/> />
<TextInput <TextInput
label="IBAN" label="IBAN"
placeholder="DE91100000000123456789" placeholder="DE91100000000123456789"
onConfirm={goNextIfNoErrors}
bind={[account, setAccount]} bind={[account, setAccount]}
/> />
</div> </div>

View File

@ -48,6 +48,10 @@ export function AuthMethodPostSetup({
: !country : !country
? "The country is missing" ? "The country is missing"
: undefined; : undefined;
function goNextIfNoErrors(): void {
if (!errors) addPostAuth();
}
return ( return (
<AnastasisClientFrame hideNav title="Add postal authentication"> <AnastasisClientFrame hideNav title="Add postal authentication">
<p> <p>
@ -56,19 +60,40 @@ export function AuthMethodPostSetup({
will receive in a letter to that address. will receive in a letter to that address.
</p> </p>
<div> <div>
<TextInput grabFocus label="Full Name" bind={[fullName, setFullName]} /> <TextInput
grabFocus
label="Full Name"
bind={[fullName, setFullName]}
onConfirm={goNextIfNoErrors}
/>
</div> </div>
<div> <div>
<TextInput label="Street" bind={[street, setStreet]} /> <TextInput
onConfirm={goNextIfNoErrors}
label="Street"
bind={[street, setStreet]}
/>
</div> </div>
<div> <div>
<TextInput label="City" bind={[city, setCity]} /> <TextInput
onConfirm={goNextIfNoErrors}
label="City"
bind={[city, setCity]}
/>
</div> </div>
<div> <div>
<TextInput label="Postal Code" bind={[postcode, setPostcode]} /> <TextInput
onConfirm={goNextIfNoErrors}
label="Postal Code"
bind={[postcode, setPostcode]}
/>
</div> </div>
<div> <div>
<TextInput label="Country" bind={[country, setCountry]} /> <TextInput
onConfirm={goNextIfNoErrors}
label="Country"
bind={[country, setCountry]}
/>
</div> </div>
{configured.length > 0 && ( {configured.length > 0 && (

View File

@ -89,7 +89,12 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
<AnastasisClientFrame hideNav title="Postal Challenge"> <AnastasisClientFrame hideNav title="Postal Challenge">
<SolveOverviewFeedbackDisplay feedback={feedback} /> <SolveOverviewFeedbackDisplay feedback={feedback} />
<p>Wait for the answer</p> <p>Wait for the answer</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <TextInput
onConfirm={onNext}
label="Answer"
grabFocus
bind={[answer, setAnswer]}
/>
<div <div
style={{ style={{

View File

@ -26,6 +26,9 @@ export function AuthMethodQuestionSetup({
: !answerText : !answerText
? "Add the answer to your question" ? "Add the answer to your question"
: undefined; : undefined;
function goNextIfNoErrors(): void {
if (!errors) addQuestionAuth();
}
return ( return (
<AnastasisClientFrame hideNav title="Add Security Question"> <AnastasisClientFrame hideNav title="Add Security Question">
<div> <div>
@ -39,6 +42,7 @@ export function AuthMethodQuestionSetup({
<TextInput <TextInput
label="Security question" label="Security question"
grabFocus grabFocus
onConfirm={goNextIfNoErrors}
placeholder="Your question" placeholder="Your question"
bind={[questionText, setQuestionText]} bind={[questionText, setQuestionText]}
/> />
@ -46,6 +50,7 @@ export function AuthMethodQuestionSetup({
<div> <div>
<TextInput <TextInput
label="Answer" label="Answer"
onConfirm={goNextIfNoErrors}
placeholder="Your answer" placeholder="Your answer"
bind={[answerText, setAnswerText]} bind={[answerText, setAnswerText]}
/> />

View File

@ -91,11 +91,14 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode {
<p> <p>
In this challenge you need to provide the answer for the next question: In this challenge you need to provide the answer for the next question:
</p> </p>
<pre> <pre>{selectedChallenge.instructions}</pre>
{selectedChallenge.instructions}
</pre>
<p>Type the answer below</p> <p>Type the answer below</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <TextInput
label="Answer"
onConfirm={onNext}
grabFocus
bind={[answer, setAnswer]}
/>
<div <div
style={{ style={{

View File

@ -25,6 +25,9 @@ export function AuthMethodSmsSetup({
inputRef.current?.focus(); inputRef.current?.focus();
}, []); }, []);
const errors = !mobileNumber ? "Add a mobile number" : undefined; const errors = !mobileNumber ? "Add a mobile number" : undefined;
function goNextIfNoErrors(): void {
if (!errors) addSmsAuth();
}
return ( return (
<AnastasisClientFrame hideNav title="Add SMS authentication"> <AnastasisClientFrame hideNav title="Add SMS authentication">
<div> <div>
@ -37,6 +40,7 @@ export function AuthMethodSmsSetup({
<PhoneNumberInput <PhoneNumberInput
label="Mobile number" label="Mobile number"
placeholder="Your mobile number" placeholder="Your mobile number"
onConfirm={goNextIfNoErrors}
grabFocus grabFocus
bind={[mobileNumber, setMobileNumber]} bind={[mobileNumber, setMobileNumber]}
/> />

View File

@ -11,7 +11,7 @@ import { AuthMethodSolveProps } from "./index";
export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
const [answer, setAnswer] = useState(""); const [answer, setAnswer] = useState("");
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false);
const reducer = useAnastasisContext(); const reducer = useAnastasisContext();
if (!reducer) { if (!reducer) {
return ( return (
@ -91,23 +91,38 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
<SolveOverviewFeedbackDisplay feedback={feedback} /> <SolveOverviewFeedbackDisplay feedback={feedback} />
<p> <p>
An sms has been sent to "<b>{selectedChallenge.instructions}</b>". The An sms has been sent to "<b>{selectedChallenge.instructions}</b>". The
message has and identification code and recovery code that starts with "<b>A-</b>". message has and identification code and recovery code that starts with "
Wait the message to arrive and the enter the recovery code below. <b>A-</b>". Wait the message to arrive and the enter the recovery code
below.
</p> </p>
{!expanded ? <p> {!expanded ? (
The identification code in the SMS should start with "{selectedUuid.substring(0, 10)}" <p>
<span class="icon has-tooltip-top" data-tooltip="click to expand" onClick={() => setExpanded(e => !e)}> The identification code in the SMS should start with "
<i class="mdi mdi-information" /> {selectedUuid.substring(0, 10)}"
</span> <span
</p> class="icon has-tooltip-top"
: <p> data-tooltip="click to expand"
The identification code in the SMS is "{selectedUuid}" onClick={() => setExpanded((e) => !e)}
<span class="icon has-tooltip-top" data-tooltip="click to show less code" onClick={() => setExpanded(e => !e)}> >
<i class="mdi mdi-information" /> <i class="mdi mdi-information" />
</span> </span>
</p>} </p>
<TextInput label="Answer" ) : (
<p>
The identification code in the SMS is "{selectedUuid}"
<span
class="icon has-tooltip-top"
data-tooltip="click to show less code"
onClick={() => setExpanded((e) => !e)}
>
<i class="mdi mdi-information" />
</span>
</p>
)}
<TextInput
label="Answer"
grabFocus grabFocus
onConfirm={onNext}
bind={[answer, setAnswer]} bind={[answer, setAnswer]}
placeholder="A-1234567812345678" placeholder="A-1234567812345678"
/> />

View File

@ -38,6 +38,9 @@ export function AuthMethodTotpSetup({
: !testCodeMatches : !testCodeMatches
? "The test code doesnt match" ? "The test code doesnt match"
: undefined; : undefined;
function goNextIfNoErrors(): void {
if (!errors) addTotpAuth();
}
return ( return (
<AnastasisClientFrame hideNav title="Add TOTP authentication"> <AnastasisClientFrame hideNav title="Add TOTP authentication">
<p> <p>
@ -54,7 +57,11 @@ export function AuthMethodTotpSetup({
<p> <p>
After scanning the code with your TOTP App, test it in the input below. After scanning the code with your TOTP App, test it in the input below.
</p> </p>
<TextInput label="Test code" bind={[test, setTest]} /> <TextInput
label="Test code"
onConfirm={goNextIfNoErrors}
bind={[test, setTest]}
/>
{configured.length > 0 && ( {configured.length > 0 && (
<section class="section"> <section class="section">
<div class="block">Your TOTP numbers:</div> <div class="block">Your TOTP numbers:</div>

View File

@ -89,7 +89,12 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode {
<AnastasisClientFrame hideNav title="TOTP Challenge"> <AnastasisClientFrame hideNav title="TOTP Challenge">
<SolveOverviewFeedbackDisplay feedback={feedback} /> <SolveOverviewFeedbackDisplay feedback={feedback} />
<p>enter the totp solution</p> <p>enter the totp solution</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <TextInput
label="Answer"
onConfirm={onNext}
grabFocus
bind={[answer, setAnswer]}
/>
<div <div
style={{ style={{

View File

@ -20,6 +20,9 @@ export function AuthMethodVideoSetup({
}, },
}); });
}; };
function goNextIfNoErrors(): void {
addVideoAuth();
}
return ( return (
<AnastasisClientFrame hideNav title="Add video authentication"> <AnastasisClientFrame hideNav title="Add video authentication">
<p> <p>
@ -32,6 +35,7 @@ export function AuthMethodVideoSetup({
<ImageInput <ImageInput
label="Choose photograph" label="Choose photograph"
grabFocus grabFocus
onConfirm={goNextIfNoErrors}
bind={[image, setImage]} bind={[image, setImage]}
/> />
</div> </div>