diff options
author | Sebastian <sebasjm@gmail.com> | 2024-01-11 16:41:24 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-01-11 16:41:42 -0300 |
commit | 82d4ed90caa4a6ea3bdda1fb80ccecf3dc3637f9 (patch) | |
tree | 59162f0565311e8699ca643a8bd60337ee7f582b /packages/demobank-ui/src/pages/admin/AccountForm.tsx | |
parent | ca67640f9f94f1150c0fb67c148dc79daa9d3fa0 (diff) | |
download | wallet-core-82d4ed90caa4a6ea3bdda1fb80ccecf3dc3637f9.tar.xz |
2fa
Diffstat (limited to 'packages/demobank-ui/src/pages/admin/AccountForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/admin/AccountForm.tsx | 104 |
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> ); |