aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/admin/AccountForm.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/admin/AccountForm.tsx')
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountForm.tsx104
1 files changed, 96 insertions, 8 deletions
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index 859c04396..7296e7744 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -1,9 +1,9 @@
import { AmountString, Amounts, PaytoString, TalerCorebankApi, TranslatedString, buildPayto, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
-import { CopyButton, ShowInputErrorLabel, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Attention, CopyButton, ShowInputErrorLabel, useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
-import { useBankCoreApiContext } from "../../context/config.js";
-import { ErrorMessageMappingFor, PartialButDefined, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
+import { VersionHint, useBankCoreApiContext } from "../../context/config.js";
+import { ErrorMessageMappingFor, PartialButDefined, TanChannel, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
import { InputAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { getRandomPassword } from "../rnd.js";
@@ -24,6 +24,7 @@ export type AccountFormData = {
cashout_payto_uri?: string,
email?: string,
phone?: string,
+ tan_channel?: TanChannel | "remove",
}
type ChangeByPurposeType = {
@@ -55,7 +56,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
onChange: ChangeByPurposeType[PurposeType];
purpose: PurposeType;
}): VNode {
- const { config } = useBankCoreApiContext()
+ const { config, hints } = useBankCoreApiContext()
const { i18n } = useTranslationContext();
const { state: credentials } = useBackendState();
const [form, setForm] = useState<AccountFormData>({});
@@ -75,8 +76,11 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
email: template?.contact_data?.email ?? "",
phone: template?.contact_data?.phone ?? "",
username: username ?? "",
+ tan_channel: template?.tan_channel,
}
+ const OLD_CASHOUT_API = hints.indexOf(VersionHint.CASHOUT_BEFORE_2FA) !== -1
+
const showingCurrentUserInfo = credentials.status !== "loggedIn" ? false : username === credentials.username
const userIsAdmin = credentials.status !== "loggedIn" ? false : credentials.isUserAdministrator
@@ -86,6 +90,9 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
const editableThreshold = userIsAdmin && (purpose === "create" || purpose === "update")
const editableAccount = purpose === "create" && userIsAdmin
+ const hasPhone = !!defaultValue.phone || !!form.phone
+ const hasEmail = !!defaultValue.email || !!form.email
+
function updateForm(newForm: typeof defaultValue): void {
const cashoutParsed = !newForm.cashout_payto_uri
? undefined
@@ -173,6 +180,8 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
payto_uri: internalURI,
is_public: !!newForm.isPublic,
is_taler_exchange: !!newForm.isExchange,
+ // @ts-ignore
+ tan_channel: newForm.tan_channel === "remove" ? null : newForm.tan_channel,
}
callback(result)
return;
@@ -190,6 +199,8 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
debit_threshold: threshold,
is_public: !!newForm.isPublic,
name: newForm.name,
+ // @ts-ignore
+ tan_channel: newForm?.tan_channel === "remove" ? null : newForm.tan_channel,
}
callback(result)
return;
@@ -409,7 +420,87 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
<span aria-hidden="true" data-enabled={form.isExchange ?? defaultValue.isExchange ? "true" : "false"} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
</button>
</div>
- </div>}
+ </div>
+ }
+ {/* channel, not shown if old cashout api */}
+ {OLD_CASHOUT_API ? undefined : config.supported_tan_channels.length === 0 ?
+ <div class="sm:col-span-5">
+ <Attention type="warning" title={i18n.str`No cashout channel available`}>
+ <i18n.Translate>
+ This server doesn't support second factor authentication.
+ </i18n.Translate>
+ </Attention>
+ </div>
+ :
+ <div class="sm:col-span-5">
+ <label
+ class="block text-sm font-medium leading-6 text-gray-900"
+ for="channel"
+ >
+ {i18n.str`Confirmation the operation using`}
+ </label>
+ <div class="mt-2 max-w-xl text-sm text-gray-500">
+ <div class="px-4 mt-4 grid grid-cols-1 gap-y-6">
+ {config.supported_tan_channels.indexOf(TanChannel.EMAIL) === -1 ? undefined :
+ <label onClick={(e) => {
+ if (!hasEmail) return;
+ if (form.tan_channel === TanChannel.EMAIL) {
+ form.tan_channel = "remove"
+ } else {
+ form.tan_channel = TanChannel.EMAIL
+ }
+ updateForm(structuredClone(form))
+ e.preventDefault()
+ }} data-disabled={purpose === "show" || !hasEmail} data-selected={(form.tan_channel ?? defaultValue.tan_channel) === TanChannel.EMAIL}
+ class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border bg-white data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600">
+ <input type="radio" name="channel" value="Newsletter" class="sr-only" />
+ <span class="flex flex-1">
+ <span class="flex flex-col">
+ <span id="project-type-0-label" class="block text-sm font-medium text-gray-900 ">
+ <i18n.Translate>Email</i18n.Translate>
+ </span>
+ {purpose !== "show" && !hasEmail && i18n.str`add a email in your profile to enable this option`}
+ </span>
+ </span>
+ <svg data-selected={(form.tan_channel ?? defaultValue.tan_channel) === TanChannel.EMAIL} class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+ </svg>
+ </label>
+ }
+
+ {config.supported_tan_channels.indexOf(TanChannel.SMS) === -1 ? undefined :
+ <label onClick={(e) => {
+ if (!hasPhone) return;
+ if (form.tan_channel === TanChannel.SMS) {
+ form.tan_channel = "remove"
+ } else {
+ form.tan_channel = TanChannel.SMS
+ }
+ updateForm(structuredClone(form))
+ e.preventDefault()
+ }} data-disabled={purpose === "show" || !hasPhone} data-selected={(form.tan_channel ?? defaultValue.tan_channel) === TanChannel.SMS}
+ class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600">
+ <input type="radio" name="channel" value="Existing Customers" class="sr-only" />
+ <span class="flex flex-1">
+ <span class="flex flex-col">
+ <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
+ <i18n.Translate>SMS</i18n.Translate>
+ </span>
+ {purpose !== "show" && !hasPhone && i18n.str`add a phone number in your profile to enable this option`}
+ </span>
+ </span>
+ <svg data-selected={(form.tan_channel ?? defaultValue.tan_channel) === TanChannel.SMS} class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+ </svg>
+ </label>
+ }
+ <pre>
+ {JSON.stringify(form, undefined, 2)}
+ </pre>
+ </div>
+ </div>
+ </div>
+ }
<div class="sm:col-span-5">
<div class="flex items-center justify-between">
@@ -434,9 +525,6 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({
</div>
</div>
- <pre>
- {JSON.stringify(errors, undefined, 2)}
- </pre>
{children}
</form>
);