more ui
This commit is contained in:
parent
e39d5c488e
commit
7d4c5a71aa
@ -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 = {
|
||||||
|
@ -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(
|
||||||
|
@ -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">
|
||||||
|
@ -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),
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user