summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-09-22 15:29:19 -0300
committerSebastian <sebasjm@gmail.com>2023-09-25 14:50:43 -0300
commita59df74fb2b4374fd58f68fd4abaffe623cd54d6 (patch)
tree01d930cbdf2f50f1d3b228af37ebaa9c2c183489
parentdfd23f63ba40a2afb0cb41bf742b0ae647a2b38c (diff)
downloadwallet-core-a59df74fb2b4374fd58f68fd4abaffe623cd54d6.tar.gz
wallet-core-a59df74fb2b4374fd58f68fd4abaffe623cd54d6.tar.bz2
wallet-core-a59df74fb2b4374fd58f68fd4abaffe623cd54d6.zip
more ui
-rw-r--r--packages/demobank-ui/src/components/Routing.tsx27
-rw-r--r--packages/demobank-ui/src/hooks/settings.ts3
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/index.ts2
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/state.ts3
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/views.tsx4
-rw-r--r--packages/demobank-ui/src/pages/BankFrame.tsx15
-rw-r--r--packages/demobank-ui/src/pages/HomePage.tsx17
-rw-r--r--packages/demobank-ui/src/pages/OperationState/index.ts10
-rw-r--r--packages/demobank-ui/src/pages/OperationState/state.ts109
-rw-r--r--packages/demobank-ui/src/pages/OperationState/views.tsx363
-rw-r--r--packages/demobank-ui/src/pages/PaymentOptions.tsx17
-rw-r--r--packages/demobank-ui/src/pages/QrCodeSection.tsx104
-rw-r--r--packages/demobank-ui/src/pages/WalletWithdrawForm.tsx273
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalQRCode.tsx163
-rw-r--r--packages/demobank-ui/src/pages/business/Home.tsx1
15 files changed, 693 insertions, 418 deletions
diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index e1fd93737..90d2d4c48 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -46,6 +46,20 @@ export function Routing(): VNode {
)}
/>
<Route
+ path="/operation/:wopid"
+ component={({ wopid }: { wopid: string }) => (
+ <WithdrawalOperationPage
+ operationId={wopid}
+ onContinue={() => {
+ route("/account");
+ }}
+ // onLoadNotOk={() => {
+ // route("/account");
+ // }}
+ />
+ )}
+ />
+ <Route
path="/register"
component={() => (
<RegistrationPage
@@ -65,10 +79,6 @@ export function Routing(): VNode {
<BankFrame account={backend.state.username}>
<Router history={history}>
<Route
- path="/test"
- component={Test}
- />
- <Route
path="/operation/:wopid"
component={({ wopid }: { wopid: string }) => (
<WithdrawalOperationPage
@@ -76,9 +86,6 @@ export function Routing(): VNode {
onContinue={() => {
route("/account");
}}
- // onLoadNotOk={() => {
- // route("/account");
- // }}
/>
)}
/>
@@ -108,9 +115,9 @@ export function Routing(): VNode {
} else {
return <HomePage
account={username}
- // onPendingOperationFound={(wopid) => {
- // route(`/operation/${wopid}`);
- // }}
+ goToConfirmOperation={(wopid) => {
+ route(`/operation/${wopid}`);
+ }}
goToBusinessAccount={() => {
route("/business");
}}
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index c2fd93a0c..5f004c6d4 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -30,6 +30,7 @@ interface Settings {
currentWithdrawalOperationId: string | undefined;
showWithdrawalSuccess: boolean;
showDemoDescription: boolean;
+ showInstallWallet: boolean;
maxWithdrawalAmount: number;
fastWithdrawal: boolean;
}
@@ -39,6 +40,7 @@ export const codecForSettings = (): Codec<Settings> =>
.property("currentWithdrawalOperationId", codecOptional(codecForString()))
.property("showWithdrawalSuccess", (codecForBoolean()))
.property("showDemoDescription", (codecForBoolean()))
+ .property("showInstallWallet", (codecForBoolean()))
.property("fastWithdrawal", (codecForBoolean()))
.property("maxWithdrawalAmount", codecForNumber())
.build("Settings");
@@ -47,6 +49,7 @@ const defaultSettings: Settings = {
currentWithdrawalOperationId: undefined,
showWithdrawalSuccess: true,
showDemoDescription: true,
+ showInstallWallet: true,
maxWithdrawalAmount: 25,
fastWithdrawal: false,
};
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts
index 128a6d30f..81eeb4a03 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -29,6 +29,7 @@ export interface Props {
error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
) => VNode;
goToBusinessAccount: () => void;
+ goToConfirmOperation: (id:string) => void;
}
export type State = State.Loading | State.LoadingError | State.Ready | State.InvalidIban | State.UserNotFound;
@@ -54,6 +55,7 @@ export namespace State {
account: string,
limit: AmountJson,
goToBusinessAccount: () => void;
+ goToConfirmOperation: (id:string) => void;
}
export interface InvalidIban {
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
index a57e19901..1a1475c0d 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -20,7 +20,7 @@ import { useBackendContext } from "../../context/backend.js";
import { useAccountDetails } from "../../hooks/access.js";
import { Props, State } from "./index.js";
-export function useComponentState({ account, goToBusinessAccount }: Props): State {
+export function useComponentState({ account, goToBusinessAccount, goToConfirmOperation }: Props): State {
const result = useAccountDetails(account);
const backend = useBackendContext();
const { i18n } = useTranslationContext();
@@ -75,6 +75,7 @@ export function useComponentState({ account, goToBusinessAccount }: Props): Stat
return {
status: "ready",
goToBusinessAccount,
+ goToConfirmOperation,
error: undefined,
account,
limit,
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 0187989af..23a815bd8 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -123,7 +123,7 @@ function ShowDemoInfo():VNode {
</div>
}
-export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
+export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> {
const { i18n } = useTranslationContext();
return <Fragment>
@@ -131,7 +131,7 @@ export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready):
<ShowDemoInfo />
- <PaymentOptions limit={limit} />
+ <PaymentOptions limit={limit} goToConfirmOperation={goToConfirmOperation} />
<Transactions account={account} />
</Fragment>;
}
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index d1c94135b..5bfaa63ec 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -210,6 +210,21 @@ export function BankFrame({
<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 install wallet first</i18n.Translate>
+ </span>
+ </span>
+ <button type="button" data-enabled={settings.showInstallWallet} 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("showInstallWallet", !settings.showInstallWallet);
+ }}>
+ <span aria-hidden="true" data-enabled={settings.showInstallWallet} 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>
+ <li class="mt-2">
+ <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>Use fast withdrawal</i18n.Translate>
</span>
</span>
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 2acfc9b57..8d5e1f3b9 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -36,6 +36,7 @@ import { useSettings } from "../hooks/settings.js";
import { AccountPage } from "./AccountPage/index.js";
import { LoginForm } from "./LoginForm.js";
import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
+import { route } from "preact-router";
const logger = new Logger("AccountPage");
@@ -52,25 +53,20 @@ const logger = new Logger("AccountPage");
export function HomePage({
onRegister,
account,
- // onPendingOperationFound,
+ goToConfirmOperation,
goToBusinessAccount,
}: {
account: string,
- // onPendingOperationFound: (id: string) => void;
onRegister: () => void;
goToBusinessAccount: () => void;
+ goToConfirmOperation: (id:string) => void;
}): VNode {
- const [settings] = useSettings();
const { i18n } = useTranslationContext();
- // if (settings.currentWithdrawalOperationId) {
- // onPendingOperationFound(settings.currentWithdrawalOperationId);
- // return <Loading />;
- // }
-
return (
<AccountPage
account={account}
+ goToConfirmOperation={goToConfirmOperation}
goToBusinessAccount={goToBusinessAccount}
onLoadNotOk={handleNotOkResult(i18n, onRegister)}
/>
@@ -102,12 +98,13 @@ export function WithdrawalOperationPage({
);
return <Loading />;
}
-
+
return (
<WithdrawalQRCode
withdrawUri={parsedUri}
onClose={() => {
updateSettings("currentWithdrawalOperationId", undefined)
+ onContinue()
}}
/>
);
@@ -178,7 +175,7 @@ export function handleNotOkResult(
assertUnreachable(result);
}
}
-
+ route("/")
return <div>error</div>;
}
return <div />;
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts b/packages/demobank-ui/src/pages/OperationState/index.ts
index 254fcba5f..32302f272 100644
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ b/packages/demobank-ui/src/pages/OperationState/index.ts
@@ -26,6 +26,7 @@ import { ErrorLoading } from "../../components/ErrorLoading.js";
export interface Props {
currency: string;
onClose: () => void;
+ goToConfirmOperation: (id: string) => void;
}
export type State = State.Loading |
@@ -57,26 +58,33 @@ export namespace State {
error: undefined;
uri: WithdrawUriResult,
onClose: () => void;
+ onAbort: () => void;
}
export interface InvalidPayto {
status: "invalid-payto",
error: undefined;
payto: string | null;
+ onClose: () => void;
}
export interface InvalidWithdrawal {
status: "invalid-withdrawal",
error: undefined;
+ onClose: () => void;
uri: string,
}
export interface InvalidReserve {
status: "invalid-reserve",
error: undefined;
+ onClose: () => void;
reserve: string | null;
}
export interface NeedConfirmation {
status: "need-confirmation",
+ onAbort: () => void;
+ onConfirm: () => void;
error: undefined;
+ busy: boolean,
}
export interface Aborted {
status: "aborted",
@@ -111,7 +119,7 @@ const viewMapping: utils.StateViewMap<State> = {
ready: ReadyView,
};
-export const AccountPage = utils.compose(
+export const OperationState = utils.compose(
(p: Props) => useComponentState(p),
viewMapping,
);
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index 6fb7bb28f..ae03ed529 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -15,21 +15,24 @@
*/
import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { ErrorType, RequestError, notify, notifyError, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
+import { ErrorType, RequestError, notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
import { useBackendContext } from "../../context/backend.js";
-import { useAccessAPI, useAccountDetails, useWithdrawalDetails } from "../../hooks/access.js";
+import { useAccessAPI, useAccessAnonAPI, useAccountDetails, useWithdrawalDetails } from "../../hooks/access.js";
import { Props, State } from "./index.js";
import { useSettings } from "../../hooks/settings.js";
-import { buildRequestErrorMessage } from "../../utils.js";
-import { useEffect } from "preact/hooks";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
+import { useEffect, useMemo, useState } from "preact/hooks";
import { getInitialBackendBaseURL } from "../../hooks/backend.js";
-export function useComponentState({ currency, onClose }: Props): utils.RecursiveState<State> {
+export function useComponentState({ currency, onClose,goToConfirmOperation }: Props): utils.RecursiveState<State> {
const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings()
const { createWithdrawal } = useAccessAPI();
+ const { abortWithdrawal, confirmWithdrawal } = useAccessAnonAPI();
+ const [busy, setBusy] = useState<Record<string, undefined>>()
const amount = settings.maxWithdrawalAmount
+
async function doSilentStart() {
//FIXME: if amount is not enough use balance
const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
@@ -67,12 +70,14 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
}
+ const withdrawalOperationId = settings.currentWithdrawalOperationId
useEffect(() => {
- doSilentStart()
+ if (withdrawalOperationId === undefined) {
+ doSilentStart()
+ }
}, [settings.fastWithdrawal, amount])
const baseUrl = getInitialBackendBaseURL()
- const withdrawalOperationId = settings.currentWithdrawalOperationId
if (!withdrawalOperationId) {
return {
@@ -81,6 +86,63 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
}
+ const wid = withdrawalOperationId
+
+ async function doAbort() {
+ try {
+ setBusy({})
+ await abortWithdrawal(wid);
+ onClose();
+ } 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
+ )
+ }
+ }
+ setBusy(undefined)
+ }
+
+ async function doConfirm() {
+ try {
+ setBusy({})
+ await confirmWithdrawal(wid);
+ notifyInfo(i18n.str`Wire transfer completed!`)
+ } catch (error) {
+ if (error instanceof RequestError) {
+ notify(
+ buildRequestErrorMessage(i18n, error.cause, {
+ onClientError: (status) =>
+ status === HttpStatusCode.Conflict
+ ? i18n.str`The withdrawal has been aborted previously and can't be confirmed`
+ : status === HttpStatusCode.UnprocessableEntity
+ ? i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`
+ : undefined,
+ }),
+ );
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
+ }
+ setBusy(undefined)
+ }
const bankIntegrationApiBaseUrl = `${baseUrl}/integration-api`
const uri = stringifyWithdrawUri({
bankIntegrationApiBaseUrl,
@@ -92,11 +154,13 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
status: "invalid-withdrawal",
error: undefined,
uri,
+ onClose,
}
}
return (): utils.RecursiveState<State> => {
const result = useWithdrawalDetails(withdrawalOperationId);
+
if (!result.ok) {
if (result.loading) {
return {
@@ -119,10 +183,17 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
if (data.confirmation_done) {
+ if (!settings.showWithdrawalSuccess) {
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ }
return {
status: "confirmed",
error: undefined,
- onClose,
+ onClose: async () => {
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ },
}
}
@@ -131,7 +202,12 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
status: "ready",
error: undefined,
uri: parsedUri,
- onClose
+ onClose: async () => {
+ await doAbort()
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ },
+ onAbort: doAbort,
}
}
@@ -139,7 +215,8 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
return {
status: "invalid-reserve",
error: undefined,
- reserve: data.selected_reserve_pub
+ reserve: data.selected_reserve_pub,
+ onClose,
}
}
@@ -149,13 +226,23 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
return {
status: "invalid-payto",
error: undefined,
- payto: data.selected_exchange_account
+ payto: data.selected_exchange_account,
+ onClose,
}
}
+
+ // goToConfirmOperation(withdrawalOperationId)
return {
status: "need-confirmation",
error: undefined,
+ onAbort: async () => {
+ await doAbort()
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose()
+ },
+ busy: !!busy,
+ onConfirm: doConfirm
}
}
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
index db25eaf61..17f1d8457 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, stringifyPaytoUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { Transactions } from "../../components/Transactions/index.js";
@@ -24,42 +24,375 @@ import { CopyButton } from "../../components/CopyButton.js";
import { bankUiSettings } from "../../settings.js";
import { useBusinessAccountDetails } from "../../hooks/circuit.js";
import { useSettings } from "../../hooks/settings.js";
+import { useEffect, useMemo, useState } from "preact/hooks";
+import { undefinedIfEmpty } from "../../utils.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { QR } from "../../components/QR.js";
-export function InvalidPaytoView({ error }: State.InvalidPayto) {
+export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
return (
- <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+ <div>Payto from server is not valid &quot;{payto}&quot;</div>
);
}
-export function InvalidWithdrawalView({ error }: State.InvalidWithdrawal) {
+export function InvalidWithdrawalView({ uri, onClose }: State.InvalidWithdrawal) {
return (
- <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+ <div>Withdrawal uri from server is not valid &quot;{uri}&quot;</div>
);
}
-export function InvalidReserveView({ error }: State.InvalidReserve) {
+export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) {
return (
- <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+ <div>Reserve from server is not valid &quot;{reserve}&quot;</div>
);
}
-export function NeedConfirmationView({ error }: State.NeedConfirmation) {
+export function NeedConfirmationView({ error, onAbort, onConfirm, busy }: State.NeedConfirmation) {
+ const { i18n } = useTranslationContext()
+
+ const captchaNumbers = useMemo(() => {
+ return {
+ a: Math.floor(Math.random() * 10),
+ b: Math.floor(Math.random() * 10),
+ };
+ }, []);
+ const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>();
+ const answer = parseInt(captchaAnswer ?? "", 10);
+ const errors = undefinedIfEmpty({
+ answer: !captchaAnswer
+ ? i18n.str`Answer the question before continue`
+ : Number.isNaN(answer)
+ ? i18n.str`The answer should be a number`
+ : answer !== captchaNumbers.a + captchaNumbers.b
+ ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.`
+ : undefined,
+ }) ?? (busy ? {} as Record<string, undefined> : undefined);
+
return (
- <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+ <div class="bg-white shadow sm:rounded-lg">
+ <div class="px-4 py-5 sm:p-6">
+ <h3 class="text-base font-semibold text-gray-900">
+ <i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
+ </h3>
+ <div class="mt-2 max-w-xl text-sm text-gray-500">
+ <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-4 sm:gap-x-3">
+
+ <label class={"relative sm:col-span-2 flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 ring-2 ring-indigo-600"}>
+ <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" />
+ <span class="flex flex-1">
+ <span class="flex flex-col">
+ <span id="project-type-0-label" class="block text-sm font-medium text-gray-900 ">
+ <i18n.Translate>challenge response test</i18n.Translate>
+ </span>
+ </span>
+ </span>
+ <svg class="h-5 w-5 text-indigo-600" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+ </svg>
+ </label>
+
+
+ <label class="relative flex cursor-pointer rounded-lg border bg-gray-100 p-4 shadow-sm focus:outline-none 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" />
+ <span class="flex flex-1">
+ <span class="flex flex-col">
+ <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
+ <i18n.Translate>using SMS</i18n.Translate>
+ </span>
+ <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
+ <i18n.Translate>not available</i18n.Translate>
+ </span>
+ </span>
+ </span>
+ <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+ </svg>
+ </label>
+
+ <label class="relative flex cursor-pointer rounded-lg border bg-gray-100 p-4 shadow-sm focus:outline-none 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" />
+ <span class="flex flex-1">
+ <span class="flex flex-col">
+ <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
+ <i18n.Translate>one time password</i18n.Translate>
+ </span>
+ <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
+ <i18n.Translate>not available</i18n.Translate>
+ </span>
+ </span>
+ </span>
+ <svg class="h-5 w-5 text-indigo-600 hidden" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+ </svg>
+ </label>
+ </div>
+ </div>
+ <div class="mt-3 text-sm leading-6">
+
+ <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">
+ <label for="withdraw-amount">{i18n.str`What is`}&nbsp;
+ <em>
+ {captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
+ </em>
+ ?
+ </label>
+ <div class="mt-2">
+ <div class="relative rounded-md shadow-sm">
+ <input
+ type="text"
+ // class="block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ aria-describedby="answer"
+ autoFocus
+ class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ value={captchaAnswer ?? ""}
+ required
+
+ name="answer"
+ id="answer"
+ autocomplete="off"
+ onChange={(e): void => {
+ setCaptchaAnswer(e.currentTarget.value)
+ }}
+ />
+ </div>
+ <ShowInputErrorLabel message={errors?.answer} isDirty={captchaAnswer !== undefined} />
+ </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={onAbort}
+ >
+ <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={!!errors}
+ onClick={(e) => {
+ e.preventDefault()
+ onConfirm()
+ }}
+ >
+ <i18n.Translate>Transfer</i18n.Translate>
+ </button>
+ </div>
+
+ </form>
+ </div>
+ <div class="px-4 mt-4 ">
+ {/* <div class="w-full">
+ <div class="px-4 sm:px-0 text-sm">
+ <p><i18n.Translate>Wire transfer details</i18n.Translate></p>
+ </div>
+ <div class="mt-6 border-t border-gray-100">
+ <dl class="divide-y divide-gray-100">
+ {((): VNode => {
+ switch (details.account.targetType) {
+ case "iban": {
+ const p = details.account as PaytoUriIBAN
+ const name = p.params["receiver-name"]
+ return <Fragment>
+ <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+ <dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.iban}</dd>
+ </div>
+ {name &&
+ <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+ <dt class="text-sm font-medium leading-6 text-gray-900">Exchange name</dt>
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
+ </div>
+ }
+ </Fragment>
+ }
+ case "x-taler-bank": {
+ const p = details.account as PaytoUriTalerBank
+ const name = p.params["receiver-name"]
+ return <Fragment>
+ <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+ <dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.account}</dd>
+ </div>
+ {name &&
+ <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+ <dt class="text-sm font-medium leading-6 text-gray-900">Exchange name</dt>
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
+ </div>
+ }
+ </Fragment>
+ }
+ default:
+ return <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+ <dt class="text-sm font-medium leading-6 text-gray-900">Exchange account</dt>
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{details.account.targetPath}</dd>
+ </div>
+
+ }
+ })()}
+ <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+ <dt class="text-sm font-medium leading-6 text-gray-900">Withdrawal identification</dt>
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0 break-words">{details.reserve}</dd>
+ </div>
+ <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+ <dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">To be added</dd>
+ // {/* Amounts.stringifyValue(details.amount)
+ </div>
+ </dl>
+ </div>
+ </div> */}
+
+ </div>
+ </div>
+ </div>
+
);
}
-export function AbortedView({ error }: State.Aborted) {
+export function AbortedView({ error, onClose }: State.Aborted) {
return (
- <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+ <div>aborted</div>
);
}
-export function ConfirmedView({ error }: State.Confirmed) {
+export function ConfirmedView({ error, onClose }: State.Confirmed) {
+ const { i18n } = useTranslationContext();
+ const [settings, updateSettings] = useSettings()
return (
- <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+ <Fragment>
+
+ <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white p-4 text-left shadow-xl transition-all ">
+
+ <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
+ <svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
+ </svg>
+ </div>
+ <div class="mt-3 text-center sm:mt-5">
+ <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
+ <i18n.Translate>Withdrawal OK</i18n.Translate>
+ </h3>
+ <div class="mt-2">
+ <p class="text-sm text-gray-500">
+ <i18n.Translate>
+ The wire transfer to the Taler exchange bank's account is completed, now the
+ exchange will send the requested amount into your GNU Taler wallet.
+ </i18n.Translate>
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="mt-4">
+ <div class="flex items-center justify-between">
+ <span class="flex flex-grow flex-col">
+ <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+ <i18n.Translate>Do not show this again</i18n.Translate>
+ </span>
+ </span>
+ <button type="button" data-enabled={!settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
+ onClick={() => {
+ updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
+ }}>
+ <span aria-hidden="true" data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+ </button>
+ </div>
+ </div>
+ <div class="mt-5 sm:mt-6">
+ <button type="button"
+ class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+ onClick={async (e) => {
+ e.preventDefault();
+ onClose()
+ }}>
+ <i18n.Translate>Close</i18n.Translate>
+ </button>
+ </div>
+ </Fragment>
+
);
}
-export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
+export function ReadyView({ uri, onClose }: State.Ready): VNode<{}> {
const { i18n } = useTranslationContext();
- return <div />
+ 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} ${uri.withdrawalOperationId}`;
+ }, []);
+ const talerWithdrawUri = stringifyWithdrawUri(uri);
+ const [show, setShow] = useState(false)
+ return <Fragment>
+
+ <div class="bg-white shadow sm:rounded-lg mt-4">
+ <div class="p-4">
+ <h3 class="text-base font-semibold leading-6 text-gray-900">
+ <i18n.Translate>On this device</i18n.Translate>
+ </h3>
+ <div class="mt-2 sm:flex sm:items-start sm:justify-between">
+ <div class="max-w-xl text-sm text-gray-500">
+ <p>
+ <i18n.Translate>If you are using a desktop browser you can open the popup now or click the link if you have the "Inject Taler support" option enabled.</i18n.Translate>
+ </p>
+ </div>
+ <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 sm:items-center">
+ <a href={talerWithdrawUri}
+ 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>Start</i18n.Translate>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="bg-white shadow sm:rounded-lg mt-2">
+ <div class="p-4">
+ <h3 class="text-base font-semibold leading-6 text-gray-900">
+ <i18n.Translate>On a mobile phone</i18n.Translate>
+ </h3>
+ <div class="mt-2 sm:flex sm:items-start sm:justify-between">
+ <div class="max-w-xl text-sm text-gray-500">
+ <p>
+ <i18n.Translate>Scan the QR code with your mobile device.</i18n.Translate>
+ </p>
+ </div>
+ <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 sm:items-center">
+ <button type="button"
+ class="inline-flex items-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-500"
+ onClick={() => {
+ setShow(!show)
+ }}
+ >
+ {!show ?
+ <i18n.Translate>Show QR</i18n.Translate>
+ :
+ <i18n.Translate>Hide QR</i18n.Translate>
+ }
+ </button>
+ </div>
+ </div>
+ {show &&
+ <div class="mt-2 max-w-md ml-auto mr-auto">
+ <QR text={talerWithdrawUri} />
+ </div>
+ }
+ </div>
+ </div>
+
+ <div class="flex justify-end mt-4">
+ <button type="button"
+ class="inline-flex items-center 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-500"
+ onClick={() => {
+ onClose()
+ }}
+ >
+ Cancel
+ </button>
+ </div>
+ </Fragment>
}
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 573f8c769..2830f5c1e 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -26,12 +26,11 @@ import { useSettings } from "../hooks/settings.js";
* Let the user choose a payment option,
* then specify the details trigger the action.
*/
-export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
+export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJson, goToConfirmOperation: (id: string) => void }): VNode {
const { i18n } = useTranslationContext();
- const [settings, updateSettings] = useSettings();
+ const [settings] = useSettings();
const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>();
- // const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined);
return (
<div class="mt-2">
@@ -56,6 +55,14 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
<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>
+ {!!settings.currentWithdrawalOperationId &&
+ <span class="inline-flex items-center gap-x-1.5 rounded-full bg-green-100 px-1.5 py-0.5 text-xs font-medium text-green-700">
+ <svg class="h-1.5 w-1.5 fill-green-500" viewBox="0 0 6 6" aria-hidden="true">
+ <circle cx="3" cy="3" r="3" />
+ </svg>
+ <i18n.Translate>Operation in progress</i18n.Translate>
+ </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">
@@ -88,9 +95,7 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
<WalletWithdrawForm
focus
limit={limit}
- onSuccess={(id) => {
- updateSettings("currentWithdrawalOperationId", id);
- }}
+ goToConfirmOperation={goToConfirmOperation}
onCancel={() => {
setTab(undefined)
}}
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 416c714e2..0a5a386ae 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -137,107 +137,3 @@ export function QrCodeSection({
}
-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>
- );
-}
-
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 08f706919..8dbdd9da6 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -36,33 +36,52 @@ import { useAccessAPI } from "../hooks/access.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
import { Amount } from "./PaytoWireTransferForm.js";
import { useSettings } from "../hooks/settings.js";
-import { WithdrawalOperationState } from "./WithdrawalQRCode.js";
-import { Loading } from "../components/Loading.js";
+import { OperationState } from "./OperationState/index.js";
const logger = new Logger("WalletWithdrawForm");
const RefAmount = forwardRef(Amount);
-export function WalletWithdrawForm({
- focus,
- limit,
- onSuccess,
- onCancel,
-}: {
+
+function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
limit: AmountJson;
focus?: boolean;
- onSuccess: (operationId: string) => void;
+ goToConfirmOperation: (operationId: string) => void;
onCancel: () => void;
}): VNode {
const { i18n } = useTranslationContext();
- const { createWithdrawal } = useAccessAPI();
const [settings, updateSettings] = useSettings()
+ const { createWithdrawal } = useAccessAPI();
const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
if (focus) ref.current?.focus();
}, [focus]);
+ if (!!settings.currentWithdrawalOperationId) {
+ return <div class="rounded-md bg-yellow-50 ring-yellow-2 p-4">
+ <div class="flex">
+ <div class="flex-shrink-0">
+ <svg class="h-5 w-5 text-yellow-300" 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-yellow-800">
+ <i18n.Translate>There is an operation already</i18n.Translate>
+ </h3>
+ <div class="mt-2 text-sm text-yellow-700">
+ <p>
+ <i18n.Translate>
+ To complete or cancel the operation click <a class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
+ </i18n.Translate>
+ </p>
+ </div>
+ </div>
+ </div>
+ </div >
+ }
+
const trimmedAmountStr = amountStr?.trim();
const parsedAmount = trimmedAmountStr
@@ -92,7 +111,8 @@ export function WalletWithdrawForm({
i18n.str`Server responded with an invalid withdraw URI`,
i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`);
} else {
- onSuccess(uri.withdrawalOperationId);
+ updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
+ goToConfirmOperation(uri.withdrawalOperationId);
}
} catch (error) {
if (error instanceof RequestError) {
@@ -115,113 +135,168 @@ export function WalletWithdrawForm({
}
}
+ return <form
+ class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2 mt-4"
+ 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
+ </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>
+
+ </form>
+}
+
+
+export function WalletWithdrawForm({
+ focus,
+ limit,
+ onCancel,
+ goToConfirmOperation,
+}: {
+ limit: AmountJson;
+ focus?: boolean;
+ goToConfirmOperation: (operationId: string) => void;
+ onCancel: () => void;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const [settings, updateSettings] = useSettings()
+
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">
<div class="px-4 sm:px-0">
<h2 class="text-base font-semibold leading-7 text-gray-900"><i18n.Translate>Prepare your wallet</i18n.Translate></h2>
<p class="mt-1 text-sm text-gray-500">
- <i18n.Translate>Upon starting you will receive the money in your digital wallet, if you don't have one please <a target="_blank" rel="noreferrer noopener" class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net/en/wallet.html">install one from here</a></i18n.Translate>.
- </p>
- <p class="mt-1 text-sm text-gray-500">
- <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 confirm or cancel the operation.</i18n.Translate>
</p>
</div>
- {!settings.fastWithdrawal ?
- <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"
+ <div class="col-span-2">
+ {settings.showInstallWallet && <div class="rounded-md bg-blue-50 ring-blue-2 ring-2 p-4">
+ <div class="flex">
+ <div class="flex-shrink-0">
+ <svg class="h-5 w-5 text-blue-300" 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>You need a GNU Taler Wallet</i18n.Translate>
+ </h3>
+ <div class="mt-2 text-sm text-blue-700">
+ <p>
+ <i18n.Translate>
+ If you dont have one yet you can follow the instruction <a target="_blank" rel="noreferrer noopener" class="font-semibold text-blue-700 hover:text-blue-600" href="https://taler.net/en/wallet.html">here</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();
- setAmountStr("50.00")
+ updateSettings("showInstallWallet", false);
}}
>
- 50.00
+ I know
+ <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>
- <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")
- }}
- >
+ </p>
- 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>}
- </form>
- : settings.currentWithdrawalOperationId === undefined ?
- <Loading /> :
- <WithdrawalOperationState
- currentOperation={settings.currentWithdrawalOperationId}
+ {!settings.fastWithdrawal ?
+ <OldWithdrawalForm
+ focus={focus}
+ limit={limit}
+ onCancel={onCancel}
+ goToConfirmOperation={goToConfirmOperation}
+ />
+ :
+ <OperationState
currency={limit.currency}
- onClose={() => {
- onCancel()
- }}
+ onClose={onCancel}
+ goToConfirmOperation={goToConfirmOperation}
/>
- }
+ }
+ </div>
</div>
);
}
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 9976babdb..25c571e28 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -18,23 +18,17 @@ import {
Amounts,
HttpStatusCode,
Logger,
- TranslatedString,
WithdrawUriResult,
- parsePaytoUri,
- parseWithdrawUri,
- stringifyWithdrawUri,
+ parsePaytoUri
} from "@gnu-taler/taler-util";
-import { ErrorType, RequestError, notify, notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { Loading } from "../components/Loading.js";
-import { useAccessAPI, useWithdrawalDetails } from "../hooks/access.js";
+import { useWithdrawalDetails } from "../hooks/access.js";
import { useSettings } from "../hooks/settings.js";
import { handleNotOkResult } from "./HomePage.js";
-import { QrCodeSection, QrCodeSectionSimpler } from "./QrCodeSection.js";
+import { QrCodeSection } from "./QrCodeSection.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");
@@ -54,18 +48,11 @@ export function WithdrawalQRCode({
const [settings, updateSettings] = useSettings();
const { i18n } = useTranslationContext();
const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
+
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;
@@ -127,22 +114,6 @@ export function WithdrawalQRCode({
</div>
</div>
</div>
- <div class="mt-4">
- <div class="flex items-center justify-between">
- <span class="flex flex-grow flex-col">
- <span class="text-sm text-black font-medium leading-6 " id="availability-label">
- <i18n.Translate>Do not show this again</i18n.Translate>
- </span>
- </span>
- <button type="button" data-enabled={!settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
-
- onClick={() => {
- updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
- }}>
- <span aria-hidden="true" data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
- </button>
- </div>
- </div>
<div class="mt-5 sm:mt-6">
<button type="button"
class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -182,7 +153,6 @@ export function WithdrawalQRCode({
the exchange is selcted but no account
</div>
}
-
return (
<WithdrawalConfirmationQuestion
withdrawUri={withdrawUri}
@@ -198,126 +168,3 @@ export function WithdrawalQRCode({
/>
);
}
-
-
-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>;
-} \ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx
index 318a4cfda..f5f77a3ea 100644
--- a/packages/demobank-ui/src/pages/business/Home.tsx
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -360,7 +360,6 @@ function CreateCashout({
type="checkbox"
name="asd"
onChange={(e): void => {
- console.log("asdasd", form.isDebit);
form.isDebit = !form.isDebit;
updateForm(structuredClone(form));
}}