diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx | 237 |
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> + + <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> + ); +} |