This commit is contained in:
Sebastian 2023-09-20 16:10:32 -03:00
parent e39d5c488e
commit 7d4c5a71aa
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
6 changed files with 225 additions and 150 deletions

View File

@ -85,18 +85,26 @@ export function getInitialBackendBaseURL(): string {
typeof localStorage !== "undefined" typeof localStorage !== "undefined"
? localStorage.getItem("bank-base-url") ? localStorage.getItem("bank-base-url")
: undefined; : undefined;
let result: string;
if (!overrideUrl) { if (!overrideUrl) {
//normal path //normal path
if (!bankUiSettings.backendBaseURL) { if (!bankUiSettings.backendBaseURL) {
console.error( console.error(
"ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'", "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'",
); );
return canonicalizeBaseUrl(window.origin); result = window.origin
} }
return canonicalizeBaseUrl(bankUiSettings.backendBaseURL); result = bankUiSettings.backendBaseURL;
} else {
// testing/development path
result = overrideUrl
}
try {
return canonicalizeBaseUrl(result)
} catch (e) {
//fall back
return canonicalizeBaseUrl(window.origin)
} }
// testing/development path
return canonicalizeBaseUrl(overrideUrl);
} }
export const defaultState: BackendState = { export const defaultState: BackendState = {

View File

@ -15,8 +15,12 @@
*/ */
import { import {
AmountString,
Codec, Codec,
buildCodecForObject, buildCodecForObject,
codecForAmountString,
codecForBoolean,
codecForNumber,
codecForString, codecForString,
codecOptional, codecOptional,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
@ -24,15 +28,21 @@ import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
interface Settings { interface Settings {
currentWithdrawalOperationId: string | undefined; currentWithdrawalOperationId: string | undefined;
showWithdrawalSuccess: boolean;
maxWithdrawalAmount: number;
} }
export const codecForSettings = (): Codec<Settings> => export const codecForSettings = (): Codec<Settings> =>
buildCodecForObject<Settings>() buildCodecForObject<Settings>()
.property("currentWithdrawalOperationId", codecOptional(codecForString())) .property("currentWithdrawalOperationId", codecOptional(codecForString()))
.property("showWithdrawalSuccess", (codecForBoolean()))
.property("maxWithdrawalAmount", codecForNumber())
.build("Settings"); .build("Settings");
const defaultSettings: Settings = { const defaultSettings: Settings = {
currentWithdrawalOperationId: undefined, currentWithdrawalOperationId: undefined,
showWithdrawalSuccess: true,
maxWithdrawalAmount: 25
}; };
const DEMOBANK_SETTINGS_KEY = buildStorageKey( const DEMOBANK_SETTINGS_KEY = buildStorageKey(

View File

@ -183,8 +183,28 @@ export function BankFrame({
{/* <span class="ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-gray-50 px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600 ring-1 ring-inset ring-gray-200" aria-hidden="true">5</span> */} {/* <span class="ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-gray-50 px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600 ring-1 ring-inset ring-gray-200" aria-hidden="true">5</span> */}
</a> </a>
</li> </li>
<li>
<div class="flex items-center justify-between">
<span class="flex flex-grow flex-col">
<span class="text-sm text-black font-medium leading-6 " id="availability-label">
<i18n.Translate>Show withdrawal confirmation</i18n.Translate>
</span>
</span>
<button type="button" data-enabled={settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
onClick={() => {
console.log(settings.showWithdrawalSuccess)
updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
}}>
<span aria-hidden="true" data-enabled={settings.showWithdrawalSuccess} 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>
</li>
</ul> </ul>
</li> </li>
<li class="sm:hidden"> <li class="sm:hidden">
<div class="text-xs font-semibold leading-6 text-gray-400"> <div class="text-xs font-semibold leading-6 text-gray-400">
<i18n.Translate>Sites</i18n.Translate> <i18n.Translate>Sites</i18n.Translate>
@ -343,14 +363,14 @@ function StatusBanner(): VNode {
switch (n.message.type) { switch (n.message.type) {
case "error": case "error":
return <div class="rounded-md bg-red-50 p-4"> return <div class="rounded-md bg-red-50 p-4">
<div class="flex"> <div class="flex">
<div class="flex-shrink-0"> <div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> <svg class="h-5 w-5 text-red-400" 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 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
</svg> </svg>
</div> </div>
<div class="ml-3 flex-1 md:flex md:justify-between"> <div class="ml-3 flex-1 md:flex md:justify-between">
<p class="text-sm font-medium text-red-800">{n.message.title}</p> <p class="text-sm font-medium text-red-800">{n.message.title}</p>
<p class="mt-3 text-sm md:ml-6 md:mt-0"> <p class="mt-3 text-sm md:ml-6 md:mt-0">
<button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" <button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
onClick={(e) => { onClick={(e) => {
@ -363,15 +383,15 @@ function StatusBanner(): VNode {
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" /> <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
</svg> </svg>
</button> </button>
</p> </p>
</div>
</div> </div>
{n.message.description &&
<div class="mt-2 text-sm text-red-700">
{n.message.description}
</div>
}
</div> </div>
{n.message.description &&
<div class="mt-2 text-sm text-red-700">
{n.message.description}
</div>
}
</div>
case "info": case "info":
return <div class="rounded-md bg-green-50 border-4 border-green-600 p-6"> return <div class="rounded-md bg-green-50 border-4 border-green-600 p-6">
<div class="flex"> <div class="flex">

View File

@ -95,8 +95,9 @@ export function WithdrawalOperationPage({
}): VNode { }): VNode {
//FIXME: libeufin sandbox should return show to create the integration api endpoint //FIXME: libeufin sandbox should return show to create the integration api endpoint
//or return withdrawal uri from response //or return withdrawal uri from response
const baseUrl = getInitialBackendBaseURL()
const uri = stringifyWithdrawUri({ const uri = stringifyWithdrawUri({
bankIntegrationApiBaseUrl: `${getInitialBackendBaseURL()}/integration-api`, bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`,
withdrawalOperationId: operationId, withdrawalOperationId: operationId,
}); });
const parsedUri = parseWithdrawUri(uri); const parsedUri = parseWithdrawUri(uri);
@ -155,7 +156,7 @@ export function handleNotOkResult(
} }
case ErrorType.SERVER: { case ErrorType.SERVER: {
notify({ notify({
type: "error", type: "error",
title: i18n.str`Server returned with error`, title: i18n.str`Server returned with error`,
description: result.payload.error.description as TranslatedString, description: result.payload.error.description as TranslatedString,
debug: JSON.stringify(result.payload), debug: JSON.stringify(result.payload),
@ -164,7 +165,7 @@ export function handleNotOkResult(
} }
case ErrorType.UNREADABLE: { case ErrorType.UNREADABLE: {
notify({ notify({
type:"error", type: "error",
title: i18n.str`Unexpected error.`, title: i18n.str`Unexpected error.`,
description: i18n.str`Response from ${result.info?.url} is unreadable, http status: ${result.status}`, description: i18n.str`Response from ${result.info?.url} is unreadable, http status: ${result.status}`,
debug: JSON.stringify(result), debug: JSON.stringify(result),
@ -173,7 +174,7 @@ export function handleNotOkResult(
} }
case ErrorType.UNEXPECTED: { case ErrorType.UNEXPECTED: {
notify({ notify({
type:"error", type: "error",
title: i18n.str`Unexpected error.`, title: i18n.str`Unexpected error.`,
description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`, description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`,
debug: JSON.stringify(result), debug: JSON.stringify(result),

View File

@ -20,24 +20,23 @@ import {
HttpStatusCode, HttpStatusCode,
Logger, Logger,
PaytoUri, PaytoUri,
PaytoUriGeneric,
PaytoUriIBAN, PaytoUriIBAN,
PaytoUriTalerBank, PaytoUriTalerBank,
TranslatedString, TranslatedString,
WithdrawUriResult, WithdrawUriResult
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
RequestError, RequestError,
notify, notify,
notifyError, notifyError,
notifyInfo,
useTranslationContext, useTranslationContext,
} from "@gnu-taler/web-util/browser"; } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact"; import { Fragment, VNode, h } from "preact";
import { useMemo, useState } from "preact/hooks"; import { useMemo, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { useAccessAnonAPI } from "../hooks/access.js"; import { useAccessAnonAPI } from "../hooks/access.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { Amount } from "./PaytoWireTransferForm.js";
const logger = new Logger("WithdrawalConfirmationQuestion"); const logger = new Logger("WithdrawalConfirmationQuestion");
@ -71,6 +70,7 @@ export function WithdrawalConfirmationQuestion({
const { confirmWithdrawal, abortWithdrawal } = useAccessAnonAPI(); const { confirmWithdrawal, abortWithdrawal } = useAccessAnonAPI();
const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>(); const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>();
const answer = parseInt(captchaAnswer ?? "", 10); const answer = parseInt(captchaAnswer ?? "", 10);
const [busy, setBusy] = useState<Record<string, undefined>>()
const errors = undefinedIfEmpty({ const errors = undefinedIfEmpty({
answer: !captchaAnswer answer: !captchaAnswer
? i18n.str`Answer the question before continue` ? i18n.str`Answer the question before continue`
@ -79,13 +79,15 @@ export function WithdrawalConfirmationQuestion({
: answer !== captchaNumbers.a + captchaNumbers.b : answer !== captchaNumbers.a + captchaNumbers.b
? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.` ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.`
: undefined, : undefined,
}); }) ?? busy;
async function doTransfer() { async function doTransfer() {
try { try {
setBusy({})
await confirmWithdrawal( await confirmWithdrawal(
withdrawUri.withdrawalOperationId, withdrawUri.withdrawalOperationId,
); );
notifyInfo(i18n.str`Wire transfer completed!`)
} catch (error) { } catch (error) {
if (error instanceof RequestError) { if (error instanceof RequestError) {
notify( notify(
@ -107,10 +109,12 @@ export function WithdrawalConfirmationQuestion({
) )
} }
} }
setBusy(undefined)
} }
async function doCancel() { async function doCancel() {
try { try {
setBusy({})
await abortWithdrawal(withdrawUri.withdrawalOperationId); await abortWithdrawal(withdrawUri.withdrawalOperationId);
onAborted(); onAborted();
} catch (error) { } catch (error) {
@ -132,6 +136,7 @@ export function WithdrawalConfirmationQuestion({
) )
} }
} }
setBusy(undefined)
} }
return ( return (
@ -142,68 +147,6 @@ export function WithdrawalConfirmationQuestion({
<i18n.Translate>Confirm the withdrawal operation</i18n.Translate> <i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
</h3> </h3>
<div class="mt-2 max-w-xl text-sm text-gray-500"> <div class="mt-2 max-w-xl text-sm text-gray-500">
<div class="px-4 mt-4 ">
<div class="w-full">
<div class="px-4 sm:px-0">
<p><i18n.Translate>Wire transfer details</i18n.Translate></p>
</div>
<div class="mt-6 border-t border-gray-100">
<dl class="divide-y divide-gray-100">
{((): VNode => {
switch (details.account.targetType) {
case "iban": {
const p = details.account as PaytoUriIBAN
const name = p.params["receiver-name"]
return <Fragment>
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.iban}</dd>
</div>
{name &&
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange name</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
</div>
}
</Fragment>
}
case "x-taler-bank": {
const p = details.account as PaytoUriTalerBank
const name = p.params["receiver-name"]
return <Fragment>
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.account}</dd>
</div>
{name &&
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange name</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
</div>
}
</Fragment>
}
default:
return <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{details.account.targetPath}</dd>
</div>
}
})()}
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Withdrawal identification</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{details.reserve}</dd>
</div>
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{Amounts.stringifyValue(details.amount)}</dd>
</div>
</dl>
</div>
</div>
</div>
<div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-3 sm:gap-x-3"> <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-3 sm:gap-x-3">
<label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 ring-2 ring-indigo-600"}> <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 ring-2 ring-indigo-600"}>
@ -285,36 +228,32 @@ export function WithdrawalConfirmationQuestion({
aria-describedby="answer" aria-describedby="answer"
autoFocus autoFocus
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
// value={username ?? ""} value={captchaAnswer ?? ""}
required required
name="answer" name="answer"
id="answer" id="answer"
autocomplete="off" autocomplete="off"
// value={value ?? ""} onChange={(e): void => {
// disabled={!onChange} setCaptchaAnswer(e.currentTarget.value)
// onInput={(e): void => { }}
// if (onChange) {
// onChange(e.currentTarget.value);
// }
// }}
/> />
</div> </div>
<ShowInputErrorLabel message={errors?.answer} isDirty={false} /> <ShowInputErrorLabel message={errors?.answer} isDirty={captchaAnswer !== undefined} />
</div> </div>
</div> </div>
<div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
<button type="button" class="text-sm font-semibold leading-6 text-gray-900" <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
// onClick={onCancel} onClick={doCancel}
> >
<i18n.Translate>Cancel</i18n.Translate></button> <i18n.Translate>Cancel</i18n.Translate></button>
<button type="submit" <button type="submit"
class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
// disabled={isRawPayto ? !!errorsPayto : !!errorsWire} disabled={!!errors}
// onClick={(e) => { onClick={(e) => {
// e.preventDefault() e.preventDefault()
// doStart() doTransfer()
// }} }}
> >
<i18n.Translate>Transfer</i18n.Translate> <i18n.Translate>Transfer</i18n.Translate>
</button> </button>
@ -323,6 +262,68 @@ export function WithdrawalConfirmationQuestion({
</form> </form>
</div> </div>
</div> </div>
<div class="px-4 mt-4 ">
<div class="w-full">
<div class="px-4 sm:px-0">
<p><i18n.Translate>Wire transfer details</i18n.Translate></p>
</div>
<div class="mt-6 border-t border-gray-100">
<dl class="divide-y divide-gray-100">
{((): VNode => {
switch (details.account.targetType) {
case "iban": {
const p = details.account as PaytoUriIBAN
const name = p.params["receiver-name"]
return <Fragment>
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.iban}</dd>
</div>
{name &&
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange name</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
</div>
}
</Fragment>
}
case "x-taler-bank": {
const p = details.account as PaytoUriTalerBank
const name = p.params["receiver-name"]
return <Fragment>
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.account}</dd>
</div>
{name &&
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange name</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
</div>
}
</Fragment>
}
default:
return <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{details.account.targetPath}</dd>
</div>
}
})()}
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Withdrawal identification</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{details.reserve}</dd>
</div>
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{Amounts.stringifyValue(details.amount)}</dd>
</div>
</dl>
</div>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -62,9 +62,10 @@ export function WithdrawalQRCode({
result.type === ErrorType.CLIENT && result.type === ErrorType.CLIENT &&
result.status === HttpStatusCode.NotFound result.status === HttpStatusCode.NotFound
) { ) {
clearCurrentWithdrawal()
return <div>operation not found</div>; return <div>operation not found</div>;
} }
onLoadNotOk(); // onLoadNotOk();
return handleNotOkResult(i18n)(result); return handleNotOkResult(i18n)(result);
} }
const { data } = result; const { data } = result;
@ -85,12 +86,12 @@ export function WithdrawalQRCode({
</i18n.Translate> </i18n.Translate>
</p> </p>
<a class="pure-button pure-button-primary" <a class="pure-button pure-button-primary"
style={{float:"right"}} style={{ float: "right" }}
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
clearCurrentWithdrawal() clearCurrentWithdrawal()
onContinue() onContinue()
}}> }}>
{i18n.str`Continue`} {i18n.str`Continue`}
</a> </a>
@ -99,35 +100,82 @@ export function WithdrawalQRCode({
} }
if (data.confirmation_done) { if (data.confirmation_done) {
return <section id="main" class="content"> if (!settings.showWithdrawalSuccess) {
<h1 class="nav">{i18n.str`Operation completed`}</h1> clearCurrentWithdrawal()
onContinue()
<section id="assets" style={{maxWidth: 400, marginLeft: "auto", marginRight:"auto"}}> }
<p> return <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6">
<i18n.Translate> <div>
The wire transfer to the GNU Taler Exchange bank's account is completed, now the <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
exchange will send the requested amount into your GNU Taler wallet. <svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
</i18n.Translate> <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</p> </svg>
<p>
<i18n.Translate>
You can close this page now or continue to the account page.
</i18n.Translate>
</p>
<div style={{textAlign:"center"}}>
<a class="pure-button pure-button-primary"
onClick={async (e) => {
e.preventDefault();
clearCurrentWithdrawal()
onContinue()
}}>
{i18n.str`Continue`}
</a>
</div> </div>
</section> <div class="mt-3 text-center sm:mt-5">
</section> <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
<i18n.Translate>Withdrawal OK</i18n.Translate>
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
<i18n.Translate>
The wire transfer to the Taler exchange bank's account is completed, now the
exchange will send the requested amount into your GNU Taler wallet.
</i18n.Translate>
</p>
</div>
<div class="mt-2">
<p >
<i18n.Translate>
You can close this page now or continue to the account page.
</i18n.Translate>
</p>
</div>
</div>
</div>
<div class="mt-4">
<div class="flex items-center justify-between">
<span class="flex flex-grow flex-col">
<span class="text-sm text-black font-medium leading-6 " id="availability-label">
<i18n.Translate>Do not show this again</i18n.Translate>
</span>
</span>
<button type="button" data-enabled={!settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
onClick={() => {
updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
}}>
<span aria-hidden="true" data-enabled={!settings.showWithdrawalSuccess} 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 class="mt-5 sm:mt-6">
<button type="button"
class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
onClick={async (e) => {
e.preventDefault();
clearCurrentWithdrawal()
onContinue()
}}>
<i18n.Translate>Continue</i18n.Translate>
</button>
</div>
</div>
} }
if (!data.selection_done) {
return (
<QrCodeSection
withdrawUri={withdrawUri}
onAborted={() => {
notifyInfo(i18n.str`Operation canceled`);
clearCurrentWithdrawal()
onContinue()
}}
/>
);
}
if (!data.selected_reserve_pub) { if (!data.selected_reserve_pub) {
return <div> return <div>
the exchange is selcted but no reserve pub the exchange is selcted but no reserve pub
@ -138,23 +186,10 @@ export function WithdrawalQRCode({
if (!account) { if (!account) {
return <div> return <div>
the exchange is selcted but no account the exchange is selcted but no account
</div> </div>
} }
if (!data.selection_done) {
return (
<QrCodeSection
withdrawUri={withdrawUri}
onAborted={() => {
notifyInfo(i18n.str`Operation canceled`);
clearCurrentWithdrawal()
onContinue()
}}
/>
);
}
return ( return (
<WithdrawalConfirmationQuestion <WithdrawalConfirmationQuestion
withdrawUri={withdrawUri} withdrawUri={withdrawUri}
@ -167,7 +202,7 @@ export function WithdrawalQRCode({
notifyInfo(i18n.str`Operation canceled`); notifyInfo(i18n.str`Operation canceled`);
clearCurrentWithdrawal() clearCurrentWithdrawal()
onContinue() onContinue()
}} }}
/> />
); );
} }