This commit is contained in:
Sebastian 2023-09-21 15:44:17 -03:00
parent 4faa037c20
commit 56a6f47c7d
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
10 changed files with 552 additions and 256 deletions

View File

@ -76,9 +76,9 @@ export function Routing(): VNode {
onContinue={() => { onContinue={() => {
route("/account"); route("/account");
}} }}
onLoadNotOk={() => { // onLoadNotOk={() => {
route("/account"); // route("/account");
}} // }}
/> />
)} )}
/> />
@ -108,9 +108,9 @@ export function Routing(): VNode {
} else { } else {
return <HomePage return <HomePage
account={username} account={username}
onPendingOperationFound={(wopid) => { // onPendingOperationFound={(wopid) => {
route(`/operation/${wopid}`); // route(`/operation/${wopid}`);
}} // }}
goToBusinessAccount={() => { goToBusinessAccount={() => {
route("/business"); route("/business");
}} }}

View File

@ -29,20 +29,26 @@ import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
interface Settings { interface Settings {
currentWithdrawalOperationId: string | undefined; currentWithdrawalOperationId: string | undefined;
showWithdrawalSuccess: boolean; showWithdrawalSuccess: boolean;
showDemoDescription: boolean;
maxWithdrawalAmount: number; maxWithdrawalAmount: number;
fastWithdrawal: boolean;
} }
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("showWithdrawalSuccess", (codecForBoolean()))
.property("showDemoDescription", (codecForBoolean()))
.property("fastWithdrawal", (codecForBoolean()))
.property("maxWithdrawalAmount", codecForNumber()) .property("maxWithdrawalAmount", codecForNumber())
.build("Settings"); .build("Settings");
const defaultSettings: Settings = { const defaultSettings: Settings = {
currentWithdrawalOperationId: undefined, currentWithdrawalOperationId: undefined,
showWithdrawalSuccess: true, showWithdrawalSuccess: true,
maxWithdrawalAmount: 25 showDemoDescription: true,
maxWithdrawalAmount: 25,
fastWithdrawal: false,
}; };
const DEMOBANK_SETTINGS_KEY = buildStorageKey( const DEMOBANK_SETTINGS_KEY = buildStorageKey(

View File

@ -23,6 +23,7 @@ import { State } from "./index.js";
import { CopyButton } from "../../components/CopyButton.js"; import { CopyButton } from "../../components/CopyButton.js";
import { bankUiSettings } from "../../settings.js"; import { bankUiSettings } from "../../settings.js";
import { useBusinessAccountDetails } from "../../hooks/circuit.js"; import { useBusinessAccountDetails } from "../../hooks/circuit.js";
import { useSettings } from "../../hooks/settings.js";
export function InvalidIbanView({ error }: State.InvalidIban) { export function InvalidIbanView({ error }: State.InvalidIban) {
return ( return (
@ -78,9 +79,58 @@ function ImportantMessage(): VNode {
} }
function ShowDemoInfo():VNode {
const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings();
if (!settings.showDemoDescription) return <Fragment />
return <div class="rounded-md bg-blue-50 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-bold text-blue-800">
<i18n.Translate>This is a demo bank!</i18n.Translate>
</h3>
<div class="mt-2 text-sm text-blue-700">
<p>
<i18n.Translate>
This part of the demo shows how a bank that supports Taler
directly would work. In addition to using your own bank
account, you can also see the transaction history of some{" "}
<a href="/public-accounts">Public Accounts</a>.
</i18n.Translate>
</p>
<p class="mt-3 text-sm flex justify-end">
<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) => {
e.preventDefault();
updateSettings("showDemoDescription", false);
}}
>
Close
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<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>
</button>
</p>
</div>
</div>
</div>
</div>
}
export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> { export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
const { i18n } = useTranslationContext();
return <Fragment> return <Fragment>
<MaybeBusinessButton account={account} onClick={goToBusinessAccount} /> <MaybeBusinessButton account={account} onClick={goToBusinessAccount} />
<ShowDemoInfo />
<PaymentOptions limit={limit} /> <PaymentOptions limit={limit} />
<Transactions account={account} /> <Transactions account={account} />
</Fragment>; </Fragment>;

View File

@ -27,7 +27,6 @@ import { CopyButton, CopyIcon } from "../components/CopyButton.js";
import logo from "../assets/logo-2021.svg"; import logo from "../assets/logo-2021.svg";
import { useAccountDetails } from "../hooks/access.js"; import { useAccountDetails } from "../hooks/access.js";
const IS_PUBLIC_ACCOUNT_ENABLED = false;
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
@ -142,24 +141,40 @@ export function BankFrame({
<nav class="flex flex-1 flex-col" aria-label="Sidebar"> <nav class="flex flex-1 flex-col" aria-label="Sidebar">
<ul role="list" class="flex flex-1 flex-col gap-y-7"> <ul role="list" class="flex flex-1 flex-col gap-y-7">
<li> <li>
<ul role="list" class="-mx-2 space-y-1"> <a href="#"
<li> class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
<a href="#" onClick={() => {
class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold" backend.logOut();
onClick={() => { setOpen(false)
backend.logOut(); updateSettings("currentWithdrawalOperationId", undefined);
setOpen(false) }}
updateSettings("currentWithdrawalOperationId", undefined); >
}} <svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
> <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
<svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true"> </svg>
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /> Log out
</svg> {/* <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> */}
Log out </a>
{/* <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> */} </li>
</a> <li class="sm:hidden">
</li> <div class="text-xs font-semibold leading-6 text-gray-400">
<li> <i18n.Translate>Sites</i18n.Translate>
</div>
<ul role="list" class="-mx-2 mt-2 space-y-1">
{bankUiSettings.demoSites.map(([name, url]) => {
return <li>
<a href={url} target="_blank" rel="noopener noreferrer" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">&gt;</span>
<span class="truncate">{name}</span>
</a>
</li>
})}
</ul>
</li>
<li>
<ul role="list" class="space-y-1">
<li class="mt-2">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="flex flex-grow flex-col"> <span class="flex flex-grow flex-col">
<span class="text-sm text-black font-medium leading-6 " id="availability-label"> <span class="text-sm text-black font-medium leading-6 " id="availability-label">
@ -169,29 +184,43 @@ export function BankFrame({
<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" <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={() => { onClick={() => {
console.log(settings.showWithdrawalSuccess)
updateSettings("showWithdrawalSuccess", !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> <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> </button>
</div> </div>
</li> </li>
</ul> <li class="mt-2">
</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 demo description</i18n.Translate>
</span>
</span>
<button type="button" data-enabled={settings.showDemoDescription} 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("showDemoDescription", !settings.showDemoDescription);
<li class="sm:hidden"> }}>
<div class="text-xs font-semibold leading-6 text-gray-400"> <span aria-hidden="true" data-enabled={settings.showDemoDescription} 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>
<i18n.Translate>Sites</i18n.Translate> </button>
</div> </div>
<ul role="list" class="-mx-2 mt-2 space-y-1"> </li>
{bankUiSettings.demoSites.map(([name, url]) => { <li class="mt-2">
return <a href={url} target="_blank" rel="noopener noreferrer" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"> <div class="flex items-center justify-between">
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">&gt;</span> <span class="flex flex-grow flex-col">
<span class="truncate">{name}</span> <span class="text-sm text-black font-medium leading-6 " id="availability-label">
</a> <i18n.Translate>Use fast withdrawal</i18n.Translate>
})} </span>
</span>
<button type="button" data-enabled={settings.fastWithdrawal} 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("fastWithdrawal", !settings.fastWithdrawal);
}}>
<span aria-hidden="true" data-enabled={settings.fastWithdrawal} 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>
</ul> </ul>
@ -240,11 +269,6 @@ export function BankFrame({
// > // >
// <a href="#main" class="skip">{i18n.str`Skip to main content`}</a> // <a href="#main" class="skip">{i18n.str`Skip to main content`}</a>
// <div style="max-width: 50em; margin-left: 2em; margin-right: 2em;"> // <div style="max-width: 50em; margin-left: 2em; margin-right: 2em;">
// <h1>
// <span class="it">
// <a href="/">{bankUiSettings.bankName}</a>
// </span>
// </h1>
// {maybeDemoContent( // {maybeDemoContent(
// <p> // <p>
// {IS_PUBLIC_ACCOUNT_ENABLED ? ( // {IS_PUBLIC_ACCOUNT_ENABLED ? (
@ -298,33 +322,6 @@ export function BankFrame({
); );
} }
// function maybeDemoContent(content: VNode): VNode {
// if (bankUiSettings.showDemoNav) {
// return content;
// }
// return <Fragment />;
// }
// export function ErrorBannerFloat({
// error,
// onClear,
// }: {
// error: ErrorMessage;
// onClear?: () => void;
// }): VNode {
// return (
// <div
// style={{
// position: "fixed",
// top: 10,
// zIndex: 200,
// width: "90%",
// }}
// >
// <ErrorBanner error={error} onClear={onClear} />
// </div>
// );
// }
function StatusBanner(): VNode { function StatusBanner(): VNode {
const notifs = useNotifications() const notifs = useNotifications()
@ -469,5 +466,5 @@ function AccountBalance({ account }: { account: string }): VNode {
{Amounts.currencyOf(result.data.balance.amount)} {Amounts.currencyOf(result.data.balance.amount)}
&nbsp;{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""} &nbsp;{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""}
{Amounts.stringifyValue(result.data.balance.amount)} {Amounts.stringifyValue(result.data.balance.amount)}
</div> </div>
} }

View File

@ -52,21 +52,21 @@ const logger = new Logger("AccountPage");
export function HomePage({ export function HomePage({
onRegister, onRegister,
account, account,
onPendingOperationFound, // onPendingOperationFound,
goToBusinessAccount, goToBusinessAccount,
}: { }: {
account: string, account: string,
onPendingOperationFound: (id: string) => void; // onPendingOperationFound: (id: string) => void;
onRegister: () => void; onRegister: () => void;
goToBusinessAccount: () => void; goToBusinessAccount: () => void;
}): VNode { }): VNode {
const [settings] = useSettings(); const [settings] = useSettings();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
if (settings.currentWithdrawalOperationId) { // if (settings.currentWithdrawalOperationId) {
onPendingOperationFound(settings.currentWithdrawalOperationId); // onPendingOperationFound(settings.currentWithdrawalOperationId);
return <Loading />; // return <Loading />;
} // }
return ( return (
<AccountPage <AccountPage
@ -79,11 +79,9 @@ export function HomePage({
export function WithdrawalOperationPage({ export function WithdrawalOperationPage({
operationId, operationId,
onLoadNotOk,
onContinue, onContinue,
}: { }: {
operationId: string; operationId: string;
onLoadNotOk: () => void;
onContinue: () => void; onContinue: () => void;
}): 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
@ -95,6 +93,7 @@ export function WithdrawalOperationPage({
}); });
const parsedUri = parseWithdrawUri(uri); const parsedUri = parseWithdrawUri(uri);
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings();
if (!parsedUri) { if (!parsedUri) {
notifyError( notifyError(
@ -107,8 +106,9 @@ export function WithdrawalOperationPage({
return ( return (
<WithdrawalQRCode <WithdrawalQRCode
withdrawUri={parsedUri} withdrawUri={parsedUri}
onContinue={onContinue} onClose={() => {
onLoadNotOk={onLoadNotOk} updateSettings("currentWithdrawalOperationId", undefined)
}}
/> />
); );
} }

View File

@ -34,80 +34,83 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
// const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined); // const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined);
return ( return (
<fieldset> <div class="mt-2">
<legend class="px-4 text-base font-semibold leading-6 text-gray-900">
<i18n.Translate>Send money to</i18n.Translate>
</legend>
<div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4"> <fieldset>
{/* <!-- Active: "border-indigo-600 ring-2 ring-indigo-600", Not Active: "border-gray-300" --> */} <legend class="px-4 text-base font-semibold leading-6 text-gray-900">
<label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (tab === "charge-wallet" ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}> <i18n.Translate>Send money to</i18n.Translate>
<input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onClick={() => { </legend>
setTab("charge-wallet")
}} /> <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
<span class="flex flex-1"> {/* <!-- Active: "border-indigo-600 ring-2 ring-indigo-600", Not Active: "border-gray-300" --> */}
<div class="text-lg mr-2">&#x1F4B5;</div> <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (tab === "charge-wallet" ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
<span class="flex flex-col"> <input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onClick={() => {
<span id="project-type-0-label" class="block text-sm font-medium text-gray-900"> setTab("charge-wallet")
<i18n.Translate>a <b>Taler</b> wallet</i18n.Translate> }} />
</span> <span class="flex flex-1">
<span id="project-type-0-description-0" class="mt-1 flex items-center text-sm text-gray-500"> <div class="text-lg mr-2">&#x1F4B5;</div>
<i18n.Translate>Withdraw digital money into your mobile wallet or browser extension</i18n.Translate> <span class="flex flex-col">
<span id="project-type-0-label" class="block text-sm font-medium text-gray-900">
<i18n.Translate>a <b>Taler</b> wallet</i18n.Translate>
</span>
<span id="project-type-0-description-0" class="mt-1 flex items-center text-sm text-gray-500">
<i18n.Translate>Withdraw digital money into your mobile wallet or browser extension</i18n.Translate>
</span>
</span> </span>
</span> </span>
</span> <svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === "charge-wallet" ? "visible" : "hidden" }} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === "charge-wallet" ? "visible" : "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" />
<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>
</svg> </label>
</label>
<label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (tab === "wire-transfer" ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}> <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (tab === "wire-transfer" ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
<input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onClick={() => { <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onClick={() => {
setTab("wire-transfer") setTab("wire-transfer")
}} /> }} />
<span class="flex flex-1"> <span class="flex flex-1">
<div class="text-lg mr-2">&#x2194;</div> <div class="text-lg mr-2">&#x2194;</div>
<span class="flex flex-col"> <span class="flex flex-col">
<span id="project-type-1-label" class="block text-sm font-medium text-gray-900"> <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
<i18n.Translate>another bank account</i18n.Translate> <i18n.Translate>another bank account</i18n.Translate>
</span> </span>
<span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500"> <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
<i18n.Translate>Make a wire transfer to an account which you know the address.</i18n.Translate> <i18n.Translate>Make a wire transfer to an account which you know the address.</i18n.Translate>
</span>
</span> </span>
</span> </span>
</span> <svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === "wire-transfer" ? "visible" : "hidden" }} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === "wire-transfer" ? "visible" : "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" />
<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>
</svg> </label>
</label> </div>
</div> {tab === "charge-wallet" && (
{tab === "charge-wallet" && ( <WalletWithdrawForm
<WalletWithdrawForm focus
focus limit={limit}
limit={limit} onSuccess={(id) => {
onSuccess={(id) => { updateSettings("currentWithdrawalOperationId", id);
updateSettings("currentWithdrawalOperationId", id); }}
}} onCancel={() => {
onCancel={() => { setTab(undefined)
setTab(undefined) }}
}} />
/> )}
)} {tab === "wire-transfer" && (
{tab === "wire-transfer" && ( <PaytoWireTransferForm
<PaytoWireTransferForm focus
focus title={i18n.str`Transfer details`}
title={i18n.str`Transfer details`} limit={limit}
limit={limit} onSuccess={() => {
onSuccess={() => { notifyInfo(i18n.str`Wire transfer created!`);
notifyInfo(i18n.str`Wire transfer created!`); }}
}} onCancel={() => {
onCancel={() => { setTab(undefined)
setTab(undefined) }}
}} />
/> )}
)}
</fieldset> </fieldset>
</div>
) )
} }

View File

@ -135,3 +135,109 @@ export function QrCodeSection({
</Fragment> </Fragment>
); );
} }
export function QrCodeSectionSimpler({
withdrawUri,
onAborted,
}: {
withdrawUri: WithdrawUriResult;
onAborted: () => void;
}): VNode {
const { i18n } = useTranslationContext();
useEffect(() => {
//Taler Wallet WebExtension is listening to headers response and tab updates.
//In the SPA there is no header response with the Taler URI so
//this hack manually triggers the tab update after the QR is in the DOM.
// WebExtension will be using
// https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
}, []);
const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
const { abortWithdrawal } = useAccessAnonAPI();
async function doAbort() {
try {
await abortWithdrawal(withdrawUri.withdrawalOperationId);
onAborted();
} catch (error) {
if (error instanceof RequestError) {
notify(
buildRequestErrorMessage(i18n, error.cause, {
onClientError: (status) =>
status === HttpStatusCode.Conflict
? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
: undefined,
}),
);
} else {
notifyError(
i18n.str`Operation failed, please report`,
(error instanceof Error
? error.message
: JSON.stringify(error)) as TranslatedString
)
}
}
}
return (
<Fragment>
<div class="bg-white shadow-xl sm:rounded-lg">
<div class="p2 ">
<h3 class="text-base font-semibold leading-6 text-gray-900">
<i18n.Translate>If you have a Taler wallet installed in this device</i18n.Translate>
</h3>
<div class="mt-4">
<a href={talerWithdrawUri}
// class="text-sm font-semibold leading-6 text-gray-900 btn "
class="inline-flex items-center 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"
>
<i18n.Translate>Click here to start</i18n.Translate>
</a>
</div>
<div class="mt-4 max-w-xl text-sm text-gray-500">
<p><i18n.Translate>
You will see the details of the operation in your wallet including the fees (if applies).
If you still one you can install it from <a class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net/en/wallet.html">here</a>.
</i18n.Translate></p>
</div>
<div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 pt-2 mt-2 ">
<div />
<button type="button"
class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
onClick={doAbort}
>
Cancel withdrawal
</button>
</div>
</div>
</div>
<div class="bg-white shadow-xl sm:rounded-lg mt-8">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold leading-6 text-gray-900">
<i18n.Translate>Or if you have the wallet in another device</i18n.Translate>
</h3>
<div class="mt-4 max-w-xl text-sm text-gray-500">
<i18n.Translate>Scan the QR below to start the withdrawal</i18n.Translate>
</div>
<div class="mt-2 max-w-md ml-auto mr-auto">
<QR text={talerWithdrawUri} />
</div>
</div>
<div class="flex items-center justify-center gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
<button type="button"
class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
onClick={doAbort}
>
Cancel withdrawal
</button>
</div>
</div>
</Fragment>
);
}

View File

@ -20,6 +20,7 @@ import {
HttpStatusCode, HttpStatusCode,
Logger, Logger,
TranslatedString, TranslatedString,
WithdrawUriResult,
parseWithdrawUri, parseWithdrawUri,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
@ -34,6 +35,9 @@ import { useEffect, useRef, useState } from "preact/hooks";
import { useAccessAPI } from "../hooks/access.js"; import { useAccessAPI } from "../hooks/access.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
import { Amount } from "./PaytoWireTransferForm.js"; import { Amount } from "./PaytoWireTransferForm.js";
import { useSettings } from "../hooks/settings.js";
import { WithdrawalOperationState } from "./WithdrawalQRCode.js";
import { Loading } from "../components/Loading.js";
const logger = new Logger("WalletWithdrawForm"); const logger = new Logger("WalletWithdrawForm");
const RefAmount = forwardRef(Amount); const RefAmount = forwardRef(Amount);
@ -51,8 +55,9 @@ export function WalletWithdrawForm({
}): VNode { }): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { createWithdrawal } = useAccessAPI(); const { createWithdrawal } = useAccessAPI();
const [settings, updateSettings] = useSettings()
const [amountStr, setAmountStr] = useState<string | undefined>("5.00"); const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
const ref = useRef<HTMLInputElement>(null); const ref = useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {
if (focus) ref.current?.focus(); if (focus) ref.current?.focus();
@ -78,7 +83,6 @@ export function WalletWithdrawForm({
async function doStart() { async function doStart() {
if (!parsedAmount) return; if (!parsedAmount) return;
try { try {
console.log("ASDASD")
const result = await createWithdrawal({ const result = await createWithdrawal({
amount: Amounts.stringify(parsedAmount), amount: Amounts.stringify(parsedAmount),
}); });
@ -109,7 +113,6 @@ export function WalletWithdrawForm({
) )
} }
} }
} }
return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg"> return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
@ -122,91 +125,103 @@ export function WalletWithdrawForm({
<i18n.Translate>After using your wallet you will be redirected here to confirm or cancel the operation.</i18n.Translate> <i18n.Translate>After using your wallet you will be redirected here to confirm or cancel the operation.</i18n.Translate>
</p> </p>
</div> </div>
<form
class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
autoCapitalize="none"
autoCorrect="off"
onSubmit={e => {
e.preventDefault()
}}
>
<div class="px-4 py-6 sm:p-8">
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-5">
<label for="withdraw-amount">{i18n.str`Amount`}</label>
<RefAmount
currency={limit.currency}
value={amountStr}
name="withdraw-amount"
onChange={(v) => {
setAmountStr(v);
}}
error={errors?.amount}
ref={ref}
/>
</div>
<div class="sm:col-span-5">
<span class="isolate inline-flex rounded-md shadow-sm">
<button type="button"
class="relative inline-flex px-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
onClick={(e) => {
e.preventDefault();
setAmountStr("50.00")
}}
>
50.00
</button>
<button type="button"
class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
onClick={(e) => {
e.preventDefault();
setAmountStr("25.00")
}}
>
25.00 {!settings.fastWithdrawal ?
</button> <form
<button type="button" class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10" autoCapitalize="none"
onClick={(e) => { autoCorrect="off"
e.preventDefault(); onSubmit={e => {
setAmountStr("10.00") e.preventDefault()
}}
>
<div class="px-4 py-6 sm:p-8">
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-5">
<label for="withdraw-amount">{i18n.str`Amount`}</label>
<RefAmount
currency={limit.currency}
value={amountStr}
name="withdraw-amount"
onChange={(v) => {
setAmountStr(v);
}} }}
> error={errors?.amount}
10.00 ref={ref}
</button> />
<button type="button" </div>
class="relative inline-flex px-6 py-4 text-sm items-center rounded-r-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10" <div class="sm:col-span-5">
onClick={(e) => { <span class="isolate inline-flex rounded-md shadow-sm">
e.preventDefault(); <button type="button"
setAmountStr("5.00") class="relative inline-flex px-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
}} onClick={(e) => {
> e.preventDefault();
5.00 setAmountStr("50.00")
</button> }}
</span> >
</div> 50.00
</button>
<button type="button"
class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
onClick={(e) => {
e.preventDefault();
setAmountStr("25.00")
}}
>
25.00
</button>
<button type="button"
class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
onClick={(e) => {
e.preventDefault();
setAmountStr("10.00")
}}
>
10.00
</button>
<button type="button"
class="relative inline-flex px-6 py-4 text-sm items-center rounded-r-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
onClick={(e) => {
e.preventDefault();
setAmountStr("5.00")
}}
>
5.00
</button>
</span>
</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">
<button type="button" class="text-sm font-semibold leading-6 text-gray-900"
onClick={onCancel}
>
<i18n.Translate>Cancel</i18n.Translate></button>
<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"
// disabled={isRawPayto ? !!errorsPayto : !!errorsWire}
onClick={(e) => {
e.preventDefault()
doStart()
}}
>
<i18n.Translate>Continue</i18n.Translate>
</button>
</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">
<button type="button" class="text-sm font-semibold leading-6 text-gray-900"
onClick={onCancel}
>
<i18n.Translate>Cancel</i18n.Translate></button>
<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"
// disabled={isRawPayto ? !!errorsPayto : !!errorsWire}
onClick={(e) => {
e.preventDefault()
doStart()
}}
>
<i18n.Translate>Continue</i18n.Translate>
</button>
</div>
</form> </form>
: settings.currentWithdrawalOperationId === undefined ?
<Loading /> :
<WithdrawalOperationState
currentOperation={settings.currentWithdrawalOperationId}
currency={limit.currency}
onClose={() => {
onCancel()
}}
/>
}
</div> </div>
); );
} }

View File

@ -18,24 +18,29 @@ import {
Amounts, Amounts,
HttpStatusCode, HttpStatusCode,
Logger, Logger,
TranslatedString,
WithdrawUriResult, WithdrawUriResult,
parsePaytoUri, parsePaytoUri,
parseWithdrawUri,
stringifyWithdrawUri,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { ErrorType, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; import { ErrorType, RequestError, notify, notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact"; import { Fragment, VNode, h } from "preact";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { useWithdrawalDetails } from "../hooks/access.js"; import { useAccessAPI, useWithdrawalDetails } from "../hooks/access.js";
import { useSettings } from "../hooks/settings.js"; import { useSettings } from "../hooks/settings.js";
import { handleNotOkResult } from "./HomePage.js"; import { handleNotOkResult } from "./HomePage.js";
import { QrCodeSection } from "./QrCodeSection.js"; import { QrCodeSection, QrCodeSectionSimpler } from "./QrCodeSection.js";
import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js"; import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
import { useEffect, useState } from "preact/hooks";
import { buildRequestErrorMessage } from "../utils.js";
import { getInitialBackendBaseURL } from "../hooks/backend.js";
const logger = new Logger("WithdrawalQRCode"); const logger = new Logger("WithdrawalQRCode");
interface Props { interface Props {
withdrawUri: WithdrawUriResult; withdrawUri: WithdrawUriResult;
onContinue: () => void; onClose: () => void;
onLoadNotOk: () => void;
} }
/** /**
* Offer the QR code (and a clickable taler://-link) to * Offer the QR code (and a clickable taler://-link) to
@ -44,14 +49,9 @@ interface Props {
*/ */
export function WithdrawalQRCode({ export function WithdrawalQRCode({
withdrawUri, withdrawUri,
onContinue, onClose,
onLoadNotOk,
}: Props): VNode { }: Props): VNode {
const [settings, updateSettings] = useSettings(); const [settings, updateSettings] = useSettings();
function clearCurrentWithdrawal(): void {
updateSettings("currentWithdrawalOperationId", undefined);
onContinue();
}
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId); const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
if (!result.ok) { if (!result.ok) {
@ -62,7 +62,7 @@ export function WithdrawalQRCode({
result.type === ErrorType.CLIENT && result.type === ErrorType.CLIENT &&
result.status === HttpStatusCode.NotFound result.status === HttpStatusCode.NotFound
) { ) {
clearCurrentWithdrawal() onClose()
return <div>operation not found</div>; return <div>operation not found</div>;
} }
// onLoadNotOk(); // onLoadNotOk();
@ -89,8 +89,7 @@ export function WithdrawalQRCode({
style={{ float: "right" }} style={{ float: "right" }}
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
clearCurrentWithdrawal() onClose()
onContinue()
}}> }}>
{i18n.str`Continue`} {i18n.str`Continue`}
</a> </a>
@ -149,8 +148,7 @@ export function WithdrawalQRCode({
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" 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) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
clearCurrentWithdrawal() onClose()
onContinue()
}}> }}>
<i18n.Translate>Continue</i18n.Translate> <i18n.Translate>Continue</i18n.Translate>
</button> </button>
@ -165,8 +163,7 @@ export function WithdrawalQRCode({
withdrawUri={withdrawUri} withdrawUri={withdrawUri}
onAborted={() => { onAborted={() => {
notifyInfo(i18n.str`Operation canceled`); notifyInfo(i18n.str`Operation canceled`);
clearCurrentWithdrawal() onClose()
onContinue()
}} }}
/> />
); );
@ -196,9 +193,131 @@ export function WithdrawalQRCode({
}} }}
onAborted={() => { onAborted={() => {
notifyInfo(i18n.str`Operation canceled`); notifyInfo(i18n.str`Operation canceled`);
clearCurrentWithdrawal() onClose()
onContinue()
}} }}
/> />
); );
}
export function WithdrawalOperationState({
currency,
currentOperation,
onClose,
}: {currency:string, currentOperation: string, onClose: () => void}): VNode {
const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings()
const { createWithdrawal } = useAccessAPI();
const amount = settings.maxWithdrawalAmount
async function doSilentStart() {
//FIXME: if amount is not enough use balance
const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
try {
const result = await createWithdrawal({
amount: Amounts.stringify(parsedAmount),
});
const uri = parseWithdrawUri(result.data.taler_withdraw_uri);
if (!uri) {
return notifyError(
i18n.str`Server responded with an invalid withdraw URI`,
i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`);
} else {
updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
}
} catch (error) {
if (error instanceof RequestError) {
notify(
buildRequestErrorMessage(i18n, error.cause, {
onClientError: (status) =>
status === HttpStatusCode.Forbidden
? i18n.str`The operation was rejected due to insufficient funds`
: undefined,
}),
);
} else {
notifyError(
i18n.str`Operation failed, please report`,
(error instanceof Error
? error.message
: JSON.stringify(error)) as TranslatedString
)
}
}
}
useEffect(() => {
doSilentStart()
}, [settings.fastWithdrawal, amount])
const result = useWithdrawalDetails(currentOperation);
if (!result.ok) {
if (result.loading) {
return <Loading />;
}
if (
result.type === ErrorType.CLIENT &&
result.status === HttpStatusCode.NotFound
) {
onClose()
return <div>operation not found</div>;
}
// onLoadNotOk();
return handleNotOkResult(i18n)(result);
}
const { data } = result;
const baseUrl = getInitialBackendBaseURL()
const uri = stringifyWithdrawUri({
bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`,
withdrawalOperationId: currentOperation,
});
const parsedUri = parseWithdrawUri(uri);
if (data.aborted) {
return <div>
the operation was aborted, you can create another one
</div>
}
if (data.confirmation_done) {
return <div>
the wire transfer is made, you coin should arrive shortly
</div>
}
if (!parsedUri) {
return <div>
the operation is not valid, create another one
</div>
}
if (!data.selection_done) {
return (
<QrCodeSectionSimpler
withdrawUri={parsedUri}
onAborted={() => {
notifyInfo(i18n.str`Operation canceled`);
onClose()
}}
/>
);
}
if (!data.selected_reserve_pub) {
return <div>
the exchange is selcted but no reserve pub
</div>
}
const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account)
if (!account) {
return <div>
the exchange is selected but no account
</div>
}
return <div>
the operation is wating for the question to be answered
</div>;
} }

View File

@ -61,7 +61,7 @@ export function RemoveAccount({
</h3> </h3>
<div class="mt-2 text-sm text-yellow-700"> <div class="mt-2 text-sm text-yellow-700">
<p> <p>
<i18n.Translate>The account can be delete while still holding some balance. First make sure that the owner make a complete cashout.</i18n.Translate> <i18n.Translate>The account can't be delete while still holding some balance. First make sure that the owner make a complete cashout.</i18n.Translate>
</p> </p>
</div> </div>
</div> </div>