diff options
Diffstat (limited to 'packages/merchant-backoffice-ui')
6 files changed, 308 insertions, 269 deletions
diff --git a/packages/merchant-backoffice-ui/src/hooks/bank.ts b/packages/merchant-backoffice-ui/src/hooks/bank.ts index 8857ad839..4c917fe9e 100644 --- a/packages/merchant-backoffice-ui/src/hooks/bank.ts +++ b/packages/merchant-backoffice-ui/src/hooks/bank.ts @@ -13,18 +13,19 @@ 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 { - useMerchantApiContext -} from "@gnu-taler/web-util/browser"; +import { useMerchantApiContext } from "@gnu-taler/web-util/browser"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 -import { AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } from "@gnu-taler/taler-util"; +import { + AccessToken, + TalerHttpError, + TalerMerchantManagementResultByMethod, +} from "@gnu-taler/taler-util"; import _useSWR, { SWRHook, mutate } from "swr"; import { useSessionContext } from "../context/session.js"; const useSWR = _useSWR as unknown as SWRHook; -export interface InstanceBankAccountFilter { -} +export interface InstanceBankAccountFilter {} export function revalidateInstanceBankAccounts() { return mutate( @@ -35,7 +36,9 @@ export function revalidateInstanceBankAccounts() { } export function useInstanceBankAccounts() { const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { + lib: { instance }, + } = useSessionContext(); // const [offset, setOffset] = useState<string | undefined>(); @@ -57,19 +60,24 @@ export function useInstanceBankAccounts() { if (data.type !== "ok") return data; // return buildPaginatedResult(data.body.accounts, offset, setOffset, (d) => d.h_wire) + const filtered = data.body.accounts.filter((a) => a.active); + data.body.accounts = filtered; return data; } export function revalidateBankAccountDetails() { return mutate( - (key) => Array.isArray(key) && key[key.length - 1] === "getBankAccountDetails", + (key) => + Array.isArray(key) && key[key.length - 1] === "getBankAccountDetails", undefined, { revalidate: true }, ); } export function useBankAccountDetails(h_wire: string) { const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { + lib: { instance }, + } = useSessionContext(); async function fetcher([token, wireId]: [AccessToken, string]) { return await instance.getBankAccountDetails(token, wireId); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx index 2c1a13e27..6994c579c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx @@ -63,7 +63,15 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode { onCreate={async (request: Entity) => { return api.instance .addBankAccount(state.token, request) - .then(() => { + .then((created) => { + if (created.type === "fail") { + setNotif({ + message: i18n.str`could not create account`, + type: "ERROR", + description: created.detail.hint, + }); + return; + } onConfirm(); }) .catch((error) => { @@ -120,7 +128,7 @@ export async function testRevenueAPI( if (resp.type === "fail") { return resp; } - + return opFixedSuccess(resp.body.credit_account as PaytoString); } catch (err) { if (err instanceof TalerError) { @@ -131,6 +139,6 @@ export async function testRevenueAPI( // detail: err.errorDetail, // }; } - throw err + throw err; } } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx index 4ee68cd80..7e0b89f39 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx @@ -24,12 +24,12 @@ import { h, VNode } from "preact"; import { CardTable } from "./Table.js"; export interface Props { - devices: TalerMerchantApi.BankAccountSummaryEntry[]; + devices: TalerMerchantApi.BankAccountEntry[]; // onLoadMoreBefore?: () => void; // onLoadMoreAfter?: () => void; onCreate: () => void; - onDelete: (e: TalerMerchantApi.BankAccountSummaryEntry) => void; - onSelect: (e: TalerMerchantApi.BankAccountSummaryEntry) => void; + onDelete: (e: TalerMerchantApi.BankAccountEntry) => void; + onSelect: (e: TalerMerchantApi.BankAccountEntry) => void; } export function ListPage({ @@ -40,7 +40,6 @@ export function ListPage({ // onLoadMoreBefore, // onLoadMoreAfter, }: Props): VNode { - return ( <section class="section is-main-section"> <CardTable diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx index a9cb2805b..0e813f4d2 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx @@ -19,12 +19,21 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { parsePaytoUri, PaytoType, PaytoUri, PaytoUriBitcoin, PaytoUriIBAN, PaytoUriTalerBank, PaytoUriUnknown, TalerMerchantApi } from "@gnu-taler/taler-util"; +import { + parsePaytoUri, + PaytoType, + PaytoUri, + PaytoUriBitcoin, + PaytoUriIBAN, + PaytoUriTalerBank, + PaytoUriUnknown, + TalerMerchantApi, +} from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { StateUpdater, useState } from "preact/hooks"; -type Entity = TalerMerchantApi.BankAccountSummaryEntry; +type Entity = TalerMerchantApi.BankAccountEntry; interface Props { accounts: Entity[]; @@ -93,241 +102,255 @@ interface TableProps { rowSelectionHandler: StateUpdater<string[]>; } -function Table({ - accounts, - onDelete, - onSelect, -}: TableProps): VNode { +function Table({ accounts, onDelete, onSelect }: TableProps): VNode { const { i18n } = useTranslationContext(); - const emptyList: Record<PaytoType | "unknown", { parsed: PaytoUri, acc: Entity }[]> = { "bitcoin": [], "x-taler-bank": [], "iban": [], "unknown": [], } + const emptyList: Record< + PaytoType | "unknown", + { parsed: PaytoUri; acc: Entity }[] + > = { bitcoin: [], "x-taler-bank": [], iban: [], unknown: [] }; const accountsByType = accounts.reduce((prev, acc) => { - const parsed = parsePaytoUri(acc.payto_uri) - if (!parsed) return prev //skip - if (parsed.targetType !== "bitcoin" && parsed.targetType !== "x-taler-bank" && parsed.targetType !== "iban") { - prev["unknown"].push({ parsed, acc }) + const parsed = parsePaytoUri(acc.payto_uri); + if (!parsed) return prev; //skip + if ( + parsed.targetType !== "bitcoin" && + parsed.targetType !== "x-taler-bank" && + parsed.targetType !== "iban" + ) { + prev["unknown"].push({ parsed, acc }); } else { - prev[parsed.targetType].push({ parsed, acc }) + prev[parsed.targetType].push({ parsed, acc }); } - return prev - }, emptyList) - - const bitcoinAccounts = accountsByType["bitcoin"] - const talerbankAccounts = accountsByType["x-taler-bank"] - const ibanAccounts = accountsByType["iban"] - const unkownAccounts = accountsByType["unknown"] + return prev; + }, emptyList); + const bitcoinAccounts = accountsByType["bitcoin"]; + const talerbankAccounts = accountsByType["x-taler-bank"]; + const ibanAccounts = accountsByType["iban"]; + const unkownAccounts = accountsByType["unknown"]; return ( <Fragment> + {bitcoinAccounts.length > 0 && ( + <div class="table-container"> + <p class="card-header-title"> + <i18n.Translate>Bitcoin type accounts</i18n.Translate> + </p> + <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> + <thead> + <tr> + <th> + <i18n.Translate>Address</i18n.Translate> + </th> + <th> + <i18n.Translate>Sewgit 1</i18n.Translate> + </th> + <th> + <i18n.Translate>Sewgit 2</i18n.Translate> + </th> + <th /> + </tr> + </thead> + <tbody> + {bitcoinAccounts.map(({ parsed, acc }, idx) => { + const ac = parsed as PaytoUriBitcoin; + return ( + <tr key={idx}> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.targetPath} + </td> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.segwitAddrs[0]} + </td> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.segwitAddrs[1]} + </td> + <td class="is-actions-cell right-sticky"> + <div class="buttons is-right"> + <button + class="button is-danger is-small has-tooltip-left" + data-tooltip={i18n.str`delete selected accounts from the database`} + onClick={() => onDelete(acc)} + > + Delete + </button> + </div> + </td> + </tr> + ); + })} + </tbody> + </table> + </div> + )} - {bitcoinAccounts.length > 0 && <div class="table-container"> - <p class="card-header-title"><i18n.Translate>Bitcoin type accounts</i18n.Translate></p> - <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> - <thead> - <tr> - <th> - <i18n.Translate>Address</i18n.Translate> - </th> - <th> - <i18n.Translate>Sewgit 1</i18n.Translate> - </th> - <th> - <i18n.Translate>Sewgit 2</i18n.Translate> - </th> - <th /> - </tr> - </thead> - <tbody> - {bitcoinAccounts.map(({ parsed, acc }, idx) => { - const ac = parsed as PaytoUriBitcoin - return ( - <tr key={idx}> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.targetPath} - </td> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.segwitAddrs[0]} - </td> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.segwitAddrs[1]} - </td> - <td class="is-actions-cell right-sticky"> - <div class="buttons is-right"> - <button - class="button is-danger is-small has-tooltip-left" - data-tooltip={i18n.str`delete selected accounts from the database`} - onClick={() => onDelete(acc)} - > - Delete - </button> - </div> - </td> - </tr> - ); - })} - </tbody> - </table> - </div>} - - - - {talerbankAccounts.length > 0 && <div class="table-container"> - <p class="card-header-title"><i18n.Translate>Taler type accounts</i18n.Translate></p> - <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> - <thead> - <tr> - <th> - <i18n.Translate>Host</i18n.Translate> - </th> - <th> - <i18n.Translate>Account name</i18n.Translate> - </th> - <th /> - </tr> - </thead> - <tbody> - {talerbankAccounts.map(({ parsed, acc }, idx) => { - const ac = parsed as PaytoUriTalerBank - return ( - <tr key={idx}> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.host} - </td> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.account} - </td> - <td class="is-actions-cell right-sticky"> - <div class="buttons is-right"> - <button - class="button is-danger is-small has-tooltip-left" - data-tooltip={i18n.str`delete selected accounts from the database`} - onClick={() => onDelete(acc)} - > - Delete - </button> - </div> - </td> - </tr> - ); - })} - </tbody> - </table> - </div>} + {talerbankAccounts.length > 0 && ( + <div class="table-container"> + <p class="card-header-title"> + <i18n.Translate>Taler type accounts</i18n.Translate> + </p> + <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> + <thead> + <tr> + <th> + <i18n.Translate>Host</i18n.Translate> + </th> + <th> + <i18n.Translate>Account name</i18n.Translate> + </th> + <th /> + </tr> + </thead> + <tbody> + {talerbankAccounts.map(({ parsed, acc }, idx) => { + const ac = parsed as PaytoUriTalerBank; + return ( + <tr key={idx}> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.host} + </td> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.account} + </td> + <td class="is-actions-cell right-sticky"> + <div class="buttons is-right"> + <button + class="button is-danger is-small has-tooltip-left" + data-tooltip={i18n.str`delete selected accounts from the database`} + onClick={() => onDelete(acc)} + > + Delete + </button> + </div> + </td> + </tr> + ); + })} + </tbody> + </table> + </div> + )} - {ibanAccounts.length > 0 && <div class="table-container"> - <p class="card-header-title"><i18n.Translate>IBAN type accounts</i18n.Translate></p> - <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> - <thead> - <tr> - <th> - <i18n.Translate>Account name</i18n.Translate> - </th> - <th> - <i18n.Translate>IBAN</i18n.Translate> - </th> - <th /> - </tr> - </thead> - <tbody> - {ibanAccounts.map(({ parsed, acc }, idx) => { - const ac = parsed as PaytoUriIBAN - return ( - <tr key={idx}> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.params["receiver-name"]} - </td> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.iban} - </td> - <td class="is-actions-cell right-sticky"> - <div class="buttons is-right"> - <button - class="button is-danger is-small has-tooltip-left" - data-tooltip={i18n.str`delete selected accounts from the database`} - onClick={() => onDelete(acc)} - > - Delete - </button> - </div> - </td> - </tr> - ); - })} - </tbody> - </table> - </div>} + {ibanAccounts.length > 0 && ( + <div class="table-container"> + <p class="card-header-title"> + <i18n.Translate>IBAN type accounts</i18n.Translate> + </p> + <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> + <thead> + <tr> + <th> + <i18n.Translate>Account name</i18n.Translate> + </th> + <th> + <i18n.Translate>IBAN</i18n.Translate> + </th> + <th /> + </tr> + </thead> + <tbody> + {ibanAccounts.map(({ parsed, acc }, idx) => { + const ac = parsed as PaytoUriIBAN; + return ( + <tr key={idx}> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.params["receiver-name"]} + </td> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.iban} + </td> + <td class="is-actions-cell right-sticky"> + <div class="buttons is-right"> + <button + class="button is-danger is-small has-tooltip-left" + data-tooltip={i18n.str`delete selected accounts from the database`} + onClick={() => onDelete(acc)} + > + Delete + </button> + </div> + </td> + </tr> + ); + })} + </tbody> + </table> + </div> + )} - {unkownAccounts.length > 0 && <div class="table-container"> - <p class="card-header-title"><i18n.Translate>Other type accounts</i18n.Translate></p> - <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> - <thead> - <tr> - <th> - <i18n.Translate>Type</i18n.Translate> - </th> - <th> - <i18n.Translate>Path</i18n.Translate> - </th> - <th /> - </tr> - </thead> - <tbody> - {unkownAccounts.map(({ parsed, acc }, idx) => { - const ac = parsed as PaytoUriUnknown - return ( - <tr key={idx}> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.targetType} - </td> - <td - onClick={(): void => onSelect(acc)} - style={{ cursor: "pointer" }} - > - {ac.targetPath} - </td> - <td class="is-actions-cell right-sticky"> - <div class="buttons is-right"> - <button - class="button is-danger is-small has-tooltip-left" - data-tooltip={i18n.str`delete selected accounts from the database`} - onClick={() => onDelete(acc)} - > - Delete - </button> - </div> - </td> - </tr> - ); - })} - </tbody> - </table> - </div>} + {unkownAccounts.length > 0 && ( + <div class="table-container"> + <p class="card-header-title"> + <i18n.Translate>Other type accounts</i18n.Translate> + </p> + <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> + <thead> + <tr> + <th> + <i18n.Translate>Type</i18n.Translate> + </th> + <th> + <i18n.Translate>Path</i18n.Translate> + </th> + <th /> + </tr> + </thead> + <tbody> + {unkownAccounts.map(({ parsed, acc }, idx) => { + const ac = parsed as PaytoUriUnknown; + return ( + <tr key={idx}> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.targetType} + </td> + <td + onClick={(): void => onSelect(acc)} + style={{ cursor: "pointer" }} + > + {ac.targetPath} + </td> + <td class="is-actions-cell right-sticky"> + <div class="buttons is-right"> + <button + class="button is-danger is-small has-tooltip-left" + data-tooltip={i18n.str`delete selected accounts from the database`} + onClick={() => onDelete(acc)} + > + Delete + </button> + </div> + </td> + </tr> + ); + })} + </tbody> + </table> + </div> + )} </Fragment> - ); } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx index c0ddab475..9d09473bc 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx @@ -19,10 +19,13 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { HttpStatusCode, TalerError, TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util"; import { - useTranslationContext -} from "@gnu-taler/web-util/browser"; + HttpStatusCode, + TalerError, + TalerMerchantApi, + assertUnreachable, +} from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js"; @@ -40,30 +43,27 @@ interface Props { onSelect: (id: string) => void; } -export default function ListOtpDevices({ - onCreate, - onSelect, -}: Props): VNode { +export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode { const { i18n } = useTranslationContext(); const [notif, setNotif] = useState<Notification | undefined>(undefined); const { lib: api } = useSessionContext(); const { state } = useSessionContext(); const result = useInstanceBankAccounts(); - if (!result) return <Loading /> + if (!result) return <Loading />; if (result instanceof TalerError) { - return <ErrorLoadingMerchant error={result} /> + return <ErrorLoadingMerchant error={result} />; } if (result.type === "fail") { - switch(result.case) { + switch (result.case) { case HttpStatusCode.NotFound: { - return <NotFoundPageOrAdminCreate /> + return <NotFoundPageOrAdminCreate />; } case HttpStatusCode.Unauthorized: { - return <LoginPage /> + return <LoginPage />; } default: { - assertUnreachable(result) + assertUnreachable(result); } } } @@ -71,13 +71,15 @@ export default function ListOtpDevices({ return ( <Fragment> <NotificationCard notification={notif} /> - {result.body.accounts.length < 1 && - <NotificationCard notification={{ - type: "WARN", - message: i18n.str`You need to associate a bank account to receive revenue.`, - description: i18n.str`Without this the you won't be able to create new orders.` - }} /> - } + {result.body.accounts.length < 1 && ( + <NotificationCard + notification={{ + type: "WARN", + message: i18n.str`You need to associate a bank account to receive revenue.`, + description: i18n.str`Without this the you won't be able to create new orders.`, + }} + /> + )} <ListPage devices={result.body.accounts} // onLoadMoreBefore={ @@ -88,8 +90,9 @@ export default function ListOtpDevices({ onSelect={(e) => { onSelect(e.h_wire); }} - onDelete={(e: TalerMerchantApi.BankAccountSummaryEntry) => { - return api.instance.deleteBankAccount(state.token, e.h_wire) + onDelete={(e: TalerMerchantApi.BankAccountEntry) => { + return api.instance + .deleteBankAccount(state.token, e.h_wire) .then(() => setNotif({ message: i18n.str`bank account delete successfully`, @@ -102,9 +105,8 @@ export default function ListOtpDevices({ type: "ERROR", description: error.message, }), - ) - } - } + ); + }} /> </Fragment> ); 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 00833071b..812b2aa50 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 @@ -34,7 +34,7 @@ import { InputSelector } from "../../../../components/form/InputSelector.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; import { WithId } from "../../../../declaration.js"; -type Entity = TalerMerchantApi.BankAccountEntry & WithId; +type Entity = TalerMerchantApi.BankAccountDetail & WithId; const accountAuthType = ["unedit", "none", "basic"]; interface Props { @@ -68,7 +68,7 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { ? i18n.str`URL should not contain params` : facadeURL.hash ? i18n.str`URL should not hash param` - : undefined, + : undefined, credit_facade_credentials: undefinedIfEmpty({ username: state.credit_facade_credentials?.type !== "basic" @@ -83,7 +83,6 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode { : !state.credit_facade_credentials.password ? i18n.str`required` : undefined, - }), }; |