commit 89974ebd542afb907be60f9fdde554eff5266285
parent 493f746865063f1f01c3c8764f46151bc3fdd5d2
Author: Sebastian <sebasjm@taler-systems.com>
Date: Tue, 12 May 2026 13:35:45 -0300
fix #11202
Diffstat:
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,