diff options
10 files changed, 173 insertions, 151 deletions
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx index ad3cb0e32..080b9508e 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx @@ -52,14 +52,18 @@ export function InputDuration<T>({ const { error, required, value: anyValue, onChange } = useField<T>(name); let strValue = ""; - const value: Duration = anyValue + const value: Duration = anyValue; if (!value) { strValue = ""; } else if (value.d_ms === "forever") { strValue = i18n.str`forever`; } else { if (value.d_ms === undefined) { - throw Error(`assertion error: duration should have a d_ms but got '${JSON.stringify(value)}'`) + throw Error( + `assertion error: duration should have a d_ms but got '${JSON.stringify( + value, + )}'`, + ); } strValue = formatDuration( intervalToDuration({ start: 0, end: value.d_ms }), @@ -96,7 +100,7 @@ export function InputDuration<T>({ return ( <div class="field is-horizontal"> - <div class="field-label is-normal is-flex-grow-3"> + <div class="field-label is-normal"> <label class="label"> {label} {tooltip && ( @@ -107,69 +111,65 @@ export function InputDuration<T>({ </label> </div> - <div class="is-flex-grow-3"> - <div class="field-body "> - <div class="field"> - <div class="field has-addons"> - <p class={expand ? "control is-expanded " : "control "}> - <input - class="input" - type="text" - readonly - value={strValue} - placeholder={placeholder} - onClick={() => { - if (!readonly) setOpened(true); - }} - /> - {required && ( - <span class="icon has-text-danger is-right"> - <i class="mdi mdi-alert" /> - </span> - )} - </p> - <div - class="control" + <div class="field-body is-flex-grow-3"> + <div class="field"> + <div class="field has-addons"> + <p class={expand ? "control is-expanded " : "control "}> + <input + class="input" + type="text" + readonly + value={strValue} + placeholder={placeholder} onClick={() => { if (!readonly) setOpened(true); }} - > - <a class="button is-static"> - <span class="icon"> - <i class="mdi mdi-clock" /> - </span> - </a> - </div> + /> + {required && ( + <span class="icon has-text-danger is-right"> + <i class="mdi mdi-alert" /> + </span> + )} + </p> + <div + class="control" + onClick={() => { + if (!readonly) setOpened(true); + }} + > + <a class="button is-static"> + <span class="icon"> + <i class="mdi mdi-clock" /> + </span> + </a> </div> - {error && <p class="help is-danger">{error}</p>} </div> - {withForever && ( - <span data-tooltip={i18n.str`change value to never`}> - <button - class="button is-info mr-3" - onClick={() => onChange({ d_ms: "forever" } as any)} - > - <i18n.Translate>forever</i18n.Translate> - </button> - </span> - )} - {!readonly && !withoutClear && ( - <span data-tooltip={i18n.str`change value to empty`}> - <button - class="button is-info " - onClick={() => onChange(undefined as any)} - > - <i18n.Translate>clear</i18n.Translate> - </button> - </span> - )} - {side} + {error && <p class="help is-danger">{error}</p>} + <span class="has-text-grey">{help}</span> </div> - <span> - {help} - </span> - </div> + {withForever && ( + <span data-tooltip={i18n.str`change value to never`}> + <button + class="button is-info mr-3" + onClick={() => onChange({ d_ms: "forever" } as any)} + > + <i18n.Translate>forever</i18n.Translate> + </button> + </span> + )} + {!readonly && !withoutClear && ( + <span data-tooltip={i18n.str`change value to empty`}> + <button + class="button is-info " + onClick={() => onChange(undefined as any)} + > + <i18n.Translate>clear</i18n.Translate> + </button> + </span> + )} + {side} + </div> {opened && ( <SimpleModal onCancel={() => setOpened(false)}> diff --git a/packages/merchant-backoffice-ui/src/components/form/InputWithAddon.tsx b/packages/merchant-backoffice-ui/src/components/form/InputWithAddon.tsx index b8cd4c2d2..04bcbc2be 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputWithAddon.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputWithAddon.tsx @@ -69,6 +69,7 @@ export function InputWithAddon<T>({ )} </label> </div> + <div class="field-body is-flex-grow-3"> <div class="field"> <div class="field has-addons"> diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx index 123271f8d..baab9584c 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx @@ -28,6 +28,12 @@ function getInstanceTitle(path: string, id: string): string { switch (path) { case InstancePaths.settings: return `${id}: Settings`; + case InstancePaths.bank_new: + return `${id}: Account`; + case InstancePaths.bank_list: + return `${id}: Account`; + case InstancePaths.bank_update: + return `${id}: Account`; case InstancePaths.order_list: return `${id}: Orders`; case InstancePaths.order_new: diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx index d0e7a83cd..1d08c5058 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx @@ -35,7 +35,7 @@ import { ImportingAccountModal } from "../../../../components/modal/index.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; import { safeConvertURL } from "../update/UpdatePage.js"; -type Entity = TalerMerchantApi.AccountAddDetails & { repeatPassword: string }; +type Entity = TalerMerchantApi.AccountAddDetails; interface Props { onCreate: (d: TalerMerchantApi.AccountAddDetails) => Promise<void>; @@ -78,13 +78,6 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { : facadeURL.hash ? i18n.str`URL should not hash param` : undefined, - repeatPassword: !state.credit_facade_credentials - ? undefined - : state.credit_facade_credentials.type === "basic" && - (!state.credit_facade_credentials.password || - state.credit_facade_credentials.password !== state.repeatPassword) - ? i18n.str`is not the same` - : undefined, }; const hasErrors = Object.keys(errors).some( @@ -119,23 +112,35 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { }; return ( <div> - {importing && <ImportingAccountModal onCancel={()=> {setImporting(false)}} onConfirm={(ac) => { - state.payto_uri = ac.accountURI - const u = new URL(ac.infoURL) - u.password = "" - if (u.username || u.password) { - state.credit_facade_credentials = { - type: "basic", - password: u.password, - username: u.username, - } - state.repeatPassword = u.password - } - u.password = "" - u.username = "" - state.credit_facade_url = u.href; - setImporting(false) - }} />} + {importing && ( + <ImportingAccountModal + onCancel={() => { + setImporting(false); + }} + onConfirm={(ac) => { + state.payto_uri = ac.accountURI; + const u = new URL(ac.infoURL); + // if (u.username && ac.accesToken) { + // state.credit_facade_credentials = { + // type: "bearer", + // token: ac.accesToken, + // username: u.username, + // }; + // } else + if (u.username || u.password) { + state.credit_facade_credentials = { + type: "basic", + password: u.password, + username: u.username, + }; + } + u.password = ""; + u.username = ""; + state.credit_facade_url = u.href; + setImporting(false); + }} + /> + )} <section class="section is-main-section"> <div class="columns"> <div class="column" /> @@ -152,7 +157,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { <Input<Entity> name="credit_facade_url" label={i18n.str`Account info URL`} - help="https://bank.com" + help="https://bank.demo.taler.net/accounts/_username_/taler-revenue/" expand tooltip={i18n.str`From where the merchant can download information about incoming wire transfers to this account`} /> @@ -179,11 +184,6 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { label={i18n.str`Password`} tooltip={i18n.str`Password to access the account information.`} /> - <Input - name="repeatPassword" - inputType="password" - label={i18n.str`Repeat password`} - /> </Fragment> ) : undefined} </FormProvider> @@ -193,7 +193,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { class="button is-info" data-tooltip={i18n.str`Need to complete marked fields`} onClick={() => { - setImporting(true) + setImporting(true); }} > <i18n.Translate>Import from bank</i18n.Translate> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx index 1a8e9bdc1..c4ba1f0f2 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx @@ -83,15 +83,6 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { ? i18n.str`required` : undefined, - repeatPassword: - state.credit_facade_credentials?.type !== "basic" - ? undefined - : !(state.credit_facade_credentials as any).repeatPassword - ? i18n.str`required` - : (state.credit_facade_credentials as any).repeatPassword !== - state.credit_facade_credentials.password - ? i18n.str`doesn't match` - : undefined, }), }; @@ -158,7 +149,7 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { <Input<Entity> name="credit_facade_url" label={i18n.str`Account info URL`} - help="https://bank.com" + help="https://bank.demo.taler.net/accounts/_username_/taler-revenue/" expand tooltip={i18n.str`From where the merchant can download information about incoming wire transfers to this account`} /> @@ -186,11 +177,6 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { label={i18n.str`Password`} tooltip={i18n.str`Password to access the account information.`} /> - <Input - name="credit_facade_credentials.repeatPassword" - inputType="password" - label={i18n.str`Repeat password`} - /> </Fragment> ) : undefined} </FormProvider> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx index fce14dcc3..4fe11bf5c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx @@ -54,11 +54,10 @@ export default function ListTemplates({ }: Props): VNode { const { i18n } = useTranslationContext(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const { lib } = useSessionContext(); + const { state, lib } = useSessionContext(); const result = useInstanceTemplates(); const [deleting, setDeleting] = useState<TalerMerchantApi.TemplateEntry | null>(null); - const { state } = useSessionContext(); if (!result) return <Loading /> if (result instanceof TalerError) { diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx index 22ad0b8d8..6738b1c6c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx @@ -19,12 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { TalerMerchantApi } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { h, VNode } from "preact"; import { FormProvider } from "../../../../components/form/FormProvider.js"; import { InputSelector } from "../../../../components/form/InputSelector.js"; import { CardTable } from "./Table.js"; -import { TalerMerchantApi } from "@gnu-taler/taler-util"; export interface Props { transfers: TalerMerchantApi.TransferDetails[]; @@ -40,7 +40,7 @@ export interface Props { onChangePayTo: (p?: string) => void; payTo?: string; onCreate: () => void; - onDelete: () => void; + onDelete: (wid: TalerMerchantApi.TransferDetails) => void; } export function ListPage({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx index b9235c669..a6c9a00f9 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx @@ -129,12 +129,6 @@ function Table({ <i18n.Translate>Credit</i18n.Translate> </th> <th> - <i18n.Translate>Address</i18n.Translate> - </th> - <th> - <i18n.Translate>Exchange URL</i18n.Translate> - </th> - <th> <i18n.Translate>Confirmed</i18n.Translate> </th> <th> @@ -150,10 +144,8 @@ function Table({ {instances.map((i) => { return ( <tr key={i.id}> - <td>{i.id}</td> + <td title={i.wtid}>{i.wtid.substring(0,16)}...</td> <td>{i.credit_amount}</td> - <td>{i.payto_uri}</td> - <td>{i.exchange_url}</td> <td>{i.confirmed ? i18n.str`yes` : i18n.str`no`}</td> <td>{i.verified ? i18n.str`yes` : i18n.str`no`}</td> <td> @@ -167,13 +159,13 @@ function Table({ : i18n.str`unknown`} </td> <td> - {i.verified === undefined ? ( + {i.verified !== true ? ( <button class="button is-danger is-small has-tooltip-left" data-tooltip={i18n.str`delete selected transfer from the database`} onClick={() => onDelete(i)} > - Delete + <i18n.Translate>Delete</i18n.Translate> </button> ) : undefined} </td> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx index 8b4d1f3cb..267d41711 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/index.tsx @@ -19,8 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { HttpStatusCode, TalerError, assertUnreachable } from "@gnu-taler/taler-util"; -import { VNode, h } from "preact"; +import { + HttpStatusCode, + TalerError, + assertUnreachable, +} from "@gnu-taler/taler-util"; +import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js"; import { Loading } from "../../../../components/exception/loading.js"; @@ -29,6 +33,10 @@ import { useInstanceTransfers } from "../../../../hooks/transfer.js"; import { LoginPage } from "../../../login/index.js"; import { ListPage } from "./ListPage.js"; import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js"; +import { useSessionContext } from "../../../../context/session.js"; +import { NotificationCard } from "../../../../components/menu/index.js"; +import { Notification } from "../../../../utils/types.js"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; interface Props { onCreate: () => void; @@ -38,25 +46,28 @@ interface Form { payto_uri?: string; } -export default function ListTransfer({ - onCreate, -}: Props): VNode { +export default function ListTransfer({ onCreate }: Props): VNode { const setFilter = (s?: boolean) => setForm({ ...form, verified: s }); + const { i18n } = useTranslationContext(); + + const { state, lib } = useSessionContext(); + const [notif, setNotif] = useState<Notification | undefined>(undefined); const [position, setPosition] = useState<string | undefined>(undefined); const instance = useInstanceBankAccounts(); - const accounts = !instance || (instance instanceof TalerError) || instance.type === "fail" - ? [] - : instance.body.accounts.map((a) => a.payto_uri); + const accounts = + !instance || instance instanceof TalerError || instance.type === "fail" + ? [] + : instance.body.accounts.map((a) => a.payto_uri); const [form, setForm] = useState<Form>({ payto_uri: "" }); - const shoulUseDefaultAccount = accounts.length === 1 + const shoulUseDefaultAccount = accounts.length === 1; useEffect(() => { if (shoulUseDefaultAccount) { - setForm({...form, payto_uri: accounts[0]}) + setForm({ ...form, payto_uri: accounts[0] }); } - }, [shoulUseDefaultAccount]) + }, [shoulUseDefaultAccount]); const isVerifiedTransfers = form.verified === true; const isNonVerifiedTransfers = form.verified === false; @@ -78,7 +89,7 @@ export default function ListTransfer({ if (result.type === "fail") { switch (result.case) { case HttpStatusCode.Unauthorized: { - return <LoginPage /> + return <LoginPage />; } case HttpStatusCode.NotFound: { return <NotFoundPageOrAdminCreate />; @@ -90,23 +101,47 @@ export default function ListTransfer({ } return ( - <ListPage - accounts={accounts} - transfers={result.body} - onLoadMoreBefore={result.isFirstPage ? undefined: result.loadFirst } - onLoadMoreAfter={result.isLastPage ? undefined : result.loadNext} - onCreate={onCreate} - onDelete={() => { - null; - }} - onShowAll={() => setFilter(undefined)} - onShowUnverified={() => setFilter(false)} - onShowVerified={() => setFilter(true)} - isAllTransfers={isAllTransfers} - isVerifiedTransfers={isVerifiedTransfers} - isNonVerifiedTransfers={isNonVerifiedTransfers} - payTo={form.payto_uri} - onChangePayTo={(p) => setForm((v) => ({ ...v, payto_uri: p }))} - /> + <Fragment> + <NotificationCard notification={notif} /> + + <ListPage + accounts={accounts} + transfers={result.body} + onLoadMoreBefore={result.isFirstPage ? undefined : result.loadFirst} + onLoadMoreAfter={result.isLastPage ? undefined : result.loadNext} + onCreate={onCreate} + onDelete={async (transfer) => { + try { + const resp = await lib.instance.deleteWireTransfer(state.token, transfer.wtid); + if (resp.type === "ok") { + setNotif({ + message: i18n.str`Wire transfer "${transfer.wtid.substring(0,16)}..." has been deleted`, + type: "SUCCESS", + }); + } else { + setNotif({ + message: i18n.str`Failed to delete transfer`, + type: "ERROR", + description: resp.detail.hint, + }); + } + } catch (error) { + setNotif({ + message: i18n.str`Failed to delete transfer`, + type: "ERROR", + description: error instanceof Error ? error.message : undefined, + }); + } + }} + onShowAll={() => setFilter(undefined)} + onShowUnverified={() => setFilter(false)} + onShowVerified={() => setFilter(true)} + isAllTransfers={isAllTransfers} + isVerifiedTransfers={isVerifiedTransfers} + isNonVerifiedTransfers={isNonVerifiedTransfers} + payTo={form.payto_uri} + onChangePayTo={(p) => setForm((v) => ({ ...v, payto_uri: p }))} + /> + </Fragment> ); } diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts index 39c25cffd..ac21fc398 100644 --- a/packages/taler-util/src/payto.ts +++ b/packages/taler-util/src/payto.ts @@ -16,6 +16,7 @@ import { generateFakeSegwitAddress } from "./bitcoin.js"; import { Codec, Context, DecodingError, buildCodecForObject, codecForStringURL, renderContext } from "./codec.js"; +import { AccessToken, codecForAccessToken, codecOptional } from "./index.js"; import { URLSearchParams } from "./url.js"; export type PaytoUri = @@ -301,6 +302,7 @@ export function talerPaytoFromExchangeReserve( export type AccountLetter = { accountURI: PaytoString; infoURL: string; + accountToken?: AccessToken; }; export const codecForAccountLetter = @@ -308,4 +310,5 @@ export const codecForAccountLetter = buildCodecForObject<AccountLetter>() .property("infoURL", codecForStringURL(true)) .property("accountURI", codecForPaytoString()) + .property("accountToken", codecOptional(codecForAccessToken())) .build("AccountLetter"); |