taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 89974ebd542afb907be60f9fdde554eff5266285
parent 493f746865063f1f01c3c8764f46151bc3fdd5d2
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Tue, 12 May 2026 13:35:45 -0300

fix #11202

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/SolveMFA.tsx | 178+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx | 4++--
Mpackages/merchant-backoffice-ui/src/paths/newAccount/index.tsx | 28+++++++++++++++++++++++++++-
3 files changed, 127 insertions(+), 83 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/components/SolveMFA.tsx b/packages/merchant-backoffice-ui/src/components/SolveMFA.tsx @@ -26,6 +26,7 @@ import { } from "../hooks/preference.js"; import { FormErrors, FormProvider } from "./form/FormProvider.js"; import { InputCode } from "./form/InputCode.js"; +import { InputBoolean } from "./form/InputBoolean.js"; const TALER_SCREEN_ID = 5; @@ -416,12 +417,12 @@ export function SolveMFAChallenges({ // FIXME: we should save here also the expiration of the // tan channel to be used when the user press "i have the code" - type Selected = { + type Active = { ch: Challenge; expiration: AbsoluteTime; retransmission: AbsoluteTime; }; - const initialSelected: Selected | undefined = initial + const initialActive: Active | undefined = initial ? ({ ch: initial.request, expiration: !initial.response.solve_expiration @@ -434,14 +435,18 @@ export function SolveMFAChallenges({ : AbsoluteTime.fromProtocolTimestamp( initial.response.earliest_retransmission, ), - } as Selected) + } as Active) : undefined; // const [retransmission, setRetransmission] = useState(initialRetrans); - const [selected, setSelected] = useState<Selected | undefined>( - initialSelected, - ); + const [active, setActive] = useState<Active | undefined>(initialActive); + + const defaultSelected = + currentChallenge.challenges.length > 0 + ? currentChallenge.challenges[0] + : undefined; + const [selectedChallenge, setSelectedChallenge] = useState(defaultSelected); const [notification, safeFunctionHandler] = useLocalNotificationBetter(); @@ -451,7 +456,7 @@ export function SolveMFAChallenges({ ); sendMessage.onSuccess = (success, ch) => { - setSelected({ + setActive({ ch, retransmission: !success.earliest_retransmission ? AbsoluteTime.never() @@ -481,23 +486,34 @@ export function SolveMFAChallenges({ } }; - if (selected) { + const hasSolvedEnough = currentChallenge.combi_and + ? solved.length === currentChallenge.challenges.length + : solved.length > 0; + + const doSend = + hasSolvedEnough || !selectedChallenge + ? sendMessage + : sendMessage.withArgs(selectedChallenge); + + if (active) { return ( <SolveChallenge - key={selected.ch.challenge_id} + key={active.ch.challenge_id} onCancel={onCancel} - challenge={selected.ch} - expiration={selected.expiration} - retransmission={selected.retransmission} + challenge={active.ch} + expiration={active.expiration} + retransmission={active.retransmission} focus={focus} showFull={showFull ?? {}} onSolved={async () => { - const total = [...solved, selected.ch.challenge_id]; + const total = [...solved, active.ch.challenge_id]; const enough = currentChallenge.combi_and ? total.length === currentChallenge.challenges.length : total.length > 0; if (enough) { - onCompleted.withArgs(total).call(); + setSolved(total); + setActive(undefined); + await onCompleted.withArgs(total).call(); } else { setSolved(total); const nextPending = currentChallenge.challenges.find((c) => { @@ -507,7 +523,7 @@ export function SolveMFAChallenges({ if (nextPending) { await sendMessage.withArgs(nextPending).call(); } else { - setSelected(undefined); + setActive(undefined); } } }} @@ -515,7 +531,7 @@ export function SolveMFAChallenges({ ); } - const hasSolvedEnough = currentChallenge.combi_and + const enough = currentChallenge.combi_and ? solved.length === currentChallenge.challenges.length : solved.length > 0; @@ -539,70 +555,65 @@ export function SolveMFAChallenges({ class="modal-card-body" style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} > - {currentChallenge.combi_and ? ( - <i18n.Translate> - You must complete all of these requirements. - </i18n.Translate> - ) : ( - <i18n.Translate> - You need to complete at least one of this requirements. - </i18n.Translate> - )} + <div> + {currentChallenge.combi_and ? ( + <i18n.Translate> + You must complete all of these requirements. + </i18n.Translate> + ) : ( + <i18n.Translate> + You need to complete at least one of this requirements. + </i18n.Translate> + )} + </div> + <div class="control" style={{ margin: 8 }}> + <form> + {currentChallenge.challenges.map((challenge, idx) => { + return ( + <label + class="radio" + style={{ display: "flex", margin: 0, marginTop: 16 }} + > + <div style={{ padding: 4 }}> + <input + type="radio" + name="challenge_id" + checked={ + selectedChallenge?.challenge_id === + challenge.challenge_id + } + onChange={() => { + setSelectedChallenge(challenge); + }} + /> + </div> + <div style={{ alignContent: "center" }}> + {(function (ch: TanChannel): VNode { + switch (ch) { + case TanChannel.SMS: + return ( + <i18n.Translate> + An SMS to the phone number ending with{" "} + <span>{challenge.tan_info}</span> + </i18n.Translate> + ); + case TanChannel.EMAIL: + return ( + <i18n.Translate> + An email to the address starting with{" "} + <span>{challenge.tan_info}</span> + </i18n.Translate> + ); + } + })(challenge.tan_channel)} + </div> + </label> + // </section> + ); + })} + </form> + </div> </section> - {currentChallenge.challenges.map((challenge, idx) => { - const noNeedToComplete = - hasSolvedEnough || solved.indexOf(challenge.challenge_id) !== -1; - - const doSend = noNeedToComplete - ? sendMessage - : sendMessage.withArgs(challenge); - - return ( - <section - class="modal-card-body" - style={{ - border: "1px solid", - borderTop: 0, - borderBottom: 0, - }} - > - {(function (ch: TanChannel): VNode { - switch (ch) { - case TanChannel.SMS: - return ( - <i18n.Translate> - An SMS to the phone number ending with{" "} - <span>{challenge.tan_info}</span> - </i18n.Translate> - ); - case TanChannel.EMAIL: - return ( - <i18n.Translate> - An email to the address starting with{" "} - <span>{challenge.tan_info}</span> - </i18n.Translate> - ); - } - })(challenge.tan_channel)} - - <div - style={{ - justifyContent: "space-between", - display: "flex", - }} - > - <div /> - <ButtonBetterBulma - type="button" - onClick={doSend} - focus={idx === 0 && focus} - > - <i18n.Translate>Continue</i18n.Translate> - </ButtonBetterBulma> - </div> - </section> - ); - })} <footer class="modal-card-foot " style={{ @@ -614,7 +625,14 @@ export function SolveMFAChallenges({ <button class="button" type="button" onClick={onCancel}> <i18n.Translate>Cancel</i18n.Translate> </button> - <div /> + <ButtonBetterBulma type="button" onClick={doSend}> + <i18n.Translate>Continue</i18n.Translate> + </ButtonBetterBulma> + {!enough ? undefined : ( + <ButtonBetterBulma type="button" onClick={onCompleted.withArgs(solved)}> + <i18n.Translate>Complete</i18n.Translate> + </ButtonBetterBulma> + )} </footer> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx @@ -189,9 +189,9 @@ export function CreatePage({ onCreated, onBack }: Props): VNode { case HttpStatusCode.Unauthorized: return i18n.str`Unauthorized.`; case HttpStatusCode.NotFound: - return i18n.str`Not found.`; + return i18n.str`The instance does not exist.`; case HttpStatusCode.Conflict: - return i18n.str`Conflict.`; + return i18n.str`The bank account already exist but with different information. Is the name of the account holder correct?`; default: assertUnreachable(fail); } diff --git a/packages/merchant-backoffice-ui/src/paths/newAccount/index.tsx b/packages/merchant-backoffice-ui/src/paths/newAccount/index.tsx @@ -25,7 +25,7 @@ import { HttpStatusCode, InstanceConfigurationMessage, MerchantAuthMethod, - TanChannel + TanChannel, } from "@gnu-taler/taler-util"; import { buildStorageKey, @@ -240,6 +240,32 @@ export function NewAccount({ onCancel, onCreated }: Props): VNode { currentChallenge={mfa.pendingChallenge} onCompleted={retry} initial={mfa.initial} + // currentChallenge={{ + // challenges: [ + // { + // challenge_id: "1", + // tan_channel: TanChannel.EMAIL, + // tan_info: "zxc", + // }, + // { + // challenge_id: "2", + // tan_channel: TanChannel.EMAIL, + // tan_info: "aasd", + // }, + // ], + // combi_and: false, + // }} + // initial={{ + // request:{ + // challenge_id: "1", + // tan_channel: TanChannel.EMAIL, + // tan_info: "asd" + // }, + // response: { + // earliest_retransmission: TalerProtocolTimestamp.never(), + // solve_expiration: TalerProtocolTimestamp.never(), + // } + // }} focus showFull={{ [TanChannel.EMAIL]: value.email,