aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx')
-rw-r--r--packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx237
1 files changed, 237 insertions, 0 deletions
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
new file mode 100644
index 000000000..a8e34e4b9
--- /dev/null
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -0,0 +1,237 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+import {
+ TalerError,
+ TranslatedString
+} from "@gnu-taler/taler-util";
+import {
+ notify,
+ useTranslationContext
+} from "@gnu-taler/web-util/browser";
+import { format } from "date-fns";
+import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
+import { mutate } from "swr";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { Loading } from "../../components/Loading.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { useBankCoreApiContext } from "../../context/config.js";
+import { useBackendState } from "../../hooks/backend.js";
+import {
+ useCashoutDetails
+} from "../../hooks/circuit.js";
+import {
+ undefinedIfEmpty,
+ withRuntimeErrorHandling
+} from "../../utils.js";
+import { assertUnreachable } from "../HomePage.js";
+
+interface Props {
+ id: string;
+ onCancel: () => void;
+}
+export function ShowCashoutDetails({
+ id,
+ onCancel,
+}: Props): VNode {
+ const { i18n } = useTranslationContext();
+ const { state } = useBackendState();
+ const creds = state.status !== "loggedIn" ? undefined : state
+ const { api } = useBankCoreApiContext()
+ const result = useCashoutDetails(id);
+ const [code, setCode] = useState<string | undefined>(undefined);
+
+ if (!result) {
+ return <Loading />
+ }
+ if (result instanceof TalerError) {
+ return <ErrorLoading error={result} />
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case "already-aborted": return <div>this cashout is already aborted</div>
+ default: assertUnreachable(result.case)
+ }
+ }
+ const errors = undefinedIfEmpty({
+ code: !code ? i18n.str`required` : undefined,
+ });
+ const isPending = String(result.body.status).toUpperCase() === "PENDING";
+ return (
+ <div>
+ <h1>Cashout details {id}</h1>
+ <form class="pure-form">
+ <fieldset>
+ <label>
+ <i18n.Translate>Subject</i18n.Translate>
+ </label>
+ <input readOnly value={result.body.subject} />
+ </fieldset>
+ <fieldset>
+ <label>
+ <i18n.Translate>Created</i18n.Translate>
+ </label>
+ <input readOnly value={result.body.creation_time.t_s === "never" ? i18n.str`never` : format(result.body.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")} />
+ </fieldset>
+ <fieldset>
+ <label>
+ <i18n.Translate>Confirmed</i18n.Translate>
+ </label>
+ <input readOnly value={result.body.confirmation_time === undefined ? "-" :
+ (result.body.confirmation_time.t_s === "never" ?
+ i18n.str`never` :
+ format(result.body.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss"))
+ } />
+ </fieldset>
+ <fieldset>
+ <label>
+ <i18n.Translate>Debited</i18n.Translate>
+ </label>
+ <input readOnly value={result.body.amount_debit} />
+ </fieldset>
+ <fieldset>
+ <label>
+ <i18n.Translate>Credit</i18n.Translate>
+ </label>
+ <input readOnly value={result.body.amount_credit} />
+ </fieldset>
+ <fieldset>
+ <label>
+ <i18n.Translate>Status</i18n.Translate>
+ </label>
+ <input readOnly value={result.body.status} />
+ </fieldset>
+ <fieldset>
+ <label>
+ <i18n.Translate>Destination</i18n.Translate>
+ </label>
+ <input readOnly value={result.body.credit_payto_uri} />
+ </fieldset>
+ {isPending ? (
+ <fieldset>
+ <label>
+ <i18n.Translate>Code</i18n.Translate>
+ </label>
+ <input
+ value={code ?? ""}
+ onChange={(e) => {
+ setCode(e.currentTarget.value);
+ }}
+ />
+ <ShowInputErrorLabel
+ message={errors?.code}
+ isDirty={code !== undefined}
+ />
+ </fieldset>
+ ) : undefined}
+ </form>
+ <br />
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
+ <button
+ class="pure-button pure-button-secondary btn-cancel"
+ onClick={(e) => {
+ e.preventDefault();
+ onCancel();
+ }}
+ >
+ {i18n.str`Back`}
+ </button>
+ {isPending ? (
+ <div>
+ <button
+ type="submit"
+ class="pure-button pure-button-primary button-error"
+ onClick={async (e) => {
+ e.preventDefault();
+ if (!creds) return;
+ await withRuntimeErrorHandling(i18n, async () => {
+ const resp = await api.abortCashoutById(creds, id);
+ if (resp.type === "ok") {
+ onCancel();
+ } else {
+ switch (resp.case) {
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Cashout not found. It may be also mean that it was already aborted.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "already-confirmed": return notify({
+ type: "error",
+ title: i18n.str`Cashout was already confimed.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: {
+ assertUnreachable(resp)
+ }
+ }
+ }
+ })
+ }}
+ >
+ {i18n.str`Abort`}
+ </button>
+ &nbsp;
+ <button
+ type="submit"
+ disabled={!code}
+ class="pure-button pure-button-primary "
+ onClick={async (e) => {
+ e.preventDefault();
+ if (!creds || !code) return;
+ await withRuntimeErrorHandling(i18n, async () => {
+ const resp = await api.confirmCashoutById(creds, id, {
+ tan: code,
+ });
+ if (resp.type === "ok") {
+ mutate(() => true)//clean cashout state
+ } else {
+ switch (resp.case) {
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Cashout not found. It may be also mean that it was already aborted.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "wrong-tan-or-credential": return notify({
+ type: "error",
+ title: i18n.str`Invalid code or credentials.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "cashout-address-changed": return notify({
+ type: "error",
+ title: i18n.str`The cash-out address between the creation and the confirmation changed.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
+ }
+ })
+ }}
+ >
+ {i18n.str`Confirm`}
+ </button>
+ </div>
+ ) : (
+ <div />
+ )}
+ </div>
+ </div>
+ );
+}