summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-01-19 17:09:43 -0300
committerSebastian <sebasjm@gmail.com>2024-01-19 17:09:52 -0300
commitd845c0cf225a0f05214f368f4a98257238f53d07 (patch)
tree756d754f3c1c85460f01b4b5068aa02e4c0760a6
parenta04e822af063951d39f4ddc597cd163037cb5010 (diff)
downloadwallet-core-d845c0cf225a0f05214f368f4a98257238f53d07.tar.gz
wallet-core-d845c0cf225a0f05214f368f4a98257238f53d07.tar.bz2
wallet-core-d845c0cf225a0f05214f368f4a98257238f53d07.zip
fixes #8147
-rw-r--r--packages/demobank-ui/src/components/Cashouts/views.tsx37
-rw-r--r--packages/demobank-ui/src/pages/LoginForm.tsx6
-rw-r--r--packages/demobank-ui/src/pages/RegistrationPage.tsx4
-rw-r--r--packages/demobank-ui/src/pages/SolveChallengePage.tsx12
-rw-r--r--packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx4
-rw-r--r--packages/demobank-ui/src/pages/admin/AdminHome.tsx25
-rw-r--r--packages/demobank-ui/src/pages/business/CreateCashout.tsx10
-rw-r--r--packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx193
8 files changed, 74 insertions, 217 deletions
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx b/packages/demobank-ui/src/components/Cashouts/views.tsx
index c3fdec796..cb1c24aa7 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -32,13 +32,11 @@ export function FailedView({ error }: State.Failed) {
{error.detail.hint}
</div>
</Attention>
- case HttpStatusCode.NotFound: return <Attention type="danger"
- title={i18n.str`Account not found.`}>
- <div class="mt-2 text-sm text-red-700">
- {error.detail.hint}
- </div>
- </Attention>
- default: assertUnreachable(error)
+ case HttpStatusCode.NotImplemented: {
+ return <Attention type="danger" title={i18n.str`Cashout not implemented`}>
+ </Attention>;
+ }
+ default: assertUnreachable(error.case)
}
}
@@ -51,6 +49,16 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
if (resp instanceof TalerError) {
return <ErrorLoadingWithDebug error={resp} />
}
+ if (resp.type === "fail") {
+ switch (resp.case) {
+ case HttpStatusCode.NotImplemented: {
+ return <Attention type="danger" title={i18n.str`Cashout not implemented`}>
+ </Attention>;
+ }
+ default: assertUnreachable(resp.case)
+ }
+ }
+
if (!cashouts.length) return <div />
const txByDate = cashouts.reduce((prev, cur) => {
const d = cur.creation_time.t_s === "never"
@@ -74,10 +82,8 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
<thead>
<tr>
<th scope="col" class=" pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Created`}</th>
- <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Confirmed`}</th>
<th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Total debit`}</th>
<th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Total credit`}</th>
- <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Status`}</th>
<th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Subject`}</th>
</tr>
</thead>
@@ -91,9 +97,6 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
</tr>
{txs.map(item => {
const creationTime = item.creation_time.t_s === "never" ? "" : format(item.creation_time.t_s * 1000, "HH:mm:ss", { locale: dateLocale })
- const confirmationTime = item.confirmation_time
- ? item.confirmation_time.t_s === "never" ? i18n.str`never` : format(item.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss", { locale: dateLocale })
- : "-"
return (<tr key={idx} class="border-b border-gray-200 hover:bg-gray-200 last:border-none">
<td onClick={(e) => {
@@ -101,6 +104,8 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
onSelected(item.id);
}} class="relative py-2 pl-2 pr-2 text-sm ">
<div class="font-medium text-gray-900">{creationTime}</div>
+ {//FIXME: implement responsive view
+ }
{/* <dl class="font-normal sm:hidden">
<dt class="sr-only sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
<dd class="mt-1 truncate text-gray-700">
@@ -126,10 +131,6 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
<td onClick={(e) => {
e.preventDefault();
onSelected(item.id);
- }} class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 cursor-pointer">{confirmationTime}</td>
- <td onClick={(e) => {
- e.preventDefault();
- onSelected(item.id);
}} class="hidden sm:table-cell px-3 py-3.5 text-sm text-red-600 cursor-pointer"><RenderAmount value={Amounts.parseOrThrow(item.amount_debit)} spec={resp.body.regional_currency_specification} /></td>
<td onClick={(e) => {
e.preventDefault();
@@ -139,10 +140,6 @@ export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
<td onClick={(e) => {
e.preventDefault();
onSelected(item.id);
- }} class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 cursor-pointer">{item.status}</td>
- <td onClick={(e) => {
- e.preventDefault();
- onSelected(item.id);
}} class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 break-all min-w-md">
{item.subject}
</td>
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index 627252682..7ec490c19 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { TranslatedString } from "@gnu-taler/taler-util";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
import { LocalNotificationBanner, ShowInputErrorLabel, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
@@ -76,13 +76,13 @@ export function LoginForm({ currentUser, fixedUser, onRegister }: { fixedUser?:
backend.logIn({ username, token: resp.body.access_token });
} else {
switch (resp.case) {
- case "wrong-credentials": return notify({
+ case HttpStatusCode.Unauthorized: return notify({
type: "error",
title: i18n.str`Wrong credentials for "${username}"`,
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
- case "not-found": return notify({
+ case HttpStatusCode.NotFound: return notify({
type: "error",
title: i18n.str`Account not found`,
description: resp.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index 005a0bc2c..e948a5dad 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -177,13 +177,13 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
backend.logIn({ username, token: resp.body.access_token });
} else {
switch (resp.case) {
- case "wrong-credentials": return notify({
+ case HttpStatusCode.Unauthorized: return notify({
type: "error",
title: i18n.str`Wrong credentials for "${username}"`,
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
- case "not-found": return notify({
+ case HttpStatusCode.NotFound: return notify({
type: "error",
title: i18n.str`Account not found`,
description: resp.detail.hint as TranslatedString,
diff --git a/packages/demobank-ui/src/pages/SolveChallengePage.tsx b/packages/demobank-ui/src/pages/SolveChallengePage.tsx
index 3647f2b65..d0ac5ab75 100644
--- a/packages/demobank-ui/src/pages/SolveChallengePage.tsx
+++ b/packages/demobank-ui/src/pages/SolveChallengePage.tsx
@@ -27,6 +27,7 @@ import {
parsePaytoUri
} from "@gnu-taler/taler-util";
import {
+ Attention,
Loading,
LocalNotificationBanner,
ShowInputErrorLabel,
@@ -528,6 +529,17 @@ function ShowCashoutDetails({ request }: { request: TalerCorebankApi.CashoutRequ
if (info instanceof TalerError) {
return <ErrorLoadingWithDebug error={info} />
}
+ if (info.type === "fail") {
+ switch (info.case) {
+ case HttpStatusCode.NotImplemented: {
+ return <Attention type="danger" title={i18n.str`Cashout not implemented`}>
+ </Attention>;
+ }
+ default: assertUnreachable(info.case)
+ }
+ }
+
+
return <Fragment>
{request.subject !== undefined &&
<div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index ca3e2fbdf..0dfdb39f3 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -93,9 +93,9 @@ export function ShowAccountDetails({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
- case TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT: return notify({
+ case TalerErrorCode.BANK_MISSING_TAN_INFO: return notify({
type: "error",
- title: i18n.str`You can't change the contact data, please contact the your account administrator.`,
+ title: i18n.str`No information for the selected authentication channel.`,
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
index c03f228cd..b7ef3aa00 100644
--- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx
+++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
@@ -1,5 +1,5 @@
-import { AmountString, Amounts, CurrencySpecification, TalerCorebankApi, TalerError, assertUnreachable } from "@gnu-taler/taler-util";
-import { useLang, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { AmountString, Amounts, CurrencySpecification, HttpStatusCode, TalerCorebankApi, TalerError, assertUnreachable } from "@gnu-taler/taler-util";
+import { Attention, useLang, useTranslationContext } from "@gnu-taler/web-util/browser";
import { format, getDate, getHours, getMonth, getYear, setDate, setHours, setMonth, setYear, sub } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
@@ -90,10 +90,23 @@ function Metrics(): VNode {
if (resp instanceof TalerError) {
return <ErrorLoadingWithDebug error={resp} />
}
+ if (!respInfo) return <Fragment />;
+ if (respInfo instanceof TalerError) {
+ return <ErrorLoadingWithDebug error={respInfo} />
+ }
+ if (respInfo.type === "fail") {
+ switch (respInfo.case) {
+ case HttpStatusCode.NotImplemented: {
+ return <Attention type="danger" title={i18n.str`Cashout not implemented`}>
+ </Attention>;
+ }
+ default: assertUnreachable(respInfo.case)
+ }
+ }
+
if (resp.current.type !== "ok" || resp.previous.type !== "ok") {
return <Fragment />
}
- const fiatSpec = respInfo && (!(respInfo instanceof TalerError)) ? respInfo.body.fiat_currency_specification : undefined
return <Fragment>
<div class="sm:hidden">
<label for="tabs" class="sr-only"><i18n.Translate>Select a section</i18n.Translate></label>
@@ -135,7 +148,7 @@ function Metrics(): VNode {
</div>
<dl class="mt-5 grid grid-cols-1 md:grid-cols-2 divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow-lg md:divide-x md:divide-y-0">
- {!fiatSpec || resp.current.body.type !== "with-conversions" || resp.previous.body.type !== "with-conversions" ? undefined :
+ {resp.current.body.type !== "with-conversions" || resp.previous.body.type !== "with-conversions" ? undefined :
<Fragment>
<div class="px-4 py-5 sm:p-6">
<dt class="text-base font-normal text-gray-900">
@@ -144,7 +157,7 @@ function Metrics(): VNode {
<MetricValue
current={resp.current.body.cashinFiatVolume}
previous={resp.previous.body.cashinFiatVolume}
- spec={fiatSpec}
+ spec={respInfo.body.fiat_currency_specification}
/>
</div>
<div class="px-4 py-5 sm:p-6">
@@ -154,7 +167,7 @@ function Metrics(): VNode {
<MetricValue
current={resp.current.body.cashoutFiatVolume}
previous={resp.previous.body.cashoutFiatVolume}
- spec={fiatSpec}
+ spec={respInfo.body.fiat_currency_specification}
/>
</div>
</Fragment>
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index f9962fbde..a39a379de 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -120,6 +120,16 @@ export function CreateCashout({
if (info instanceof TalerError) {
return <ErrorLoadingWithDebug error={info} />
}
+ if (info.type === "fail") {
+ switch (info.case) {
+ case HttpStatusCode.NotImplemented: {
+ return <Attention type="danger" title={i18n.str`Cashout not implemented`}>
+ </Attention>;
+ }
+ default: assertUnreachable(info.case)
+ }
+ }
+
const conversionInfo = info.body.conversion_rate
if (!conversionInfo) {
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index a55bf3ab6..b4d5b6584 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -54,18 +54,13 @@ export function ShowCashoutDetails({
}: Props): VNode {
const { i18n, dateLocale } = useTranslationContext();
const { state } = useBackendState();
- const creds = state.status !== "loggedIn" ? undefined : state
- const { api } = useBankCoreApiContext()
const cid = Number.parseInt(id, 10)
const result = useCashoutDetails(Number.isNaN(cid) ? undefined : cid);
- const [code, setCode] = useState<string | undefined>(undefined);
- const [notification, notify, handleError] = useLocalNotification()
const info = useConversionInfo();
if (Number.isNaN(cid)) {
- //TODO: better error message
- return <div>cashout id should be a number</div>
+ return <Attention type="danger" title={i18n.str`cashout id should be a number`} />
}
if (!result) {
return <Loading />
@@ -89,116 +84,19 @@ export function ShowCashoutDetails({
if (info instanceof TalerError) {
return <ErrorLoadingWithDebug error={info} />
}
-
- const errors = undefinedIfEmpty({
- code: !code ? i18n.str`required` : undefined,
- });
- /**
- * @deprecated
- */
- const isPending = String(result.body.status).toUpperCase() === "PENDING";
- const { fiat_currency_specification, regional_currency_specification } = info.body
- // won't implement in retry in old API 3:0:3 since request_uid is missing
- async function doAbortCashout() {
- if (!creds) return;
- await handleError(async () => {
- const resp = await api.abortCashoutById(creds, cid);
- if (resp.type === "ok") {
- onCancel();
- } else {
- switch (resp.case) {
- case HttpStatusCode.NotFound: 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 HttpStatusCode.Conflict: return notify({
- type: "error",
- title: i18n.str`Cashout was already confimed.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case HttpStatusCode.NotImplemented: return notify({
- type: "error",
- title: i18n.str`Cashout operation is not supported.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- default: {
- assertUnreachable(resp)
- }
- }
+ if (info.type === "fail") {
+ switch (info.case) {
+ case HttpStatusCode.NotImplemented: {
+ return <Attention type="danger" title={i18n.str`Cashout not implemented`} />
}
- })
- }
- async function doConfirmCashout() {
- if (!creds || !code) return;
- await handleError(async () => {
- const resp = await api.confirmCashoutById(creds, cid, {
- tan: code,
- });
- if (resp.type === "ok") {
- mutate(() => true)//clean cashout state
- } else {
- switch (resp.case) {
- case HttpStatusCode.NotFound: 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 TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
- type: "error",
- title: i18n.str`The account does not have sufficient funds`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case TalerErrorCode.BANK_BAD_CONVERSION: return notify({
- type: "error",
- title: i18n.str`The conversion rate was incorrectly applied`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return notify({
- type: "error",
- title: i18n.str`The cashout operation is already aborted.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({
- type: "error",
- title: i18n.str`Missing destination account.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case HttpStatusCode.TooManyRequests: return notify({
- type: "error",
- title: i18n.str`Too many failed attempts.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case HttpStatusCode.NotImplemented: return notify({
- type: "error",
- title: i18n.str`Cashout operation is not supported.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return notify({
- type: "error",
- title: i18n.str`The code for this cashout is invalid.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- default: assertUnreachable(resp)
- }
- }
- })
+ default: assertUnreachable(info.case)
+ }
}
+ const { fiat_currency_specification, regional_currency_specification } = info.body
+
return (
<div>
- <LocalNotificationBanner notification={notification} />
<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">
<section class="rounded-sm px-4">
@@ -208,16 +106,6 @@ export function ShowCashoutDetails({
<dt class="text-sm text-gray-600"><i18n.Translate>Subject</i18n.Translate></dt>
<dd class="text-sm ">{result.body.subject}</dd>
</div>
-
-
- <div class="flex items-center justify-between border-t-2 afu pt-4">
- <dt class="flex items-center text-sm text-gray-600">
- <span><i18n.Translate>Status</i18n.Translate></span>
- </dt>
- <dd data-status={result.body.status} class="text-sm uppercase data-[status=pending]:text-yellow-600 data-[status=aborted]:text-red-600 data-[status=confirmed]:text-green-600" >
- {result.body.status}
- </dd>
- </div>
</dl>
</section>
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2">
@@ -252,14 +140,6 @@ export function ShowCashoutDetails({
</dd>
</div>
- {result.body.confirmation_time && result.body.confirmation_time.t_s !== "never" ?
- <div class="flex justify-between items-center border-t-2 afu pt-4">
- <dt class=" font-medium"><i18n.Translate>Confirmed</i18n.Translate></dt>
- <dd class=" font-medium">
- {format(result.body.confirmation_time.t_s * 1000, "dd/MM/yyyy HH:mm:ss", { locale: dateLocale })}
- </dd>
- </div>
- : undefined}
</dl>
</div>
</div>
@@ -267,61 +147,6 @@ export function ShowCashoutDetails({
</div>
- {!isPending ? undefined :
- <Fragment>
- <div />
- <form
- class="bg-white shadow-sm ring-1 ring-gray-900/5"
- autoCapitalize="none"
- autoCorrect="off"
- onSubmit={e => {
- e.preventDefault()
- }}
- >
- <div class="px-4 py-6 sm:p-8">
- <label for="withdraw-amount">
- <i18n.Translate>Enter the confirmation code</i18n.Translate>
- </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={code ?? ""}
- required
-
- name="answer"
- id="answer"
- autocomplete="off"
- onChange={(e): void => {
- setCode(e.currentTarget.value)
- }}
- />
- </div>
- <ShowInputErrorLabel message={errors?.code} isDirty={code !== 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="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={doAbortCashout}
- >
- <i18n.Translate>Abort</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) => {
- doConfirmCashout()
- }}
- >
- <i18n.Translate>Confirm</i18n.Translate>
- </button>
- </div>
- </form>
- </Fragment>}
</div>
<br />