more ui
This commit is contained in:
parent
4faa037c20
commit
56a6f47c7d
@ -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");
|
||||||
}}
|
}}
|
||||||
|
@ -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(
|
||||||
|
@ -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>;
|
||||||
|
@ -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">></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">></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)}
|
||||||
{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""}
|
{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""}
|
||||||
{Amounts.stringifyValue(result.data.balance.amount)}
|
{Amounts.stringifyValue(result.data.balance.amount)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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">💵</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">💵</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">↔</div>
|
<div class="text-lg mr-2">↔</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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user