aboutsummaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/paths/instance
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance')
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx25
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx16
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx60
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx32
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx70
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx42
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx31
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/categories/create/Create.stories.tsx (renamed from packages/merchant-backoffice-ui/src/paths/instance/accounts/list/List.stories.tsx)6
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/categories/create/CreatePage.tsx105
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/categories/create/index.tsx77
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/categories/list/Table.tsx205
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/categories/list/index.tsx113
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/categories/update/Update.stories.tsx (renamed from packages/merchant-backoffice-ui/src/paths/instance/templates/list/List.stories.tsx)10
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/categories/update/UpdatePage.tsx162
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/categories/update/index.tsx114
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx83
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx90
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx87
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts1
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx7
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx69
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx17
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx48
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx35
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx18
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx36
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx43
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx37
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx33
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/List.stories.tsx28
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/ListPage.tsx59
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx14
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx75
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/UpdatePage.tsx50
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx7
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatedSuccessfully.tsx72
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx26
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/list/List.stories.tsx34
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx30
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx93
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/update/Update.stories.tsx60
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx27
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx54
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx31
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx63
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx20
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx88
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx93
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/qr/index.tsx31
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx21
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx53
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx11
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx25
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx23
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/create/index.tsx27
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/Table.tsx12
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/index.tsx94
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/UpdatePage.tsx47
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/index.tsx46
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx33
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx23
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx8
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx12
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/transfers/update/index.tsx26
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx29
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx4
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx114
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx25
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx21
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx33
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx25
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx28
72 files changed, 1893 insertions, 1474 deletions
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 61f62e631..953e412fe 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
@@ -21,16 +21,15 @@
import {
HttpStatusCode,
- PaytoString,
PaytoUri,
TalerError,
TalerMerchantApi,
TranslatedString,
assertUnreachable,
- parsePaytoUri,
+ parsePaytoUri
} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
+import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import {
@@ -40,6 +39,7 @@ import {
import { Input } from "../../../../components/form/Input.js";
import { InputPaytoForm } from "../../../../components/form/InputPaytoForm.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
+import { InputToggle } from "../../../../components/form/InputToggle.js";
import {
CompareAccountsModal,
ImportingAccountModal,
@@ -47,7 +47,6 @@ import {
import { undefinedIfEmpty } from "../../../../utils/table.js";
import { safeConvertURL } from "../update/UpdatePage.js";
import { testRevenueAPI } from "./index.js";
-import { InputToggle } from "../../../../components/form/InputToggle.js";
type Entity = TalerMerchantApi.AccountAddDetails & { verified?: boolean };
@@ -73,7 +72,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
undefined,
);
const errors: FormErrors<Entity> = {
- payto_uri: !state.payto_uri ? i18n.str`required` : undefined,
+ payto_uri: !state.payto_uri ? i18n.str`Required` : undefined,
credit_facade_credentials: !state.credit_facade_credentials
? undefined
@@ -81,12 +80,12 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
username:
state.credit_facade_credentials.type === "basic" &&
!state.credit_facade_credentials.username
- ? i18n.str`required`
+ ? i18n.str`Required`
: undefined,
password:
state.credit_facade_credentials.type === "basic" &&
!state.credit_facade_credentials.password
- ? i18n.str`required`
+ ? i18n.str`Required`
: undefined,
}),
credit_facade_url: !state.credit_facade_url
@@ -94,11 +93,11 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
: !facadeURL
? i18n.str`Invalid url`
: !facadeURL.href.endsWith("/")
- ? i18n.str`URL should end with a '/'`
+ ? i18n.str`URL must end with a '/'`
: facadeURL.searchParams.size > 0
- ? i18n.str`URL should not contain params`
+ ? i18n.str`URL must not contain params`
: facadeURL.hash
- ? i18n.str`URL should not hash param`
+ ? i18n.str`URL must not hash param`
: undefined,
};
@@ -223,7 +222,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<Input<Entity>
name="credit_facade_url"
label={i18n.str`Endpoint URL`}
- help="https://bank.demo.taler.net/accounts/_username_/taler-revenue/"
+ 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`}
/>
@@ -273,7 +272,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
data-tooltip={i18n.str`Compare info from server with account form`}
disabled={!state.credit_facade_url}
onClick={async () => {
- const result = await testAccountInfo();
+ await testAccountInfo();
}}
>
<i18n.Translate>Test</i18n.Translate>
@@ -302,7 +301,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
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 6994c579c..ac48e65ea 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
@@ -28,8 +28,7 @@ import {
TalerError,
TalerMerchantApi,
TalerRevenueHttpClient,
- assertUnreachable,
- opFixedSuccess,
+ opFixedSuccess
} from "@gnu-taler/taler-util";
import {
BrowserFetchHttpLib,
@@ -49,10 +48,9 @@ interface Props {
}
export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
- const { lib: api } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
- const [tested, setTested] = useState(false);
+ // const [tested, setTested] = useState(false);
const { i18n } = useTranslationContext();
return (
@@ -61,12 +59,12 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
<CreatePage
onBack={onBack}
onCreate={async (request: Entity) => {
- return api.instance
+ return lib.instance
.addBankAccount(state.token, request)
.then((created) => {
if (created.type === "fail") {
setNotif({
- message: i18n.str`could not create account`,
+ message: i18n.str`Could not create account`,
type: "ERROR",
description: created.detail.hint,
});
@@ -76,9 +74,9 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
})
.catch((error) => {
setNotif({
- message: i18n.str`could not create account`,
+ message: i18n.str`Could not create account`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
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
deleted file mode 100644
index 7e0b89f39..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { TalerMerchantApi } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import { CardTable } from "./Table.js";
-
-export interface Props {
- devices: TalerMerchantApi.BankAccountEntry[];
- // onLoadMoreBefore?: () => void;
- // onLoadMoreAfter?: () => void;
- onCreate: () => void;
- onDelete: (e: TalerMerchantApi.BankAccountEntry) => void;
- onSelect: (e: TalerMerchantApi.BankAccountEntry) => void;
-}
-
-export function ListPage({
- devices,
- onCreate,
- onDelete,
- onSelect,
- // onLoadMoreBefore,
- // onLoadMoreAfter,
-}: Props): VNode {
- return (
- <section class="section is-main-section">
- <CardTable
- accounts={devices.map((o) => ({
- ...o,
- id: String(o.h_wire),
- }))}
- onCreate={onCreate}
- onDelete={onDelete}
- onSelect={onSelect}
- // onLoadMoreBefore={onLoadMoreBefore}
- // hasMoreBefore={!onLoadMoreBefore}
- // onLoadMoreAfter={onLoadMoreAfter}
- // hasMoreAfter={!onLoadMoreAfter}
- />
- </section>
- );
-}
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 0e813f4d2..8ab782cf4 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
@@ -64,7 +64,7 @@ export function CardTable({
<div class="card-header-icon" aria-label="more options">
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`add new accounts`}
+ data-tooltip={i18n.str`Add new account`}
>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
@@ -126,14 +126,14 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
const bitcoinAccounts = accountsByType["bitcoin"];
const talerbankAccounts = accountsByType["x-taler-bank"];
const ibanAccounts = accountsByType["iban"];
- const unkownAccounts = accountsByType["unknown"];
+ const unknownAccounts = accountsByType["unknown"];
return (
<Fragment>
{bitcoinAccounts.length > 0 && (
<div class="table-container">
<p class="card-header-title">
- <i18n.Translate>Bitcoin type accounts</i18n.Translate>
+ <i18n.Translate>Wire method: Bitcoin</i18n.Translate>
</p>
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
@@ -177,10 +177,10 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
<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`}
+ data-tooltip={i18n.str`Delete selected accounts from the database`}
onClick={() => onDelete(acc)}
>
- Delete
+ <i18n.Translate>Delete</i18n.Translate>
</button>
</div>
</td>
@@ -195,7 +195,7 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
{talerbankAccounts.length > 0 && (
<div class="table-container">
<p class="card-header-title">
- <i18n.Translate>Taler type accounts</i18n.Translate>
+ <i18n.Translate>Wire method: x-taler-bank</i18n.Translate>
</p>
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
@@ -230,10 +230,10 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
<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`}
+ data-tooltip={i18n.str`Delete selected accounts from the database`}
onClick={() => onDelete(acc)}
>
- Delete
+ <i18n.Translate>Delete</i18n.Translate>
</button>
</div>
</td>
@@ -248,7 +248,7 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
{ibanAccounts.length > 0 && (
<div class="table-container">
<p class="card-header-title">
- <i18n.Translate>IBAN type accounts</i18n.Translate>
+ <i18n.Translate>Wire method: IBAN</i18n.Translate>
</p>
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
@@ -283,10 +283,10 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
<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`}
+ data-tooltip={i18n.str`Delete selected accounts from the database`}
onClick={() => onDelete(acc)}
>
- Delete
+ <i18n.Translate>Delete</i18n.Translate>
</button>
</div>
</td>
@@ -298,10 +298,10 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
</div>
)}
- {unkownAccounts.length > 0 && (
+ {unknownAccounts.length > 0 && (
<div class="table-container">
<p class="card-header-title">
- <i18n.Translate>Other type accounts</i18n.Translate>
+ <i18n.Translate>Other accounts</i18n.Translate>
</p>
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
@@ -316,7 +316,7 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
</tr>
</thead>
<tbody>
- {unkownAccounts.map(({ parsed, acc }, idx) => {
+ {unknownAccounts.map(({ parsed, acc }, idx) => {
const ac = parsed as PaytoUriUnknown;
return (
<tr key={idx}>
@@ -336,10 +336,10 @@ function Table({ accounts, onDelete, onSelect }: TableProps): VNode {
<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`}
+ data-tooltip={i18n.str`Delete selected accounts from the database`}
onClick={() => onDelete(acc)}
>
- Delete
+ <i18n.Translate>Delete</i18n.Translate>
</button>
</div>
</td>
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 9d09473bc..2bb1144f5 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
@@ -36,7 +36,7 @@ import { useInstanceBankAccounts } from "../../../../hooks/bank.js";
import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
-import { ListPage } from "./ListPage.js";
+import { CardTable } from "./Table.js";
interface Props {
onCreate: () => void;
@@ -46,8 +46,7 @@ interface Props {
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 { state, lib } = useSessionContext();
const result = useInstanceBankAccounts();
if (!result) return <Loading />;
@@ -80,34 +79,43 @@ export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode {
}}
/>
)}
- <ListPage
- devices={result.body.accounts}
- // onLoadMoreBefore={
- // result.isFirstPage ? undefined: result.loadFirst
- // }
- // onLoadMoreAfter={result.isLastPage ? undefined : result.loadNext}
- onCreate={onCreate}
- onSelect={(e) => {
- onSelect(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`,
- type: "SUCCESS",
- }),
- )
- .catch((error) =>
- setNotif({
- message: i18n.str`could not delete the bank account`,
- type: "ERROR",
- description: error.message,
- }),
- );
- }}
- />
+ <section class="section is-main-section">
+ <CardTable
+ accounts={result.body.accounts.map((o) => ({
+ ...o,
+ id: String(o.h_wire),
+ }))}
+ onCreate={onCreate}
+ onSelect={(e) => {
+ onSelect(e.h_wire);
+ }}
+ onDelete={async (e: TalerMerchantApi.BankAccountEntry) => {
+ return lib.instance
+ .deleteBankAccount(state.token, e.h_wire)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Bank account delete successfully`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not delete the bank account`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
+ .catch((error) =>
+ setNotif({
+ message: i18n.str`Could not delete the bank account`,
+ type: "ERROR",
+ description: error instanceof Error ? error.message : String(error),
+ }),
+ );
+ }}
+ />
+ </section>
</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 73fe43026..e6dcfec7b 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
@@ -77,7 +77,7 @@ export function UpdatePage({
payto_uri: account.payto_uri,
credit_facade_url: account.credit_facade_url,
credit_facade_credentials: {
- // @ts-ignore
+ // @ts-expect-error unofficial unedited value
type: "unedit",
},
});
@@ -95,48 +95,46 @@ export function UpdatePage({
const facadeURL = safeConvertURL(state.credit_facade_url);
- const errors: FormErrors<FormType> = {
- payto_uri: !state.payto_uri ? i18n.str`required` : undefined,
+ const errors = undefinedIfEmpty<FormErrors<FormType>>({
+ payto_uri: !state.payto_uri ? i18n.str`Required` : undefined,
credit_facade_url: !state.credit_facade_url
? undefined
: !facadeURL
? i18n.str`Invalid url`
: !facadeURL.href.endsWith("/")
- ? i18n.str`URL should end with a '/'`
+ ? i18n.str`URL must end with a '/'`
: facadeURL.searchParams.size > 0
- ? i18n.str`URL should not contain params`
+ ? i18n.str`URL must not contain params`
: facadeURL.hash
- ? i18n.str`URL should not hash param`
+ ? i18n.str`URL must not hash param`
: undefined,
credit_facade_credentials: !state.credit_facade_credentials
? undefined
: undefinedIfEmpty({
type:
replacingAccountId &&
- // @ts-ignore
+ // @ts-expect-error unedit is not in facade creds
state.credit_facade_credentials?.type === "unedit"
- ? i18n.str`required`
+ ? i18n.str`Required`
: undefined,
username:
state.credit_facade_credentials?.type !== "basic"
? undefined
: !state.credit_facade_credentials.username
- ? i18n.str`required`
+ ? i18n.str`Required`
: undefined,
password:
state.credit_facade_credentials?.type !== "basic"
? undefined
: !state.credit_facade_credentials.password
- ? i18n.str`required`
+ ? i18n.str`Required`
: undefined,
}),
- };
+ });
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
+ const hasErrors = errors !== undefined;
const submitForm = () => {
if (hasErrors) return Promise.reject();
@@ -150,7 +148,7 @@ export function UpdatePage({
credit_facade_url == undefined ||
state.credit_facade_credentials === undefined
? undefined
- : // @ts-ignore
+ : // @ts-expect-error unedit is not in facade creds
state.credit_facade_credentials.type === "unedit"
? undefined
: state.credit_facade_credentials.type === "basic"
@@ -164,14 +162,12 @@ export function UpdatePage({
};
if (replacingAccountId) {
- console.log("======== REPLACE");
return onReplace(account, {
payto_uri: state.payto_uri!,
credit_facade_credentials,
credit_facade_url,
});
} else {
- console.log("======== UPDATE");
return onUpdate({ credit_facade_credentials, credit_facade_url });
}
};
@@ -283,7 +279,7 @@ export function UpdatePage({
<Input<Entity>
name="credit_facade_url"
label={i18n.str`Endpoint URL`}
- help="https://bank.demo.taler.net/accounts/_username_/taler-revenue/"
+ 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`}
/>
@@ -293,9 +289,9 @@ export function UpdatePage({
tooltip={i18n.str`Choose the authentication type for the account info URL`}
values={accountAuthType}
toStr={(str) => {
- if (str === "none") return "Without authentication";
- if (str === "basic") return "With authentication";
- return "Do not change";
+ if (str === "none") return i18n.str`Without authentication`;
+ if (str === "basic") return i18n.str`With authentication`;
+ return i18n.str`Do not change`;
}}
/>
{state.credit_facade_credentials?.type === "basic" ? (
@@ -334,7 +330,7 @@ export function UpdatePage({
data-tooltip={i18n.str`Compare info from server with account form`}
disabled={!state.credit_facade_url}
onClick={async () => {
- const result = await testAccountInfo();
+ await testAccountInfo();
}}
>
<i18n.Translate>Test</i18n.Translate>
@@ -354,7 +350,7 @@ export function UpdatePage({
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
index 60dad7257..aca8aeb8e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
@@ -51,8 +51,7 @@ export default function UpdateValidator({
onConfirm,
onBack,
}: Props): VNode {
- const { lib: api } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const result = useBankAccountDetails(bid);
const [notif, setNotif] = useState<Notification | undefined>(undefined);
@@ -83,12 +82,12 @@ export default function UpdateValidator({
account={{ ...result.body, id: bid }}
onBack={onBack}
onUpdate={async (request) => {
- return api.instance
+ return lib.instance
.updateBankAccount(state.token, bid, request)
.then((updated) => {
if (updated.type === "fail") {
setNotif({
- message: i18n.str`could not update account`,
+ message: i18n.str`Could not update account`,
type: "ERROR",
description: updated.detail.hint,
});
@@ -98,52 +97,52 @@ export default function UpdateValidator({
})
.catch((error) => {
setNotif({
- message: i18n.str`could not update account`,
+ message: i18n.str`Could not update account`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
onReplace={async (prev, next) => {
try {
- const created = await api.instance.addBankAccount(
+ const created = await lib.instance.addBankAccount(
state.token,
next,
);
if (created.type === "fail") {
setNotif({
- message: i18n.str`could not create account`,
+ message: i18n.str`Could not create account`,
type: "ERROR",
description: created.detail.hint,
});
return;
}
- } catch (error: any) {
+ } catch (error) {
setNotif({
- message: i18n.str`could not create account`,
+ message: i18n.str`Could not create account`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
return;
}
try {
- const deleted = await api.instance.deleteBankAccount(
+ const deleted = await lib.instance.deleteBankAccount(
state.token,
prev.h_wire,
);
if (deleted.type === "fail") {
setNotif({
- message: i18n.str`could not delete account`,
+ message: i18n.str`Could not delete account`,
type: "ERROR",
description: deleted.detail.hint,
});
return;
}
- } catch (error: any) {
+ } catch (error) {
setNotif({
- message: i18n.str`could not delete account`,
+ message: i18n.str`Could not delete account`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
return;
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/List.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/create/Create.stories.tsx
index 18e762642..36b31ebe8 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/List.stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/categories/create/Create.stories.tsx
@@ -19,10 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { FunctionalComponent, h } from "preact";
-import { ListPage as TestedComponent } from "./ListPage.js";
+import { h, VNode, FunctionalComponent } from "preact";
+import { CreatePage as TestedComponent } from "./CreatePage.js";
export default {
- title: "Pages/Accounts/List",
+ title: "Pages/OtpDevices/Create",
component: TestedComponent,
};
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/categories/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/create/CreatePage.tsx
new file mode 100644
index 000000000..af8cf1c0b
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/instance/categories/create/CreatePage.tsx
@@ -0,0 +1,105 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2024 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import {
+ TalerMerchantApi
+} from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
+import { useState } from "preact/hooks";
+import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
+import {
+ FormErrors,
+ FormProvider,
+} from "../../../../components/form/FormProvider.js";
+import { Input } from "../../../../components/form/Input.js";
+import { undefinedIfEmpty } from "../../../../utils/table.js";
+
+type Entity = TalerMerchantApi.CategoryCreateRequest;
+
+interface Props {
+ onCreate: (d: Entity) => Promise<void>;
+ onBack?: () => void;
+}
+
+export function CreatePage({ onCreate, onBack }: Props): VNode {
+ const { i18n } = useTranslationContext();
+
+ const [state, setState] = useState<Partial<Entity>>({name_i18n: {}});
+
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
+ name: !state.name
+ ? i18n.str`Required`
+ : !/[a-zA-Z0-9]*/.test(state.name)
+ ? i18n.str`Invalid. Only characters and numbers`
+ : undefined,
+ });
+
+ const hasErrors = errors !== undefined;
+
+ const submitForm = () => {
+ if (hasErrors) return Promise.reject();
+ return onCreate(state as Entity);
+ };
+
+ return (
+ <div>
+ <section class="section is-main-section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <FormProvider
+ object={state}
+ valueHandler={setState}
+ errors={errors}
+ >
+ <Input<Entity>
+ name="name"
+ label={i18n.str`Name`}
+ tooltip={i18n.str`Category name`}
+ />{" "}
+ </FormProvider>
+
+ <div class="buttons is-right mt-5">
+ {onBack && (
+ <button class="button" onClick={onBack}>
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ )}
+ <AsyncButton
+ disabled={hasErrors}
+ data-tooltip={
+ hasErrors
+ ? i18n.str`Need to complete marked fields`
+ : i18n.str`Confirm operation`
+ }
+ onClick={submitForm}
+ >
+ <i18n.Translate>Confirm</i18n.Translate>
+ </AsyncButton>
+ </div>
+ </div>
+ <div class="column" />
+ </div>
+ </section>
+ </div>
+ );
+}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/categories/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/create/index.tsx
new file mode 100644
index 000000000..0eb8055da
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/instance/categories/create/index.tsx
@@ -0,0 +1,77 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2024 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { TalerMerchantApi } 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 { NotificationCard } from "../../../../components/menu/index.js";
+import { useSessionContext } from "../../../../context/session.js";
+import { Notification } from "../../../../utils/types.js";
+import { CreatePage } from "./CreatePage.js";
+
+type Entity = TalerMerchantApi.CategoryCreateRequest;
+interface Props {
+ onBack?: () => void;
+ onConfirm: () => void;
+}
+
+export default function CreateCategory({ onConfirm, onBack }: Props): VNode {
+ const { state, lib } = useSessionContext();
+ const [notif, setNotif] = useState<Notification | undefined>(undefined);
+ const { i18n } = useTranslationContext();
+
+ return (
+ <>
+ <NotificationCard notification={notif} />
+ <CreatePage
+ onBack={onBack}
+ onCreate={async (request: Entity) => {
+ return lib.instance
+ .addCategory(state.token, request)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Category added successfully`,
+ type: "SUCCESS",
+ });
+ onConfirm()
+ } else {
+ setNotif({
+ message: i18n.str`Could not add category`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
+ .catch((error) => {
+ setNotif({
+ message: i18n.str`Could not add category`,
+ type: "ERROR",
+ description:
+ error instanceof Error ? error.message : String(error),
+ });
+ });
+ }}
+ />
+ </>
+ );
+}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/categories/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/list/Table.tsx
new file mode 100644
index 000000000..876d035df
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/instance/categories/list/Table.tsx
@@ -0,0 +1,205 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2024 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/>
+ */
+
+/**
+ *
+ * @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 { StateUpdater, useState } from "preact/hooks";
+
+type Entity = TalerMerchantApi.CategoryListEntry;
+
+interface Props {
+ devices: Entity[];
+ onDelete: (e: Entity) => void;
+ onSelect: (e: Entity) => void;
+ onCreate: () => void;
+ onLoadMoreBefore?: () => void;
+ onLoadMoreAfter?: () => void;
+}
+
+export function CardTable({
+ devices,
+ onCreate,
+ onDelete,
+ onSelect,
+ onLoadMoreAfter,
+ onLoadMoreBefore,
+}: Props): VNode {
+ const [rowSelection, rowSelectionHandler] = useState<string[]>([]);
+
+ const { i18n } = useTranslationContext();
+
+ return (
+ <div class="card has-table">
+ <header class="card-header">
+ <p class="card-header-title">
+ <span class="icon">
+ <i class="mdi mdi-label" />
+ </span>
+ <i18n.Translate>Categories</i18n.Translate>
+ </p>
+ <div class="card-header-icon" aria-label="more options">
+ <span
+ class="has-tooltip-left"
+ data-tooltip={i18n.str`Add new devices`}
+ >
+ <button class="button is-info" type="button" onClick={onCreate}>
+ <span class="icon is-small">
+ <i class="mdi mdi-plus mdi-36px" />
+ </span>
+ </button>
+ </span>
+ </div>
+ </header>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {devices.length > 0 ? (
+ <Table
+ instances={devices}
+ onDelete={onDelete}
+ onSelect={onSelect}
+ rowSelection={rowSelection}
+ rowSelectionHandler={rowSelectionHandler}
+ onLoadMoreAfter={onLoadMoreAfter}
+ onLoadMoreBefore={onLoadMoreBefore}
+ />
+ ) : (
+ <EmptyTable />
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+interface TableProps {
+ rowSelection: string[];
+ instances: Entity[];
+ onDelete: (e: Entity) => void;
+ onSelect: (e: Entity) => void;
+ rowSelectionHandler: StateUpdater<string[]>;
+ onLoadMoreBefore?: () => void;
+ onLoadMoreAfter?: () => void;
+}
+
+function Table({
+ instances,
+ onLoadMoreAfter,
+ onDelete,
+ onSelect,
+ onLoadMoreBefore,
+}: TableProps): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <div class="table-container">
+ {onLoadMoreBefore && (
+ <button
+ class="button is-fullwidth"
+ data-tooltip={i18n.str`Load more devices before the first one`}
+ onClick={onLoadMoreBefore}
+ >
+ <i18n.Translate>Load first page</i18n.Translate>
+ </button>
+ )}
+ <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
+ <thead>
+ <tr>
+ <th>
+ <i18n.Translate>ID</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Name</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Total products</i18n.Translate>
+ </th>
+ <th />
+ </tr>
+ </thead>
+ <tbody>
+ {instances.map((i) => {
+ return (
+ <tr key={i.category_id}>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.category_id}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.name}
+ </td>
+ <td
+ onClick={(): void => onSelect(i)}
+ style={{ cursor: "pointer" }}
+ >
+ {i.product_count}
+ </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 category from the database`}
+ onClick={() => onDelete(i)}
+ >
+ <i18n.Translate>Delete</i18n.Translate>
+ </button>
+ </div>
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ {onLoadMoreAfter && (
+ <button
+ class="button is-fullwidth"
+ data-tooltip={i18n.str`Load more devices after the last one`}
+ onClick={onLoadMoreAfter}
+ >
+ <i18n.Translate>Load next page</i18n.Translate>
+ </button>
+ )}
+ </div>
+ );
+}
+
+function EmptyTable(): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <div class="content has-text-grey has-text-centered">
+ <p>
+ <span class="icon is-large">
+ <i class="mdi mdi-magnify mdi-48px" />
+ </span>
+ </p>
+ <p>
+ <i18n.Translate>
+ There is no categories yet, add more pressing the + sign
+ </i18n.Translate>
+ </p>
+ </div>
+ );
+}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/categories/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/list/index.tsx
new file mode 100644
index 000000000..732ab5201
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/instance/categories/list/index.tsx
@@ -0,0 +1,113 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2024 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import {
+ 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";
+import { Loading } from "../../../../components/exception/loading.js";
+import { NotificationCard } from "../../../../components/menu/index.js";
+import { useSessionContext } from "../../../../context/session.js";
+import { useInstanceCategories } from "../../../../hooks/category.js";
+import { Notification } from "../../../../utils/types.js";
+import { LoginPage } from "../../../login/index.js";
+import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
+import { CardTable } from "./Table.js";
+
+interface Props {
+ onCreate: () => void;
+ onSelect: (id: string) => void;
+}
+
+export default function ListCategories({ onCreate, onSelect }: Props): VNode {
+ // const [position, setPosition] = useState<string | undefined>(undefined);
+ const { i18n } = useTranslationContext();
+ const [notif, setNotif] = useState<Notification | undefined>(undefined);
+ const { state, lib } = useSessionContext();
+ const result = useInstanceCategories();
+
+ if (!result) return <Loading />;
+ if (result instanceof TalerError) {
+ return <ErrorLoadingMerchant error={result} />;
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case HttpStatusCode.NotFound: {
+ return <NotFoundPageOrAdminCreate />;
+ }
+ case HttpStatusCode.Unauthorized: {
+ return <LoginPage />;
+ }
+ default: {
+ assertUnreachable(result);
+ }
+ }
+ }
+
+ return (
+ <Fragment>
+ <NotificationCard notification={notif} />
+
+ <section class="section is-main-section">
+ <CardTable
+ devices={result.body.categories}
+ onLoadMoreBefore={undefined} //result.isFirstPage ? undefined : result.loadFirst}
+ onLoadMoreAfter={undefined} //result.isLastPage ? undefined : result.loadNext}
+ onCreate={onCreate}
+ onSelect={(e) => {
+ onSelect(String(e.category_id));
+ }}
+ onDelete={async (e: TalerMerchantApi.CategoryListEntry) => {
+ return lib.instance
+ .deleteCategory(state.token, String(e.category_id))
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Category delete successfully`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not delete the category`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
+ .catch((error) =>
+ setNotif({
+ message: i18n.str`Could not delete the category`,
+ type: "ERROR",
+ description: error instanceof Error ? error.message : String(error),
+ }),
+ );
+ }}
+ />
+ </section>
+ </Fragment>
+ );
+}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/List.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/update/Update.stories.tsx
index 707324d40..06ea9d07a 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/List.stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/categories/update/Update.stories.tsx
@@ -19,10 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { FunctionalComponent, h } from "preact";
-import { ListPage as TestedComponent } from "./ListPage.js";
+import { h, VNode, FunctionalComponent } from "preact";
+import { UpdatePage as TestedComponent } from "./UpdatePage.js";
export default {
- title: "Pages/Templates/List",
+ title: "Pages/OtpDevices/Update",
component: TestedComponent,
+ argTypes: {
+ onUpdate: { action: "onUpdate" },
+ onBack: { action: "onBack" },
+ },
};
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/categories/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/update/UpdatePage.tsx
new file mode 100644
index 000000000..a08189192
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/instance/categories/update/UpdatePage.tsx
@@ -0,0 +1,162 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2024 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { TalerError, TalerMerchantApi } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
+import { FormProvider } from "../../../../components/form/FormProvider.js";
+import { Input } from "../../../../components/form/Input.js";
+import { InputArray } from "../../../../components/form/InputArray.js";
+import { useSessionContext } from "../../../../context/session.js";
+import { WithId } from "../../../../declaration.js";
+import {
+ useInstanceProducts
+} from "../../../../hooks/product.js";
+
+type Entity = TalerMerchantApi.CategoryProductList & WithId;
+
+interface Props {
+ onUpdate: (d: Entity) => Promise<void>;
+ onBack?: () => void;
+ category: Entity;
+}
+export function UpdatePage({ category, onUpdate, onBack }: Props): VNode {
+ const { i18n } = useTranslationContext();
+ const {
+ state: { token },
+ lib,
+ } = useSessionContext();
+ // FIXME: if the product list is big the will bring a lot of info
+ const inventoryResult = useInstanceProducts();
+
+ const inventory =
+ !inventoryResult ||
+ inventoryResult instanceof TalerError ||
+ inventoryResult.type === "fail"
+ ? []
+ : inventoryResult.body;
+
+ const [state, setState] = useState<
+ Partial<Entity & { product_map: { id: string; description: string }[] }>
+ >({
+ ...category,
+ product_map: [],
+ });
+
+ useEffect(() => {
+ if (!category || !category?.products) return;
+ console.log(category.products);
+ const ps = category.products.map((prod) => {
+ return lib.instance
+ .getProductDetails(token, String(prod.product_id))
+ .then((res) => {
+ return res.type === "fail"
+ ? undefined
+ : { id: String(prod.product_id), description: res.body.description };
+ });
+ });
+ Promise.all(ps).then((all) => {
+ const product_map = all.filter(notEmpty);
+ setState({ ...state, product_map });
+ });
+ }, []);
+
+ const submitForm = () => {
+ const pids = state.product_map?.map((p) => {
+ return { product_id: p.id };
+ });
+ state.products = pids;
+ delete state.product_map;
+ return onUpdate(state as Entity);
+ };
+
+ return (
+ <div>
+ <section class="section">
+ <section class="hero is-hero-bar">
+ <div class="hero-body">
+ <div class="level">
+ <div class="level-left">
+ <div class="level-item">
+ <span class="is-size-4">
+ <i18n.Translate>Id:</i18n.Translate>
+ &nbsp;
+ <b>{category.id}</b>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+ <hr />
+
+ <section class="section is-main-section">
+ <div class="columns">
+ <div class="column is-four-fifths">
+ <FormProvider object={state} valueHandler={setState}>
+ <Input<Entity>
+ name="name"
+ label={i18n.str`Name`}
+ tooltip={i18n.str`Name of the category`}
+ />
+ <InputArray
+ name="product_map"
+ label={i18n.str`Products`}
+ getSuggestion={async () => {
+ return inventory.map((prod) => {
+ return {
+ description: prod.description,
+ id: prod.id,
+ };
+ });
+ }}
+ help={i18n.str`Search by product description or id`}
+ tooltip={i18n.str`Products that this category will list.`}
+ unique
+ />
+ </FormProvider>
+
+ <div class="buttons is-right mt-5">
+ {onBack && (
+ <button class="button" onClick={onBack}>
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ )}
+ <AsyncButton
+ disabled={false}
+ data-tooltip={i18n.str`Confirm operation`}
+ onClick={submitForm}
+ >
+ <i18n.Translate>Confirm</i18n.Translate>
+ </AsyncButton>
+ </div>
+ </div>
+ </div>
+ </section>
+ </section>
+ </div>
+ );
+}
+function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
+ return value !== null && value !== undefined;
+}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/categories/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/update/index.tsx
new file mode 100644
index 000000000..19352ca3e
--- /dev/null
+++ b/packages/merchant-backoffice-ui/src/paths/instance/categories/update/index.tsx
@@ -0,0 +1,114 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2024 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/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import {
+ HttpStatusCode,
+ TalerError,
+ 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";
+import { Loading } from "../../../../components/exception/loading.js";
+import { NotificationCard } from "../../../../components/menu/index.js";
+import { useSessionContext } from "../../../../context/session.js";
+import { useCategoryDetails } from "../../../../hooks/category.js";
+import { Notification } from "../../../../utils/types.js";
+import { LoginPage } from "../../../login/index.js";
+import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
+import { UpdatePage } from "./UpdatePage.js";
+
+interface Props {
+ onBack?: () => void;
+ onConfirm: () => void;
+ cid: string;
+}
+export default function UpdateCategory({
+ cid,
+ onConfirm,
+ onBack,
+}: Props): VNode {
+ const result = useCategoryDetails(cid);
+ const [notif, setNotif] = useState<Notification | undefined>(undefined);
+ const { state, lib } = useSessionContext();
+
+ const { i18n } = useTranslationContext();
+
+ if (!result) return <Loading />;
+ if (result instanceof TalerError) {
+ return <ErrorLoadingMerchant error={result} />;
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case HttpStatusCode.NotFound: {
+ return <NotFoundPageOrAdminCreate />;
+ }
+ case HttpStatusCode.Unauthorized: {
+ return <LoginPage />;
+ }
+ default: {
+ assertUnreachable(result);
+ }
+ }
+ }
+
+ return (
+ <Fragment>
+ <NotificationCard notification={notif} />
+ <UpdatePage
+ category={{
+ ...result.body,
+ id: cid,
+ }}
+ onBack={onBack}
+ onUpdate={async (newInfo) => {
+ return lib.instance
+ .updateCategory(state.token, cid, newInfo)
+ .then((d) => {
+ if (d.type === "ok") {
+ onConfirm();
+ } else {
+ switch (d.case) {
+ case HttpStatusCode.NotFound: {
+ setNotif({
+ message: i18n.str`Could not update category`,
+ type: "ERROR",
+ description: i18n.str`Category id is unknown`,
+ });
+ break;
+ }
+ }
+ }
+ })
+ .catch((error) => {
+ setNotif({
+ message: i18n.str`Could not update category`,
+ type: "ERROR",
+ description:
+ error instanceof Error ? error.message : String(error),
+ });
+ });
+ }}
+ />
+ </Fragment>
+ );
+}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx
deleted file mode 100644
index 3168c7cc4..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { FormProvider } from "../../../components/form/FormProvider.js";
-import { Input } from "../../../components/form/Input.js";
-import { TalerMerchantApi } from "@gnu-taler/taler-util";
-
-type Entity = TalerMerchantApi.InstanceReconfigurationMessage;
-interface Props {
- onUpdate: () => void;
- onDelete: () => void;
- selected: TalerMerchantApi.QueryInstancesResponse;
-}
-
-function convert(
- from: TalerMerchantApi.QueryInstancesResponse,
-): Entity {
- const defaults = {
- default_wire_fee_amortization: 1,
- use_stefan: true,
- default_pay_delay: { d_us: 1000 * 60 * 60 * 1000 }, //one hour
- default_wire_transfer_delay: { d_us: 1000 * 60 * 60 * 2 * 1000 }, //two hours
- };
- return { ...defaults, ...from };
-}
-
-export function DetailPage({ selected }: Props): VNode {
- const [value, valueHandler] = useState<Partial<Entity>>(convert(selected));
-
- const { i18n } = useTranslationContext();
-
- return (
- <div>
- <section class="hero is-hero-bar">
- <div class="hero-body">
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <h1 class="title">Here goes the instance description</h1>
- </div>
- </div>
- <div class="level-right" style="display: none;">
- <div class="level-item" />
- </div>
- </div>
- </div>
- </section>
-
- <section class="section is-main-section">
- <div class="columns">
- <div class="column" />
- <div class="column is-6">
- <FormProvider<Entity> object={value} valueHandler={valueHandler}>
- <Input<Entity> name="name" readonly label={i18n.str`Name`} />
- </FormProvider>
- </div>
- <div class="column" />
- </div>
- </section>
- </div>
- );
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
deleted file mode 100644
index e1a7f87f0..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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 { HttpStatusCode, TalerError, assertUnreachable } from "@gnu-taler/taler-util";
-import { Fragment, VNode, h } from "preact";
-import { useState } from "preact/hooks";
-import { ErrorLoadingMerchant } from "../../../components/ErrorLoadingMerchant.js";
-import { Loading } from "../../../components/exception/loading.js";
-import { DeleteModal } from "../../../components/modal/index.js";
-import { useSessionContext } from "../../../context/session.js";
-import { useInstanceDetails } from "../../../hooks/instance.js";
-import { LoginPage } from "../../login/index.js";
-import { NotFoundPageOrAdminCreate } from "../../notfound/index.js";
-import { DetailPage } from "./DetailPage.js";
-
-interface Props {
- onUpdate: () => void;
- onDelete: () => void;
-}
-
-export default function Detail({
- onUpdate,
- onDelete,
-}: Props): VNode {
- const { state } = useSessionContext();
- const result = useInstanceDetails();
- const [deleting, setDeleting] = useState<boolean>(false);
-
- // const { deleteInstance } = useInstanceAPI();
- const { lib } = useSessionContext();
-
- if (!result) return <Loading />
- if (result instanceof TalerError) {
- return <ErrorLoadingMerchant error={result} />
- }
- if (result.type === "fail") {
- switch(result.case) {
- case HttpStatusCode.Unauthorized: {
- return <LoginPage />
- }
- case HttpStatusCode.NotFound: {
- return <NotFoundPageOrAdminCreate />;
- }
- default: {
- assertUnreachable(result)
- }
- }
- }
-
-
- return (
- <Fragment>
- <DetailPage
- selected={result.body}
- onUpdate={onUpdate}
- onDelete={() => setDeleting(true)}
- />
- {deleting && (
- <DeleteModal
- element={{ name: result.body.name, id: state.instance }}
- onCancel={() => setDeleting(false)}
- onConfirm={async (): Promise<void> => {
- if (state.status !== "loggedIn") {
- return
- }
- try {
- await lib.instance.deleteCurrentInstance(state.token);
- onDelete();
- } catch (error) {
- //FIXME: show message error
- }
- setDeleting(false);
- }}
- />
- )}
- </Fragment>
- );
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx
deleted file mode 100644
index 42cb1cb02..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser";
-import { FunctionalComponent, h } from "preact";
-import { DetailPage as TestedComponent } from "./DetailPage.js";
-
-export default {
- title: "Pages/Instance/Detail",
- component: TestedComponent,
- argTypes: {
- onUpdate: { action: "onUpdate" },
- onBack: { action: "onBack" },
- },
-};
-
-function createExample<Props>(
- Internal: FunctionalComponent<Props>,
- props: Partial<Props>,
-) {
- const component = (args: any) => (
- <MerchantApiProviderTesting
- value={{
- cancelRequest: () => { },
- changeBackend: () => { },
- config: {
- currency: "ARS",
- version: "1",
- currencies: {
- "ASD": {
- name: "testkudos",
- alt_unit_names: {},
- num_fractional_input_digits: 1,
- num_fractional_normal_digits: 1,
- num_fractional_trailing_zero_digits: 1,
- }
- },
- exchanges: [],
- name: "taler-merchant"
- },
- hints: [],
- lib: {} as any,
- onActivity: (() => { }) as any,
- url: new URL("asdasd"),
- }}
- >
- <Internal {...(props as any)} />
- </MerchantApiProviderTesting>
- );
- return { component, props };
-}
-
-export const Example = createExample(TestedComponent, {
- selected: {
- name: "name",
- auth: { method: "external" },
- address: {},
- user_type: "business",
- jurisdiction: {},
- use_stefan: true,
- default_pay_delay: {
- d_us: 1000 * 1000, //one second
- },
- default_wire_transfer_delay: {
- d_us: 1000 * 1000, //one second
- },
- merchant_pub: "ASDWQEKASJDKSADJ",
- },
-});
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts b/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts
index 8f06937df..a21ba918e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts
+++ b/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts
@@ -14,5 +14,4 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-export * as details from "./details/stories.js";
export * as kycList from "./kyc/list/ListPage.stories.js";
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
index ed0e1220f..ff63dc64e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx
@@ -45,12 +45,7 @@ export default function ListKYC(_p: Props): VNode {
return <div />
}
case HttpStatusCode.BadGateway: {
- const status = result.body;
-
- if (!status) {
- return <div>no kyc required</div>;
- }
- return <ListPage status={status} />;
+ return <ListPage status={result.body} />;
}
case HttpStatusCode.ServiceUnavailable: {
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
index 151905b5e..3adf79b04 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
@@ -27,9 +27,7 @@ import {
TalerMerchantApi,
TalerProtocolDuration,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { format, isFuture } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
@@ -149,15 +147,15 @@ export function CreatePage({
? undefined
: Amounts.parse(value.pricing.order_price);
- const errors: FormErrors<Entity> = {
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
pricing: undefinedIfEmpty({
- summary: !value.pricing?.summary ? i18n.str`required` : undefined,
+ summary: !value.pricing?.summary ? i18n.str`Required` : undefined,
order_price: !value.pricing?.order_price
- ? i18n.str`required`
+ ? i18n.str`Required`
: !parsedPrice
- ? i18n.str`not valid`
+ ? i18n.str`Invalid`
: Amounts.isZero(parsedPrice)
- ? i18n.str`must be greater than 0`
+ ? i18n.str`Must be greater than 0`
: undefined,
}),
payments: undefinedIfEmpty({
@@ -168,58 +166,59 @@ export function CreatePage({
value.payments.refund_deadline,
value.payments.pay_deadline,
) === -1
- ? i18n.str`refund deadline cannot be before pay deadline`
+ ? i18n.str`Refund deadline can't be before pay deadline`
: value.payments.wire_transfer_deadline &&
Duration.cmp(
value.payments.wire_transfer_deadline,
value.payments.refund_deadline,
) === -1
- ? i18n.str`wire transfer deadline cannot be before refund deadline`
+ ? i18n.str`Wire transfer deadline can't be before refund deadline`
: undefined,
pay_deadline: !value.payments?.pay_deadline
- ? i18n.str`required`
+ ? i18n.str`Required`
: value.payments.wire_transfer_deadline &&
Duration.cmp(
value.payments.wire_transfer_deadline,
value.payments.pay_deadline,
) === -1
- ? i18n.str`wire transfer deadline cannot be before pay deadline`
+ ? i18n.str`Wire transfer deadline can't be before pay deadline`
: undefined,
wire_transfer_deadline: !value.payments?.wire_transfer_deadline
- ? i18n.str`required`
+ ? i18n.str`Required`
: undefined,
auto_refund_deadline: !value.payments?.auto_refund_deadline
? undefined
: !value.payments?.refund_deadline
- ? i18n.str`should have a refund deadline`
+ ? i18n.str`Must have a refund deadline`
: Duration.cmp(
value.payments.refund_deadline,
value.payments.auto_refund_deadline,
) == -1
- ? i18n.str`auto refund cannot be after refund deadline`
+ ? i18n.str`Auto refund can't be after refund deadline`
: undefined,
}),
shipping: undefinedIfEmpty({
delivery_date: !value.shipping?.delivery_date
? undefined
: !isFuture(value.shipping.delivery_date)
- ? i18n.str`should be in the future`
+ ? i18n.str`Must be in the future`
: undefined,
}),
- };
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
+ });
+ const hasErrors = errors !== undefined;
const submit = (): void => {
- const order = value as any; //schema.cast(value);
+ const order = value;
+ const price = order.pricing?.order_price as AmountString | undefined;
+ const summary = order.pricing?.summary;
if (!value.payments) return;
if (!value.shipping) return;
+ if (!price || ! summary) return;
const request: TalerMerchantApi.PostOrderRequest = {
order: {
- amount: order.pricing.order_price,
- summary: order.pricing.summary,
+ amount: price,
+ summary: summary,
products: productList,
extra: undefinedIfEmpty(value.extra),
pay_deadline: AbsoluteTime.toProtocolTimestamp(
@@ -340,7 +339,9 @@ export function CreatePage({
const minAgeByProducts = inventoryList.reduce(
(cur, prev) =>
- !prev.product.minimum_age || cur > prev.product.minimum_age ? cur : prev.product.minimum_age,
+ !prev.product.minimum_age || cur > prev.product.minimum_age
+ ? cur
+ : prev.product.minimum_age,
0,
);
@@ -396,8 +397,10 @@ export function CreatePage({
alternative={
allProducts.length > 0 && (
<p>
- {allProducts.length} products with a total price of{" "}
- {totalAsString}.
+ <i18n.Translate>
+ {allProducts.length} products with a total price of{" "}
+ {totalAsString}.
+ </i18n.Translate>
</p>
)
}
@@ -451,7 +454,7 @@ export function CreatePage({
name="pricing.products_price"
label={i18n.str`Total price`}
readonly
- tooltip={i18n.str`total product price added up`}
+ tooltip={i18n.str`Total product price added up`}
/>
<InputCurrency
name="pricing.order_price"
@@ -471,7 +474,7 @@ export function CreatePage({
<InputCurrency
name="pricing.order_price"
label={i18n.str`Order price`}
- tooltip={i18n.str`final order price`}
+ tooltip={i18n.str`Final order price`}
/>
)}
@@ -497,7 +500,7 @@ export function CreatePage({
<InputGroup
name="shipping.delivery_location"
label={i18n.str`Location`}
- tooltip={i18n.str`address where the products will be delivered`}
+ tooltip={i18n.str`Address where the products will be delivered`}
>
<InputLocation name="shipping.delivery_location" />
</InputGroup>
@@ -542,7 +545,7 @@ export function CreatePage({
valueHandler(c);
}}
>
- <i18n.Translate>default</i18n.Translate>
+ <i18n.Translate>Default</i18n.Translate>
</button>
</span>
}
@@ -575,7 +578,7 @@ export function CreatePage({
});
}}
>
- <i18n.Translate>default</i18n.Translate>
+ <i18n.Translate>Default</i18n.Translate>
</button>
</span>
}
@@ -609,7 +612,7 @@ export function CreatePage({
});
}}
>
- <i18n.Translate>default</i18n.Translate>
+ <i18n.Translate>Default</i18n.Translate>
</button>
</span>
}
@@ -731,7 +734,7 @@ export function CreatePage({
e.preventDefault();
}}
>
- add
+ <i18n.Translate>Add</i18n.Translate>
</button>
</div>
</InputGroup>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
index 04f0b2482..37cb4ffdc 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx
@@ -37,6 +37,7 @@ import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
import { CreatePage } from "./CreatePage.js";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
export type Entity = {
request: TalerMerchantApi.PostOrderRequest;
@@ -47,11 +48,12 @@ interface Props {
onConfirm: (id: string) => void;
}
export default function OrderCreate({ onConfirm, onBack }: Props): VNode {
- const { lib } = useSessionContext();
+ const { state, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
- const { state } = useSessionContext();
const detailsResult = useInstanceDetails();
+ // FIXME: if the product list is big the will bring a lot of info
const inventoryResult = useInstanceProducts();
+ const { i18n } = useTranslationContext();
if (!detailsResult) return <Loading />;
if (detailsResult instanceof TalerError) {
@@ -102,16 +104,21 @@ export default function OrderCreate({ onConfirm, onBack }: Props): VNode {
return onConfirm(r.body.order_id);
} else {
setNotif({
- message: "could not create order",
+ message: i18n.str`Could not create order`,
type: "ERROR",
+ description:
+ r.case === HttpStatusCode.Gone
+ ? i18n.str`No more stock for product with id "${r.body.product_id}".`
+ : r.detail.hint,
});
}
})
.catch((error) => {
setNotif({
- message: "could not create order",
+ message: i18n.str`Could not create order`,
type: "ERROR",
- description: error.message,
+ description:
+ error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
index 498ea83e3..2ffb4203d 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
@@ -75,13 +75,13 @@ function ContractTerms({ value }: { value: CT }) {
readonly
name="summary"
label={i18n.str`Summary`}
- tooltip={i18n.str`human-readable description of the whole purchase`}
+ tooltip={i18n.str`Human-readable description of the whole purchase`}
/>
<InputCurrency<CT>
readonly
name="amount"
label={i18n.str`Amount`}
- tooltip={i18n.str`total price for the transaction`}
+ tooltip={i18n.str`Total price for the transaction`}
/>
{value.fulfillment_url && (
<Input<CT>
@@ -95,43 +95,43 @@ function ContractTerms({ value }: { value: CT }) {
readonly
name="max_fee"
label={i18n.str`Max fee`}
- tooltip={i18n.str`maximum total deposit fee accepted by the merchant for this contract`}
+ tooltip={i18n.str`Maximum total deposit fee accepted by the merchant for this contract`}
/>
<InputDate<CT>
readonly
name="timestamp"
label={i18n.str`Created at`}
- tooltip={i18n.str`time when this contract was generated`}
+ tooltip={i18n.str`Time when this contract was generated`}
/>
<InputDate<CT>
readonly
name="refund_deadline"
label={i18n.str`Refund deadline`}
- tooltip={i18n.str`after this deadline has passed no refunds will be accepted`}
+ tooltip={i18n.str`After this deadline has passed no refunds will be accepted`}
/>
<InputDate<CT>
readonly
name="pay_deadline"
label={i18n.str`Payment deadline`}
- tooltip={i18n.str`after this deadline, the merchant won't accept payments for the contract`}
+ tooltip={i18n.str`After this deadline, the merchant won't accept payments for the contract`}
/>
<InputDate<CT>
readonly
name="wire_transfer_deadline"
label={i18n.str`Wire transfer deadline`}
- tooltip={i18n.str`transfer deadline for the exchange`}
+ tooltip={i18n.str`Transfer deadline for the exchange`}
/>
<InputDate<CT>
readonly
name="delivery_date"
label={i18n.str`Delivery date`}
- tooltip={i18n.str`time indicating when the order should be delivered`}
+ tooltip={i18n.str`Time indicating when the order should be delivered`}
/>
{value.delivery_date && (
<InputGroup
name="delivery_location"
label={i18n.str`Location`}
- tooltip={i18n.str`where the order will be delivered`}
+ tooltip={i18n.str`Where the order will be delivered`}
>
<InputLocation name="payments.delivery_location" />
</InputGroup>
@@ -140,13 +140,13 @@ function ContractTerms({ value }: { value: CT }) {
readonly
name="auto_refund"
label={i18n.str`Auto-refund delay`}
- tooltip={i18n.str`how long the wallet should try to get an automatic refund for the purchase`}
+ tooltip={i18n.str`How long the wallet should try to get an automatic refund for the purchase`}
/>
<Input<CT>
readonly
name="extra"
label={i18n.str`Extra info`}
- tooltip={i18n.str`extra data that is only interpreted by the merchant frontend`}
+ tooltip={i18n.str`Extra data that is only interpreted by the merchant frontend`}
/>
</FormProvider>
</InputGroup>
@@ -221,7 +221,7 @@ function ClaimedPage({
<div class="level-item">
<i18n.Translate>Order</i18n.Translate> #{id}
<div class="tag is-info ml-4">
- <i18n.Translate>claimed</i18n.Translate>
+ <i18n.Translate>Claimed</i18n.Translate>
</div>
</div>
</div>
@@ -248,7 +248,7 @@ function ClaimedPage({
>
<p>
<b>
- <i18n.Translate>claimed at</i18n.Translate>:
+ <i18n.Translate>Claimed at</i18n.Translate>:
</b>{" "}
{order.contract_terms.timestamp.t_s === "never"
? "never"
@@ -458,16 +458,16 @@ function PaidPage({
<div class="level-item">
<i18n.Translate>Order</i18n.Translate> #{id}
<div class="tag is-success ml-4">
- <i18n.Translate>paid</i18n.Translate>
+ <i18n.Translate>Paid</i18n.Translate>
</div>
{order.wired ? (
<div class="tag is-success ml-4">
- <i18n.Translate>wired</i18n.Translate>
+ <i18n.Translate>Wired</i18n.Translate>
</div>
) : null}
{order.refunded ? (
<div class="tag is-danger ml-4">
- <i18n.Translate>refunded</i18n.Translate>
+ <i18n.Translate>Refunded</i18n.Translate>
</div>
) : null}
</div>
@@ -487,8 +487,8 @@ function PaidPage({
class="has-tooltip-left"
data-tooltip={
refundable
- ? i18n.str`refund order`
- : i18n.str`not refundable`
+ ? i18n.str`Refund order`
+ : i18n.str`Not refundable`
}
>
<button
@@ -496,7 +496,7 @@ function PaidPage({
disabled={!refundable}
onClick={() => onRefund(id)}
>
- <i18n.Translate>refund</i18n.Translate>
+ <i18n.Translate>Refund</i18n.Translate>
</button>
</span>
</div>
@@ -638,7 +638,7 @@ function UnpaidPage({
</h1>
</div>
<div class="tag is-dark">
- <i18n.Translate>unpaid</i18n.Translate>
+ <i18n.Translate>Unpaid</i18n.Translate>
</div>
</div>
</div>
@@ -656,7 +656,7 @@ function UnpaidPage({
>
<p>
<b>
- <i18n.Translate>pay at</i18n.Translate>:
+ <i18n.Translate>Pay at</i18n.Translate>:
</b>{" "}
<a
href={order.order_status_url}
@@ -668,7 +668,7 @@ function UnpaidPage({
</p>
<p>
<b>
- <i18n.Translate>created at</i18n.Translate>:
+ <i18n.Translate>Created at</i18n.Translate>:
</b>{" "}
{order.creation_time.t_s === "never"
? "never"
@@ -693,13 +693,13 @@ function UnpaidPage({
readonly
name="summary"
label={i18n.str`Summary`}
- tooltip={i18n.str`human-readable description of the whole purchase`}
+ tooltip={i18n.str`Human-readable description of the whole purchase`}
/>
<InputCurrency<Unpaid>
readonly
name="total_amount"
label={i18n.str`Amount`}
- tooltip={i18n.str`total price for the transaction`}
+ tooltip={i18n.str`Total price for the transaction`}
/>
<Input<Unpaid>
name="order_status"
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
index b28e59b29..7d5acd07f 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx
@@ -18,9 +18,7 @@ import {
TalerError,
assertUnreachable,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+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";
@@ -41,8 +39,7 @@ export interface Props {
export default function Update({ oid, onBack }: Props): VNode {
const result = useOrderDetails(oid);
const [notif, setNotif] = useState<Notification | undefined>(undefined);
- const { lib: api } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const { i18n } = useTranslationContext();
@@ -64,7 +61,7 @@ export default function Update({ oid, onBack }: Props): VNode {
);
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
default: {
assertUnreachable(result);
@@ -83,19 +80,27 @@ export default function Update({ oid, onBack }: Props): VNode {
if (state.status !== "loggedIn") {
return;
}
- api.instance
+ lib.instance
.addRefund(state.token, id, value)
- .then(() =>
- setNotif({
- message: i18n.str`refund created successfully`,
- type: "SUCCESS",
- }),
- )
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Refund created successfully`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not create the refund`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) =>
setNotif({
- message: i18n.str`could not create the refund`,
+ message: i18n.str`Could not create the refund`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
}),
);
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
index 4681f9943..fd1c9fa30 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx
@@ -80,7 +80,7 @@ export function ListPage({
isWiredActive,
}: ListPageProps): VNode {
const { i18n } = useTranslationContext();
- const dateTooltip = i18n.str`select date to show nearby orders`;
+ const dateTooltip = i18n.str`Select date to show nearby orders`;
const [pickDate, setPickDate] = useState(false);
const [settings] = usePreference();
@@ -93,7 +93,7 @@ export function ListPage({
<li class={isNotPaidActive}>
<div
class="has-tooltip-right"
- data-tooltip={i18n.str`only show paid orders`}
+ data-tooltip={i18n.str`Only show paid orders`}
>
<a onClick={onShowNotPaid}>
<i18n.Translate>New</i18n.Translate>
@@ -103,7 +103,7 @@ export function ListPage({
<li class={isPaidActive}>
<div
class="has-tooltip-right"
- data-tooltip={i18n.str`only show paid orders`}
+ data-tooltip={i18n.str`Only show paid orders`}
>
<a onClick={onShowPaid}>
<i18n.Translate>Paid</i18n.Translate>
@@ -113,7 +113,7 @@ export function ListPage({
<li class={isRefundedActive}>
<div
class="has-tooltip-right"
- data-tooltip={i18n.str`only show orders with refunds`}
+ data-tooltip={i18n.str`Only show orders with refunds`}
>
<a onClick={onShowRefunded}>
<i18n.Translate>Refunded</i18n.Translate>
@@ -123,7 +123,7 @@ export function ListPage({
<li class={isNotWiredActive}>
<div
class="has-tooltip-left"
- data-tooltip={i18n.str`only show orders where customers paid, but wire payments from payment provider are still pending`}
+ data-tooltip={i18n.str`Only show orders where customers paid, but wire payments from payment provider are still pending`}
>
<a onClick={onShowNotWired}>
<i18n.Translate>Not wired</i18n.Translate>
@@ -133,7 +133,7 @@ export function ListPage({
<li class={isWiredActive}>
<div
class="has-tooltip-left"
- data-tooltip={i18n.str`only show orders where customers paid, but wire payments from payment provider are still pending`}
+ data-tooltip={i18n.str`Only show orders where customers paid, but wire payments from payment provider are still pending`}
>
<a onClick={onShowWired}>
<i18n.Translate>Completed</i18n.Translate>
@@ -143,7 +143,7 @@ export function ListPage({
<li class={isAllActive}>
<div
class="has-tooltip-right"
- data-tooltip={i18n.str`remove all filters`}
+ data-tooltip={i18n.str`Remove all filters`}
>
<a onClick={onShowAll}>
<i18n.Translate>All</i18n.Translate>
@@ -161,7 +161,7 @@ export function ListPage({
<a class="button is-fullwidth" onClick={() => onSelectDate(undefined)}>
<span
class="icon"
- data-tooltip={i18n.str`clear date filter`}
+ data-tooltip={i18n.str`Clear date filter`}
>
<i class="mdi mdi-close" />
</span>
@@ -175,7 +175,7 @@ export function ListPage({
type="text"
readonly
value={!jumpToDate || jumpToDate.t_ms === "never" ? "" : format(jumpToDate.t_ms, dateFormatForSettings(settings))}
- placeholder={i18n.str`date (${dateFormatForSettings(settings)})`}
+ placeholder={i18n.str`Jump to date (${dateFormatForSettings(settings)})`}
onClick={() => {
setPickDate(true);
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
index 7b9691fdd..74bfe7939 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
@@ -80,7 +80,7 @@ export function CardTable({
<div class="card-header-icon" aria-label="more options" />
<div class="card-header-icon" aria-label="more options">
- <span class="has-tooltip-left" data-tooltip={i18n.str`create order`}>
+ <span class="has-tooltip-left" data-tooltip={i18n.str`Create order`}>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
<i class="mdi mdi-plus mdi-36px" />
@@ -137,7 +137,7 @@ function Table({
<div class="table-container">
{onLoadMoreBefore && (
<button class="button is-fullwidth" onClick={onLoadMoreBefore}>
- <i18n.Translate>load first page</i18n.Translate>
+ <i18n.Translate>Load first page</i18n.Translate>
</button>
)}
<table class="table is-striped is-hoverable is-fullwidth">
@@ -211,9 +211,9 @@ function Table({
</table>
{onLoadMoreAfter && (
<button class="button is-fullwidth"
- data-tooltip={i18n.str`load more orders after the last one`}
+ data-tooltip={i18n.str`Load more orders after the last one`}
onClick={onLoadMoreAfter}>
- <i18n.Translate>load next page</i18n.Translate>
+ <i18n.Translate>Load next page</i18n.Translate>
</button>
)}
</div>
@@ -277,20 +277,20 @@ export function RefundModal({
: orderPrice;
const isRefundable = Amounts.isNonZero(totalRefundable);
- const duplicatedText = i18n.str`duplicated`;
+ const duplicatedText = i18n.str`Duplicated`;
const errors: FormErrors<State> = {
- mainReason: !form.mainReason ? i18n.str`required` : undefined,
+ mainReason: !form.mainReason ? i18n.str`Required` : undefined,
description:
!form.description && form.mainReason !== duplicatedText
- ? i18n.str`required`
+ ? i18n.str`Required`
: undefined,
refund: !form.refund
- ? i18n.str`required`
+ ? i18n.str`Required`
: !Amounts.parse(form.refund)
- ? i18n.str`invalid format`
+ ? i18n.str`Invalid`
: Amounts.cmp(totalRefundable, Amounts.parse(form.refund)!) === -1
- ? i18n.str`this value exceed the refundable amount`
+ ? i18n.str`This value exceed the refundable amount`
: undefined,
};
const hasErrors = Object.keys(errors).some(
@@ -335,13 +335,13 @@ export function RefundModal({
<thead>
<tr>
<th>
- <i18n.Translate>date</i18n.Translate>
+ <i18n.Translate>Date</i18n.Translate>
</th>
<th>
- <i18n.Translate>amount</i18n.Translate>
+ <i18n.Translate>Amount</i18n.Translate>
</th>
<th>
- <i18n.Translate>reason</i18n.Translate>
+ <i18n.Translate>Reason</i18n.Translate>
</th>
</tr>
</thead>
@@ -378,7 +378,7 @@ export function RefundModal({
<InputCurrency<State>
name="refund"
label={i18n.str`Refund`}
- tooltip={i18n.str`amount to be refunded`}
+ tooltip={i18n.str`Amount to be refunded`}
>
<i18n.Translate>Max refundable:</i18n.Translate>{" "}
{Amounts.stringify(totalRefundable)}
@@ -389,16 +389,16 @@ export function RefundModal({
values={[
i18n.str`Choose one...`,
duplicatedText,
- i18n.str`requested by the customer`,
- i18n.str`other`,
+ i18n.str`Requested by the customer`,
+ i18n.str`Other`,
]}
- tooltip={i18n.str`why this order is being refunded`}
+ tooltip={i18n.str`Why this order is being refunded`}
/>
{form.mainReason && form.mainReason !== duplicatedText ? (
<Input<State>
label={i18n.str`Description`}
name="description"
- tooltip={i18n.str`more information to give context`}
+ tooltip={i18n.str`More information to give context`}
/>
) : undefined}
</FormProvider>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
index 8a1f85b1c..3d1892b47 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx
@@ -26,9 +26,7 @@ import {
TalerMerchantApi,
assertUnreachable,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
@@ -64,27 +62,26 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode {
const result = useInstanceOrders(filter, (d) =>
setFilter({ ...filter, position: d }),
);
- const { lib } = useSessionContext();
+ const { state, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
- const { state } = useSessionContext();
if (!result) return <Loading />;
if (result instanceof TalerError) {
return <ErrorLoadingMerchant error={result} />;
}
if (result.type === "fail") {
- switch(result.case) {
+ switch (result.case) {
case HttpStatusCode.NotFound: {
return <NotFoundPageOrAdminCreate />;
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
default: {
- assertUnreachable(result)
+ assertUnreachable(result);
}
}
}
@@ -113,8 +110,8 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode {
return resp.type === "ok";
}}
onSelect={onSelect}
- description={i18n.str`jump to order with the given product ID`}
- placeholder={i18n.str`order id`}
+ description={i18n.str`Jump to order with the given product ID`}
+ placeholder={i18n.str`Order id`}
/>
<ListPage
@@ -160,17 +157,25 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode {
onConfirm={(value) => {
lib.instance
.addRefund(state.token, orderToBeRefunded.order_id, value)
- .then(() =>
- setNotif({
- message: i18n.str`refund created successfully`,
- type: "SUCCESS",
- }),
- )
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Refund created successfully`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not create the refund`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) =>
setNotif({
- message: i18n.str`could not create the refund`,
+ message: i18n.str`Could not create the refund`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
}),
)
.then(() => setOrderToBeRefunded(undefined));
@@ -208,7 +213,7 @@ function RefundModalForTable({ id, onConfirm, onCancel }: RefundProps): VNode {
);
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
default: {
assertUnreachable(result);
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
index a16817bab..bd024fb38 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
@@ -35,6 +35,7 @@ import {
import { Input } from "../../../../components/form/Input.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
+import { undefinedIfEmpty } from "../../../../utils/table.js";
type Entity = TalerMerchantApi.OtpDeviceAddDetails;
@@ -53,34 +54,32 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
const [showKey, setShowKey] = useState(false);
- const errors: FormErrors<Entity> = {
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
otp_device_id: !state.otp_device_id
- ? i18n.str`required`
+ ? i18n.str`Required`
: !/[a-zA-Z0-9]*/.test(state.otp_device_id)
- ? i18n.str`no valid. only characters and numbers`
+ ? i18n.str`Invalid. Only characters and numbers`
: undefined,
- otp_algorithm: !state.otp_algorithm ? i18n.str`required` : undefined,
+ otp_algorithm: !state.otp_algorithm ? i18n.str`Required` : undefined,
otp_key: !state.otp_key
- ? i18n.str`required`
+ ? i18n.str`Required`
: !isRfc3548Base32Charset(state.otp_key)
- ? i18n.str`just letters and numbers from 2 to 7`
+ ? i18n.str`Just letters and numbers from 2 to 7`
: state.otp_key.length !== 32
- ? i18n.str`size of the key should be 32`
+ ? i18n.str`Size of the key must be 32`
: undefined,
otp_device_description: !state.otp_device_description
- ? i18n.str`required`
+ ? i18n.str`Required`
: !/[a-zA-Z0-9]*/.test(state.otp_device_description)
- ? i18n.str`no valid. only characters and numbers`
+ ? i18n.str`Invalid. Only characters and numbers`
: undefined,
- };
+ });
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
+ const hasErrors = errors !== undefined;
const submitForm = () => {
if (hasErrors) return Promise.reject();
- return onCreate(state as any);
+ return onCreate(state as TalerMerchantApi.OtpDeviceAddDetails);
};
return (
@@ -112,14 +111,14 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
toStr={(v) => algorithmsNames[v]}
fromStr={(v) => Number(v)}
/>
- {state.otp_algorithm ? (
+ {state.otp_algorithm ? (
<Fragment>
<InputWithAddon<Entity>
expand
name="otp_key"
label={i18n.str`Device key`}
inputType={showKey ? "text" : "password"}
- help="Be sure to be very hard to guess or use the random generator"
+ help={i18n.str`Be sure to be very hard to guess or use the random generator`}
tooltip={i18n.str`Your device need to have exactly the same value`}
fromStr={(v) => v.toUpperCase()}
addonAfterAction={() => {
@@ -136,7 +135,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
}
side={
<button
- data-tooltip={i18n.str`generate random secret key`}
+ data-tooltip={i18n.str`Generate random secret key`}
class="button is-info mr-3"
onClick={(e) => {
setState((s) => ({
@@ -146,7 +145,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
e.preventDefault();
}}
>
- <i18n.Translate>random</i18n.Translate>
+ <i18n.Translate>Random</i18n.Translate>
</button>
}
/>
@@ -165,7 +164,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx
index 8ab0e1f26..adb417a09 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx
@@ -36,14 +36,14 @@ interface Props {
}
export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
- const { lib: api } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
- const [created, setCreated] = useState<TalerMerchantApi.OtpDeviceAddDetails | null>(null)
+ const [created, setCreated] =
+ useState<TalerMerchantApi.OtpDeviceAddDetails | null>(null);
if (created) {
- return <CreatedSuccessfully entity={created} onConfirm={onConfirm} />
+ return <CreatedSuccessfully entity={created} onConfirm={onConfirm} />;
}
return (
@@ -51,16 +51,29 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
<NotificationCard notification={notif} />
<CreatePage
onBack={onBack}
- onCreate={(request: Entity) => {
- return api.instance.addOtpDevice(state.token, request)
- .then((d) => {
- setCreated(request)
+ onCreate={async (request: Entity) => {
+ return lib.instance
+ .addOtpDevice(state.token, request)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Device added successfully`,
+ type: "SUCCESS",
+ });
+ setCreated(request);
+ } else {
+ setNotif({
+ message: i18n.str`Could not add device`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
})
.catch((error) => {
setNotif({
- message: i18n.str`could not create device`,
+ message: i18n.str`Could not add device`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/List.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/List.stories.tsx
deleted file mode 100644
index 49032c80e..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/List.stories.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { FunctionalComponent, h } from "preact";
-import { ListPage as TestedComponent } from "./ListPage.js";
-
-export default {
- title: "Pages/OtpDevices/List",
- component: TestedComponent,
-};
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/ListPage.tsx
deleted file mode 100644
index 8ca0a9c58..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/ListPage.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { TalerMerchantApi } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import { CardTable } from "./Table.js";
-
-export interface Props {
- devices: TalerMerchantApi.OtpDeviceEntry[];
- onLoadMoreBefore?: () => void;
- onLoadMoreAfter?: () => void;
- onCreate: () => void;
- onDelete: (e: TalerMerchantApi.OtpDeviceEntry) => void;
- onSelect: (e: TalerMerchantApi.OtpDeviceEntry) => void;
-}
-
-export function ListPage({
- devices,
- onCreate,
- onDelete,
- onSelect,
- onLoadMoreBefore,
- onLoadMoreAfter,
-}: Props): VNode {
-
- return (
- <section class="section is-main-section">
- <CardTable
- devices={devices.map((o) => ({
- ...o,
- id: String(o.otp_device_id),
- }))}
- onCreate={onCreate}
- onDelete={onDelete}
- onSelect={onSelect}
- onLoadMoreBefore={onLoadMoreBefore}
- onLoadMoreAfter={onLoadMoreAfter}
- />
- </section>
- );
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx
index e4206ff7d..cdba229f6 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/Table.tsx
@@ -59,7 +59,7 @@ export function CardTable({
<div class="card-header-icon" aria-label="more options">
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`add new devices`}
+ data-tooltip={i18n.str`Add new devices`}
>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
@@ -114,10 +114,10 @@ function Table({
{onLoadMoreBefore && (
<button
class="button is-fullwidth"
- data-tooltip={i18n.str`load more devices before the first one`}
+ data-tooltip={i18n.str`Load more devices before the first one`}
onClick={onLoadMoreBefore}
>
- <i18n.Translate>load first page</i18n.Translate>
+ <i18n.Translate>Load first page</i18n.Translate>
</button>
)}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -152,10 +152,10 @@ function Table({
<div class="buttons is-right">
<button
class="button is-danger is-small has-tooltip-left"
- data-tooltip={i18n.str`delete selected devices from the database`}
+ data-tooltip={i18n.str`Delete selected devices from the database`}
onClick={() => onDelete(i)}
>
- Delete
+ <i18n.Translate>Delete</i18n.Translate>
</button>
</div>
</td>
@@ -167,10 +167,10 @@ function Table({
{onLoadMoreAfter && (
<button
class="button is-fullwidth"
- data-tooltip={i18n.str`load more devices after the last one`}
+ data-tooltip={i18n.str`Load more devices after the last one`}
onClick={onLoadMoreAfter}
>
- <i18n.Translate>load next page</i18n.Translate>
+ <i18n.Translate>Load next page</i18n.Translate>
</button>
)}
</div>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx
index b6a077863..d6f2bfe4d 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx
@@ -23,11 +23,9 @@ import {
HttpStatusCode,
TalerError,
TalerMerchantApi,
- assertUnreachable
+ assertUnreachable,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+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";
@@ -38,7 +36,7 @@ import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
-import { ListPage } from "./ListPage.js";
+import { CardTable } from "./Table.js";
interface Props {
onCreate: () => void;
@@ -49,8 +47,7 @@ export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode {
// const [position, setPosition] = useState<string | undefined>(undefined);
const { i18n } = useTranslationContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const result = useInstanceOtpDevices();
if (!result) return <Loading />;
@@ -63,7 +60,7 @@ export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode {
return <NotFoundPageOrAdminCreate />;
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
default: {
assertUnreachable(result);
@@ -75,32 +72,42 @@ export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode {
<Fragment>
<NotificationCard notification={notif} />
- <ListPage
- devices={result.body.otp_devices}
- onLoadMoreBefore={undefined} //result.isFirstPage ? undefined : result.loadFirst}
- onLoadMoreAfter={undefined} //result.isLastPage ? undefined : result.loadNext}
- onCreate={onCreate}
- onSelect={(e) => {
- onSelect(e.otp_device_id);
- }}
- onDelete={(e: TalerMerchantApi.OtpDeviceEntry) => {
- return lib.instance
- .deleteOtpDevice(state.token, e.otp_device_id)
- .then(() =>
- setNotif({
- message: i18n.str`validator delete successfully`,
- type: "SUCCESS",
- }),
- )
- .catch((error) =>
- setNotif({
- message: i18n.str`could not delete the validator`,
- type: "ERROR",
- description: error.message,
- }),
- );
- }}
- />
+ <section class="section is-main-section">
+ <CardTable
+ devices={result.body.otp_devices}
+ onLoadMoreBefore={undefined} //result.isFirstPage ? undefined : result.loadFirst}
+ onLoadMoreAfter={undefined} //result.isLastPage ? undefined : result.loadNext}
+ onCreate={onCreate}
+ onSelect={(e) => {
+ onSelect(e.otp_device_id);
+ }}
+ onDelete={async (e: TalerMerchantApi.OtpDeviceEntry) => {
+ return lib.instance
+ .deleteOtpDevice(state.token, e.otp_device_id)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Device delete successfully`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not delete the device`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
+ .catch((error) =>
+ setNotif({
+ message: i18n.str`Could not delete the device`,
+ type: "ERROR",
+ description: error instanceof Error ? error.message : String(error),
+ }),
+ );
+ }}
+ />
+ </section>
</Fragment>
);
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/UpdatePage.tsx
index 7866b9cd9..68fbf371a 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/UpdatePage.tsx
@@ -19,15 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { randomRfc3548Base32Key, TalerMerchantApi } from "@gnu-taler/taler-util";
+import {
+ randomRfc3548Base32Key,
+ TalerMerchantApi,
+} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
-import {
- FormErrors,
- FormProvider,
-} from "../../../../components/form/FormProvider.js";
+import { FormProvider } from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
@@ -48,15 +48,8 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode {
const [state, setState] = useState<Partial<Entity>>(device);
const [showKey, setShowKey] = useState(false);
- const errors: FormErrors<Entity> = {};
-
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
-
const submitForm = () => {
- if (hasErrors) return Promise.reject();
- return onUpdate(state as any);
+ return onUpdate(state as Entity);
};
return (
@@ -68,7 +61,8 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode {
<div class="level-left">
<div class="level-item">
<span class="is-size-4">
- Device: <b>{device.id}</b>
+ <i18n.Translate>Device:</i18n.Translate>
+ <b>{device.id}</b>
</span>
</div>
</div>
@@ -80,11 +74,7 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode {
<section class="section is-main-section">
<div class="columns">
<div class="column is-four-fifths">
- <FormProvider
- object={state}
- valueHandler={setState}
- errors={errors}
- >
+ <FormProvider object={state} valueHandler={setState}>
<Input<Entity>
name="otp_device_description"
label={i18n.str`Description`}
@@ -107,8 +97,8 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode {
inputType={showKey ? "text" : "password"}
help={
state.otp_key === undefined
- ? "Not modified"
- : "Be sure to be very hard to guess or use the random generator"
+ ? i18n.str`Not modified`
+ : i18n.str`Be sure to be very hard to guess or use the random generator`
}
tooltip={i18n.str`Your device need to have exactly the same value`}
fromStr={(v) => v.toUpperCase()}
@@ -132,25 +122,25 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode {
side={
state.otp_key === undefined ? (
<button
- onClick={(e) => {
+ onClick={() => {
setState((s) => ({ ...s, otp_key: "" }));
}}
class="button"
>
- change key
+ <i18n.Translate>Change key</i18n.Translate>
</button>
) : (
<button
- data-tooltip={i18n.str`generate random secret key`}
+ data-tooltip={i18n.str`Generate random secret key`}
class="button is-info mr-3"
- onClick={(e) => {
+ onClick={() => {
setState((s) => ({
...s,
otp_key: randomRfc3548Base32Key(),
}));
}}
>
- <i18n.Translate>random</i18n.Translate>
+ <i18n.Translate>Random</i18n.Translate>
</button>
)
}
@@ -166,12 +156,8 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode {
</button>
)}
<AsyncButton
- disabled={hasErrors}
- data-tooltip={
- hasErrors
- ? i18n.str`Need to complete marked fields`
- : "confirm operation"
- }
+ disabled={false}
+ data-tooltip={i18n.str`Confirm operation`}
onClick={submitForm}
>
<i18n.Translate>Confirm</i18n.Translate>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx
index 2fe3abaae..8f9997cfb 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx
@@ -58,8 +58,7 @@ export default function UpdateValidator({
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const [keyUpdated, setKeyUpdated] =
useState<TalerMerchantApi.OtpDeviceAddDetails | null>(null);
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const { i18n } = useTranslationContext();
@@ -136,9 +135,9 @@ export default function UpdateValidator({
})
.catch((error) => {
setNotif({
- message: i18n.str`could not update template`,
+ message: i18n.str`Could not update template`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatedSuccessfully.tsx
deleted file mode 100644
index 2b6ebed45..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatedSuccessfully.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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 { h, VNode } from "preact";
-import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js";
-import { Entity } from "./index.js";
-import emptyImage from "../../assets/empty.png";
-
-interface Props {
- entity: Entity;
- onConfirm: () => void;
- onCreateAnother?: () => void;
-}
-
-export function CreatedSuccessfully({
- entity,
- onConfirm,
- onCreateAnother,
-}: Props): VNode {
- return (
- <Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">Image</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <img src={entity.image} style={{ width: 200, height: 200 }} />
- </p>
- </div>
- </div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">Description</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <textarea class="input" readonly value={entity.description} />
- </p>
- </div>
- </div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">Price</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input class="input" readonly value={entity.price} />
- </p>
- </div>
- </div>
- </div>
- </Template>
- );
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx
index 9de5cae78..5352fd792 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx
@@ -34,8 +34,7 @@ interface Props {
onConfirm: () => void;
}
export default function CreateProduct({ onConfirm, onBack }: Props): VNode {
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
@@ -45,13 +44,28 @@ export default function CreateProduct({ onConfirm, onBack }: Props): VNode {
<CreatePage
onBack={onBack}
onCreate={(request: TalerMerchantApi.ProductAddDetail) => {
- return lib.instance.addProduct(state.token, request)
- .then(() => onConfirm())
+ return lib.instance
+ .addProduct(state.token, request)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Product created successfully`,
+ type: "SUCCESS",
+ });
+ onConfirm();
+ } else {
+ setNotif({
+ message: i18n.str`Could not create product`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not create product`,
+ message: i18n.str`Could not create product`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/List.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/List.stories.tsx
index 580a92cdc..ca0b62704 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/List.stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/List.stories.tsx
@@ -43,20 +43,20 @@ function createExample<Props>(
return r;
}
-export const Example = createExample(TestedComponent, {
- instances: [
- {
- id: "orderid",
- description: "description1",
- description_i18n: {} as any,
- image: "",
- price: "TESTKUDOS:10" as AmountString,
- taxes: [],
- total_lost: 10,
- total_sold: 5,
- total_stock: 15,
- unit: "bar",
- address: {},
- },
- ],
-});
+// export const Example = createExample(TestedComponent, {
+// instances: [
+// {
+// id: "orderid",
+// description: "description1",
+// description_i18n: {} as any,
+// image: "",
+// price: "TESTKUDOS:10" as AmountString,
+// taxes: [],
+// total_lost: 10,
+// total_sold: 5,
+// total_stock: 15,
+// unit: "bar",
+// address: {},
+// },
+// ],
+// });
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
index 08d42a8c9..a4e3663ef 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
@@ -75,7 +75,7 @@ export function CardTable({
<div class="card-header-icon" aria-label="more options">
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`add product to inventory`}
+ data-tooltip={i18n.str`Add product to inventory`}
>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
@@ -138,7 +138,7 @@ function Table({
<div class="table-container">
{onLoadMoreBefore && (
<button class="button is-fullwidth" onClick={onLoadMoreBefore}>
- <i18n.Translate>load first page</i18n.Translate>
+ <i18n.Translate>Load first page</i18n.Translate>
</button>
)}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -227,7 +227,7 @@ function Table({
}
style={{ cursor: "pointer" }}
>
- {isFree ? i18n.str`free` : `${i.price} / ${i.unit}`}
+ {isFree ? i18n.str`Free` : `${i.price} / ${i.unit}`}
</td>
<td
onClick={() =>
@@ -268,7 +268,7 @@ function Table({
<div class="buttons is-right">
<span
class="has-tooltip-bottom"
- data-tooltip={i18n.str`go to product update page`}
+ data-tooltip={i18n.str`Go to product update page`}
>
<button
class="button is-small is-success "
@@ -280,7 +280,7 @@ function Table({
</span>
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`remove this product from the database`}
+ data-tooltip={i18n.str`Remove this product from the database`}
>
<button
class="button is-small is-danger"
@@ -315,9 +315,9 @@ function Table({
</table>
{onLoadMoreAfter && (
<button class="button is-fullwidth"
- data-tooltip={i18n.str`load more products after the last one`}
+ data-tooltip={i18n.str`Load more products after the last one`}
onClick={onLoadMoreAfter}>
- <i18n.Translate>load next page</i18n.Translate>
+ <i18n.Translate>Load next page</i18n.Translate>
</button>
)}
</div>
@@ -358,7 +358,7 @@ function FastProductWithInfiniteStockUpdateForm({
<InputCurrency<FastProductUpdate>
name="price"
label={i18n.str`Price`}
- tooltip={i18n.str`update the product with new price`}
+ tooltip={i18n.str`Update the product with new price`}
/>
</FormProvider>
@@ -370,7 +370,7 @@ function FastProductWithInfiniteStockUpdateForm({
</button>
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`update product with new price`}
+ data-tooltip={i18n.str`Update product with new price`}
>
<button
class="button is-info"
@@ -407,7 +407,7 @@ function FastProductWithManagedStockUpdateForm({
const errors: FormErrors<FastProductUpdate> = {
lost:
currentStock + value.incoming < value.lost
- ? `lost cannot be greater that current + incoming (max ${currentStock + value.incoming
+ ? `lost can't be greater that current + incoming (max ${currentStock + value.incoming
})`
: undefined,
};
@@ -428,17 +428,17 @@ function FastProductWithManagedStockUpdateForm({
<InputNumber<FastProductUpdate>
name="incoming"
label={i18n.str`Incoming`}
- tooltip={i18n.str`add more elements to the inventory`}
+ tooltip={i18n.str`Add more elements to the inventory`}
/>
<InputNumber<FastProductUpdate>
name="lost"
label={i18n.str`Lost`}
- tooltip={i18n.str`report elements lost in the inventory`}
+ tooltip={i18n.str`Report elements lost in the inventory`}
/>
<InputCurrency<FastProductUpdate>
name="price"
label={i18n.str`Price`}
- tooltip={i18n.str`new price for the product`}
+ tooltip={i18n.str`New price for the product`}
/>
</FormProvider>
@@ -450,8 +450,8 @@ function FastProductWithManagedStockUpdateForm({
class="has-tooltip-left"
data-tooltip={
hasErrors
- ? i18n.str`the are value with errors`
- : i18n.str`update product with new stock and price`
+ ? i18n.str`The are value with errors`
+ : i18n.str`Update product with new stock and price`
}
>
<button
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
index dc3e73850..3b0fb7ed6 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/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 { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
@@ -31,9 +34,7 @@ import { JumpToElementById } from "../../../../components/form/JumpToElementById
import { NotificationCard } from "../../../../components/menu/index.js";
import { ConfirmModal } from "../../../../components/modal/index.js";
import { useSessionContext } from "../../../../context/session.js";
-import {
- useInstanceProducts
-} from "../../../../hooks/product.js";
+import { useInstanceProducts } from "../../../../hooks/product.js";
import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
@@ -44,15 +45,12 @@ interface Props {
onCreate: () => void;
onSelect: (id: string) => void;
}
-export default function ProductList({
- onCreate,
- onSelect,
-}: Props): VNode {
+export default function ProductList({ onCreate, onSelect }: Props): VNode {
const result = useInstanceProducts();
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
- const [deleting, setDeleting] =
- useState<TalerMerchantApi.ProductDetail & WithId | null>(null);
+ const { state, lib } = useSessionContext();
+ const [deleting, setDeleting] = useState<
+ (TalerMerchantApi.ProductDetail & WithId) | null
+ >(null);
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
@@ -67,7 +65,7 @@ export default function ProductList({
return <NotFoundPageOrAdminCreate />;
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
default: {
assertUnreachable(result);
@@ -85,8 +83,8 @@ export default function ProductList({
return resp.type === "ok";
}}
onSelect={onSelect}
- description={i18n.str`jump to product with the given product ID`}
- placeholder={i18n.str`product id`}
+ description={i18n.str`Jump to product with the given product ID`}
+ placeholder={i18n.str`Product id`}
/>
<CardTable
@@ -96,19 +94,31 @@ export default function ProductList({
onCreate={onCreate}
onUpdate={async (id, prod) => {
try {
- await lib.instance.updateProduct(state.token, id, prod);
- setNotif({
- message: i18n.str`product updated successfully`,
- type: "SUCCESS",
- });
+ const resp = await lib.instance.updateProduct(
+ state.token,
+ id,
+ prod,
+ );
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Product updated successfully`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not update the product`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
} catch (error) {
setNotif({
- message: i18n.str`could not update the product`,
+ message: i18n.str`Could not update the product`,
type: "ERROR",
description: error instanceof Error ? error.message : undefined,
});
}
- return
+ return;
}}
onSelect={(product) => onSelect(product.id)}
onDelete={(prod: TalerMerchantApi.ProductDetail & WithId) =>
@@ -125,14 +135,25 @@ export default function ProductList({
onCancel={() => setDeleting(null)}
onConfirm={async (): Promise<void> => {
try {
- await lib.instance.deleteProduct(state.token, deleting.id);
- setNotif({
- message: i18n.str`Product "${deleting.description}" (ID: ${deleting.id}) has been deleted`,
- type: "SUCCESS",
- });
+ const resp = await lib.instance.deleteProduct(
+ state.token,
+ deleting.id,
+ );
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Product "${deleting.description}" (ID: ${deleting.id}) has been deleted`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not delete the product`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
} catch (error) {
setNotif({
- message: i18n.str`Failed to delete product`,
+ message: i18n.str`Could not delete the product`,
type: "ERROR",
description: error instanceof Error ? error.message : undefined,
});
@@ -141,11 +162,17 @@ export default function ProductList({
}}
>
<p>
- If you delete the product named <b>&quot;{deleting.description}&quot;</b> (ID:{" "}
- <b>{deleting.id}</b>), the stock and related information will be lost
+ <i18n.Translate>
+ If you delete the product named{" "}
+ <b>&quot;{deleting.description}&quot;</b> (ID:{" "}
+ <b>{deleting.id}</b>
+ ), the stock and related information will be lost
+ </i18n.Translate>
</p>
<p class="warning">
- Deleting an product <b>cannot be undone</b>.
+ <i18n.Translate>
+ Deleting an product can't be undone.
+ </i18n.Translate>
</p>
</ConfirmModal>
)}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/update/Update.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/update/Update.stories.tsx
index 7aa93b186..889ea1b26 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/update/Update.stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/update/Update.stories.tsx
@@ -41,34 +41,34 @@ function createExample<Props>(
return r;
}
-export const WithManagedStock = createExample(TestedComponent, {
- product: {
- product_id: "20102-ASDAS-QWE",
- description: "description1",
- description_i18n: {} as any,
- image: "",
- price: "TESTKUDOS:10" as AmountString,
- taxes: [],
- total_lost: 10,
- total_sold: 5,
- total_stock: 15,
- unit: "bar",
- address: {},
- },
-});
+// export const WithManagedStock = createExample(TestedComponent, {
+// product: {
+// product_id: "20102-ASDAS-QWE",
+// description: "description1",
+// description_i18n: {} as any,
+// image: "",
+// price: "TESTKUDOS:10" as AmountString,
+// taxes: [],
+// total_lost: 10,
+// total_sold: 5,
+// total_stock: 15,
+// unit: "bar",
+// address: {},
+// },
+// });
-export const WithInfiniteStock = createExample(TestedComponent, {
- product: {
- product_id: "20102-ASDAS-QWE",
- description: "description1",
- description_i18n: {} as any,
- image: "",
- price: "TESTKUDOS:10" as AmountString,
- taxes: [],
- total_lost: 10,
- total_sold: 5,
- total_stock: -1,
- unit: "bar",
- address: {},
- },
-});
+// export const WithInfiniteStock = createExample(TestedComponent, {
+// product: {
+// product_id: "20102-ASDAS-QWE",
+// description: "description1",
+// description_i18n: {} as any,
+// image: "",
+// price: "TESTKUDOS:10" as AmountString,
+// taxes: [],
+// total_lost: 10,
+// total_sold: 5,
+// total_stock: -1,
+// unit: "bar",
+// address: {},
+// },
+// });
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
index 5e3e58d80..33db7021f 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx
@@ -48,9 +48,8 @@ export default function UpdateProduct({
}: Props): VNode {
const result = useProductDetails(pid);
const [notif, setNotif] = useState<Notification | undefined>(undefined);
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
-
+ const { state, lib } = useSessionContext();
+
const { i18n } = useTranslationContext();
if (!result) return <Loading />;
@@ -71,6 +70,7 @@ export default function UpdateProduct({
}
}
+
return (
<Fragment>
<NotificationCard notification={notif} />
@@ -79,12 +79,27 @@ export default function UpdateProduct({
onBack={onBack}
onUpdate={(data) => {
return lib.instance.updateProduct(state.token, pid, data)
- .then(onConfirm)
+ .then(resp => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Product (ID: ${pid}) has been updated`,
+ type: "SUCCESS",
+ });
+ onConfirm()
+ } else {
+ setNotif({
+ message: i18n.str`Could not update product`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not create product`,
+ message: i18n.str`Could not update product`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
index 336a336ed..c20f8edf0 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
@@ -27,9 +27,7 @@ import {
TalerMerchantApi,
TranslatedString,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
@@ -69,8 +67,7 @@ interface Props {
export function CreatePage({ onCreate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
- const { config } = useSessionContext();
- const { state: session } = useSessionContext();
+ const { config, state: session } = useSessionContext();
const devices = useInstanceOtpDevices();
const [state, setState] = useState<Partial<Entity>>({
@@ -94,28 +91,32 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
const errors: FormErrors<Entity> = {
id: !state.id
- ? i18n.str`should not be empty`
+ ? i18n.str`Required`
: !/[a-zA-Z0-9]*/.test(state.id)
- ? i18n.str`no valid. only characters and numbers`
+ ? i18n.str`Invalid. only characters and numbers`
: undefined,
- description: !state.description ? i18n.str`should not be empty` : undefined,
+ description: !state.description ? i18n.str`Required` : undefined,
amount: !state.amount
- ? state.amount_editable ? undefined : i18n.str`required`
+ ? state.amount_editable
+ ? undefined
+ : i18n.str`Required`
: !parsedPrice
- ? i18n.str`not valid`
+ ? i18n.str`Invalid`
: Amounts.isZero(parsedPrice)
- ? state.amount_editable ? undefined : i18n.str`must be greater than 0`
+ ? state.amount_editable
+ ? undefined
+ : i18n.str`Must be greater than 0`
: undefined,
minimum_age:
state.minimum_age && state.minimum_age < 0
- ? i18n.str`should be greater that 0`
+ ? i18n.str`Must be greater that 0`
: undefined,
pay_duration: !state.pay_duration
- ? i18n.str`can't be empty`
+ ? i18n.str`Required`
: state.pay_duration.d_ms === "forever"
? undefined
: state.pay_duration.d_ms < 1000 //less than one second
- ? i18n.str`to short`
+ ? i18n.str`To short`
: undefined,
};
@@ -125,12 +126,14 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
(k) => (errors as Record<string, unknown>)[k] !== undefined,
);
- const zero = Amounts.stringify(Amounts.zeroOfCurrency(config.currency))
+ const zero = Amounts.stringify(Amounts.zeroOfCurrency(config.currency));
const submitForm = () => {
if (hasErrors) return Promise.reject();
- const contract_amount = state.amount_editable ? undefined : state.amount as AmountString
- const contract_summary = state.summary_editable ? undefined : state.summary
+ const contract_amount = state.amount_editable
+ ? undefined
+ : (state.amount as AmountString);
+ const contract_summary = state.summary_editable ? undefined : state.summary;
const template_contract: TalerMerchantApi.TemplateContractDetails = {
minimum_age: state.minimum_age!,
pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
@@ -140,14 +143,14 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
cList.length > 1 && state.currency_editable
? undefined
: config.currency,
- }
+ };
return onCreate({
template_id: state.id!,
template_description: state.description!,
template_contract,
editable_defaults: {
- amount: !state.amount_editable ? undefined : (state.amount ?? zero),
- summary: !state.summary_editable ? undefined : (state.summary ?? ""),
+ amount: !state.amount_editable ? undefined : state.amount ?? zero,
+ summary: !state.summary_editable ? undefined : state.summary ?? "",
currency:
cList.length === 1 || !state.currency_editable
? undefined
@@ -181,7 +184,10 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<InputWithAddon<Entity>
name="id"
help={
- new URL(`templates/${state.id ?? ""}`, session.backendUrl.href).href
+ new URL(
+ `templates/${state.id ?? ""}`,
+ session.backendUrl.href,
+ ).href
}
label={i18n.str`Identifier`}
tooltip={i18n.str`Name of the template in URLs.`}
@@ -224,7 +230,9 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
tooltip={i18n.str`Allow the user to change currency.`}
/>
<TextField name="sc" label={i18n.str`Supported currencies`}>
- <i18n.Translate>supported currencies: {cList.join(", ")}</i18n.Translate>
+ <i18n.Translate>
+ Supported currencies: {cList.join(", ")}
+ </i18n.Translate>
</TextField>
</Fragment>
)}
@@ -281,7 +289,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx
index 499c7c859..cc40591d4 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx
@@ -28,15 +28,13 @@ import { useSessionContext } from "../../../../context/session.js";
import { Notification } from "../../../../utils/types.js";
import { CreatePage } from "./CreatePage.js";
-export type Entity = TalerMerchantApi.TransferInformation;
interface Props {
onBack?: () => void;
onConfirm: () => void;
}
-export default function CreateTransfer({ onConfirm, onBack }: Props): VNode {
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+export default function CreateTemplate({ onConfirm, onBack }: Props): VNode {
+ const { state, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
@@ -45,14 +43,29 @@ export default function CreateTransfer({ onConfirm, onBack }: Props): VNode {
<NotificationCard notification={notif} />
<CreatePage
onBack={onBack}
- onCreate={(request: TalerMerchantApi.TemplateAddDetails) => {
- return lib.instance.addTemplate(state.token, request)
- .then(() => onConfirm())
+ onCreate={async (request: TalerMerchantApi.TemplateAddDetails) => {
+ return lib.instance
+ .addTemplate(state.token, request)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Template has been created`,
+ type: "SUCCESS",
+ });
+ onConfirm();
+ } else {
+ setNotif({
+ message: i18n.str`Could not create template`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not inform template`,
+ message: i18n.str`Could not create template`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx
deleted file mode 100644
index 66d8a2f7e..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { TalerMerchantApi } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import { CardTable } from "./Table.js";
-
-export interface Props {
- templates: TalerMerchantApi.TemplateEntry[];
- onLoadMoreBefore?: () => void;
- onLoadMoreAfter?: () => void;
- onCreate: () => void;
- onDelete: (e: TalerMerchantApi.TemplateEntry) => void;
- onSelect: (e: TalerMerchantApi.TemplateEntry) => void;
- onNewOrder: (e: TalerMerchantApi.TemplateEntry) => void;
- onQR: (e: TalerMerchantApi.TemplateEntry) => void;
-}
-
-export function ListPage({
- templates,
- onCreate,
- onDelete,
- onSelect,
- onNewOrder,
- onQR,
- onLoadMoreBefore,
- onLoadMoreAfter,
-}: Props): VNode {
-
- return (
- <CardTable
- templates={templates.map((o) => ({
- ...o,
- id: String(o.template_id),
- }))}
- onQR={onQR}
- onCreate={onCreate}
- onDelete={onDelete}
- onSelect={onSelect}
- onNewOrder={onNewOrder}
- onLoadMoreBefore={onLoadMoreBefore}
- onLoadMoreAfter={onLoadMoreAfter}
- />
- );
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
index 4c55bae2a..80c893049 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx
@@ -63,7 +63,7 @@ export function CardTable({
<div class="card-header-icon" aria-label="more options">
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`add new templates`}
+ data-tooltip={i18n.str`Add new templates`}
>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
@@ -124,10 +124,10 @@ function Table({
{onLoadMoreBefore && (
<button
class="button is-fullwidth"
- data-tooltip={i18n.str`load more templates before the first one`}
+ data-tooltip={i18n.str`Load more templates before the first one`}
onClick={onLoadMoreBefore}
>
- <i18n.Translate>load first page</i18n.Translate>
+ <i18n.Translate>Load first page</i18n.Translate>
</button>
)}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -162,21 +162,21 @@ function Table({
<div class="buttons is-right">
<button
class="button is-danger is-small has-tooltip-left"
- data-tooltip={i18n.str`delete selected templates from the database`}
+ data-tooltip={i18n.str`Delete selected templates from the database`}
onClick={() => onDelete(i)}
>
- Delete
+ <i18n.Translate>Delete</i18n.Translate>
</button>
<button
class="button is-info is-small has-tooltip-left"
- data-tooltip={i18n.str`use template to create new order`}
+ data-tooltip={i18n.str`Use template to create new order`}
onClick={() => onNewOrder(i)}
>
- Use template
+ <i18n.Translate>Use template</i18n.Translate>
</button>
<button
class="button is-info is-small has-tooltip-left"
- data-tooltip={i18n.str`create qr code for the template`}
+ data-tooltip={i18n.str`Create qr code for the template`}
onClick={() => onQR(i)}
>
QR
@@ -191,10 +191,10 @@ function Table({
{onLoadMoreAfter && (
<button
class="button is-fullwidth"
- data-tooltip={i18n.str`load more templates after the last one`}
+ data-tooltip={i18n.str`Load more templates after the last one`}
onClick={onLoadMoreAfter}
>
- <i18n.Translate>load next page</i18n.Translate>
+ <i18n.Translate>Load next page</i18n.Translate>
</button>
)}
</div>
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 4fe11bf5c..ad4ed9363 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
@@ -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 { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
@@ -31,13 +34,11 @@ import { JumpToElementById } from "../../../../components/form/JumpToElementById
import { NotificationCard } from "../../../../components/menu/index.js";
import { ConfirmModal } from "../../../../components/modal/index.js";
import { useSessionContext } from "../../../../context/session.js";
-import {
- useInstanceTemplates
-} from "../../../../hooks/templates.js";
+import { useInstanceTemplates } from "../../../../hooks/templates.js";
import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
-import { ListPage } from "./ListPage.js";
+import { CardTable } from "./Table.js";
interface Props {
onCreate: () => void;
@@ -59,20 +60,20 @@ export default function ListTemplates({
const [deleting, setDeleting] =
useState<TalerMerchantApi.TemplateEntry | null>(null);
- 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);
}
}
}
@@ -83,25 +84,21 @@ export default function ListTemplates({
<JumpToElementById
testIfExist={async (id) => {
- const resp = await lib.instance.getTemplateDetails(state.token, id)
- return resp.type === "ok"
+ const resp = await lib.instance.getTemplateDetails(state.token, id);
+ return resp.type === "ok";
}}
onSelect={onSelect}
- description={i18n.str`jump to template with the given template ID`}
- placeholder={i18n.str`template id`}
+ description={i18n.str`Jump to template with the given template ID`}
+ placeholder={i18n.str`Template identification`}
/>
- <ListPage
- // templates={result.body}
- // onLoadMoreBefore={
- // result.isFirstPage ? undefined: result.loadFirst
- // }
- // onLoadMoreAfter={result.isLastPage ? undefined : result.loadNext}
-
- templates={result.body.templates}
+ <CardTable
+ templates={result.body.templates.map((o) => ({
+ ...o,
+ id: String(o.template_id),
+ }))}
onLoadMoreBefore={undefined}
onLoadMoreAfter={undefined}
-
onCreate={onCreate}
onSelect={(e) => {
onSelect(e.template_id);
@@ -113,9 +110,8 @@ export default function ListTemplates({
onQR(e.template_id);
}}
onDelete={(e: TalerMerchantApi.TemplateEntry) => {
- setDeleting(e)
- }
- }
+ setDeleting(e);
+ }}
/>
{deleting && (
@@ -127,11 +123,22 @@ export default function ListTemplates({
onCancel={() => setDeleting(null)}
onConfirm={async (): Promise<void> => {
try {
- await lib.instance.deleteTemplate(state.token, deleting.template_id);
- setNotif({
- message: i18n.str`Template "${deleting.template_description}" (ID: ${deleting.template_id}) has been deleted`,
- type: "SUCCESS",
- });
+ const resp = await lib.instance.deleteTemplate(
+ state.token,
+ deleting.template_id,
+ );
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Template "${deleting.template_description}" (ID: ${deleting.template_id}) has been deleted`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Failed to delete template`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
} catch (error) {
setNotif({
message: i18n.str`Failed to delete template`,
@@ -143,11 +150,18 @@ export default function ListTemplates({
}}
>
<p>
- If you delete the template <b>&quot;{deleting.template_description}&quot;</b> (ID:{" "}
- <b>{deleting.template_id}</b>) you may loose information
+ <i18n.Translate>
+ If you delete the template{" "}
+ <b>&quot;{deleting.template_description}&quot;</b> (ID:{" "}
+ <b>{deleting.template_id}</b>) you may loose information
+ </i18n.Translate>
</p>
<p class="warning">
- Deleting an template <b>cannot be undone</b>.
+ <i18n.Translate>Deleting an template </i18n.Translate>
+ <b>
+ <i18n.Translate>can't be undone</i18n.Translate>
+ </b>
+ .
</p>
</ConfirmModal>
)}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
index 547996ea1..ec3bec184 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
@@ -21,11 +21,9 @@
import {
TalerMerchantApi,
- stringifyPayTemplateUri
+ stringifyPayTemplateUri,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
import { QR } from "../../../../components/exception/QR.js";
import { useSessionContext } from "../../../../context/session.js";
@@ -42,29 +40,6 @@ export function QrPage({ id: templateId, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
const { state } = useSessionContext();
- // const [state, setState] = useState<Partial<Entity>>({
- // amount: contract.amount,
- // summary: contract.summary,
- // });
-
- // const errors: FormErrors<Entity> = {};
-
- // const fixedAmount = !!contract.amount;
- // const fixedSummary = !!contract.summary;
-
- // const templateParams: Record<string, string> = {};
- // if (!fixedAmount) {
- // if (state.amount) {
- // templateParams.amount = state.amount;
- // } else {
- // templateParams.amount = config.currency;
- // }
- // }
-
- // if (!fixedSummary) {
- // templateParams.summary = state.summary ?? "";
- // }
-
const merchantBaseUrl = state.backendUrl.href;
const payTemplateUri = stringifyPayTemplateUri({
@@ -79,7 +54,9 @@ export function QrPage({ id: templateId, onBack }: Props): VNode {
<section id="printThis">
<QR text={payTemplateUri} />
<pre style={{ textAlign: "center" }}>
- <a target="_blank" rel="noreferrer" href={payTemplateUri}>{payTemplateUri}</a>
+ <a target="_blank" rel="noreferrer" href={payTemplateUri}>
+ {payTemplateUri}
+ </a>
</pre>
</section>
@@ -87,35 +64,6 @@ export function QrPage({ id: templateId, onBack }: Props): VNode {
<div class="columns">
<div class="column" />
<div class="column is-four-fifths">
- {/* <p class="is-size-5 mt-5 mb-5">
- <i18n.Translate>
- Here you can specify a default value for fields that are not
- fixed. Default values can be edited by the customer before the
- payment.
- </i18n.Translate>
- </p> */}
-
- <p></p>
- {/* <FormProvider
- object={state}
- valueHandler={setState}
- errors={errors}
- >
- <InputCurrency<Entity>
- name="amount"
- label={i18n.str`Amount`}
- readonly
- tooltip={i18n.str`Amount of the order`}
- />
- <Input<Entity>
- name="summary"
- inputType="multiline"
- readonly
- label={i18n.str`Summary`}
- tooltip={i18n.str`Title of the order to be shown to the customer`}
- />
- </FormProvider> */}
-
<div class="buttons is-right mt-5">
{onBack && (
<button class="button" onClick={onBack}>
@@ -138,18 +86,31 @@ export function QrPage({ id: templateId, onBack }: Props): VNode {
}
function saveAsPDF(name: string): void {
- const printWindow = window.open("", "", "height=400,width=800");
- if (!printWindow) return;
+ // TODO: Look into using media queries in the current page, to print the current page, instead of opening a new window
+
const divContents = document.getElementById("printThis");
if (!divContents) return;
- printWindow.document.write(
- `<html><head><title>Order template for ${name}</title><style>`,
- );
- printWindow.document.write("</style></head><body>&nbsp;</body></html>");
- printWindow.document.close();
- printWindow.document.body.appendChild(divContents.cloneNode(true));
+
+ let dom = `<!DOCTYPE html>
+<html>
+ <head>
+ <title>Order template for ${name}</title>
+ <style>
+ pre > a {
+ text-decoration: none;
+ }
+ </style>
+ </head>
+ <body>
+ ${divContents.outerHTML}
+ </body>
+</html>`;
+ const blobUrl = URL.createObjectURL(new Blob([dom]));
+ const printWindow = window.open(blobUrl, "", "height=400,width=800");
+ if (!printWindow) return;
printWindow.addEventListener("load", () => {
printWindow.print();
- // printWindow.close();
+ printWindow.close();
+ URL.revokeObjectURL(blobUrl);
});
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/index.tsx
index ed809c7b3..93347f616 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/index.tsx
@@ -19,13 +19,16 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { HttpStatusCode, TalerError, TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util";
+import {
+ HttpStatusCode,
+ TalerError,
+ TalerMerchantApi,
+ assertUnreachable,
+} from "@gnu-taler/taler-util";
import { VNode, h } from "preact";
import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
import { Loading } from "../../../../components/exception/loading.js";
-import {
- useTemplateDetails
-} from "../../../../hooks/templates.js";
+import { useTemplateDetails } from "../../../../hooks/templates.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
import { QrPage } from "./QrPage.js";
import { LoginPage } from "../../../login/index.js";
@@ -36,31 +39,27 @@ interface Props {
tid: string;
}
-export default function TemplateQrPage({
- tid,
- onBack,
-}: Props): VNode {
+export default function TemplateQrPage({ tid, onBack }: Props): VNode {
const result = useTemplateDetails(tid);
- 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);
}
}
}
-
return (
- <QrPage contract={result.body.template_contract} id={tid} onBack={onBack} />
+ <QrPage contract={result.body.template_contract} id={tid} onBack={onBack} />
);
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
index 113cf5baa..37f2bf898 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
@@ -68,8 +68,7 @@ interface Props {
export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
- const { config } = useSessionContext();
- const { state: session } = useSessionContext();
+ const { config, state: session } = useSessionContext();
const [state, setState] = useState<Partial<Entity>>({
description: template.template_description,
@@ -116,24 +115,24 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
const parsedPrice = !state.amount ? undefined : Amounts.parse(state.amount);
const errors: FormErrors<Entity> = {
- description: !state.description ? i18n.str`should not be empty` : undefined,
+ description: !state.description ? i18n.str`Required` : undefined,
amount: !state.amount
- ? state.amount_editable ? undefined : i18n.str`required`
+ ? state.amount_editable ? undefined : i18n.str`Required`
: !parsedPrice
- ? i18n.str`not valid`
+ ? i18n.str`Invalid`
: Amounts.isZero(parsedPrice)
- ? state.amount_editable ? undefined : i18n.str`must be greater than 0`
+ ? state.amount_editable ? undefined : i18n.str`Must be greater than 0`
: undefined,
minimum_age:
state.minimum_age && state.minimum_age < 0
- ? i18n.str`should be greater that 0`
+ ? i18n.str`Must be greater that 0`
: undefined,
pay_duration: !state.pay_duration
- ? i18n.str`can't be empty`
+ ? i18n.str`Required`
: state.pay_duration.d_ms === "forever"
? undefined
: state.pay_duration.d_ms < 1000 // less than one second
- ? i18n.str`to short`
+ ? i18n.str`Too short`
: undefined,
};
@@ -237,7 +236,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
/>
<TextField name="sc" label={i18n.str`Supported currencies`}>
<i18n.Translate>
- supported currencies: {cList.join(", ")}
+ Supported currencies: {cList.join(", ")}
</i18n.Translate>
</TextField>
</Fragment>
@@ -295,7 +294,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
index ad804831c..a42a40c63 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx
@@ -19,19 +19,20 @@
* @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";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { useSessionContext } from "../../../../context/session.js";
-import {
- useTemplateDetails,
-} from "../../../../hooks/templates.js";
+import { useTemplateDetails } from "../../../../hooks/templates.js";
import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
@@ -50,27 +51,26 @@ export default function UpdateTemplate({
onConfirm,
onBack,
}: Props): VNode {
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const result = useTemplateDetails(tid);
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
- 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);
}
}
}
@@ -79,16 +79,31 @@ export default function UpdateTemplate({
<Fragment>
<NotificationCard notification={notif} />
<UpdatePage
- template={{...result.body, id: tid}}
+ template={{ ...result.body, id: tid }}
onBack={onBack}
onUpdate={(data) => {
- return lib.instance.updateTemplate(state.token, tid, data)
- .then(onConfirm)
+ return lib.instance
+ .updateTemplate(state.token, tid, data)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Template (ID: ${tid}) has been updated`,
+ type: "SUCCESS",
+ });
+ onConfirm();
+ } else {
+ setNotif({
+ message: i18n.str`Could not update template`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not update template`,
+ message: i18n.str`Could not update template`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx
index 00cb2b827..ebdb1a6fa 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx
@@ -82,23 +82,26 @@ export default function TemplateUsePage({
onCreateOrder={(
request: TalerMerchantApi.UsingTemplateDetails,
) => {
-
return lib.instance.useTemplateCreateOrder(tid, request)
.then((res) => {
if (res.type === "ok") {
onOrderCreated(res.body.order_id)
} else {
setNotif({
- message: i18n.str`could not create order from template`,
+ message: i18n.str`Could not create order from template`,
type: "ERROR",
+ description:
+ res.case === HttpStatusCode.Gone
+ ? i18n.str`No more stock for product with id "${res.body.product_id}".`
+ : res.detail.hint,
});
}
})
.catch((error) => {
setNotif({
- message: i18n.str`could not create order from template`,
+ message: i18n.str`Could not create order from template`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
index d718ffb69..be23299ff 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
@@ -28,6 +28,7 @@ import { Input } from "../../../components/form/Input.js";
import { NotificationCard } from "../../../components/menu/index.js";
import { useSessionContext } from "../../../context/session.js";
import { AccessToken, createRFC8959AccessTokenPlain } from "@gnu-taler/taler-util";
+import { undefinedIfEmpty } from "../../../utils/table.js";
interface Props {
hasToken: boolean | undefined;
@@ -50,25 +51,23 @@ export function DetailPage({
});
const { i18n } = useTranslationContext();
- const errors = {
+ const errors = undefinedIfEmpty({
old_token:
hasToken && !form.old_token
- ? i18n.str`you need your access token to perform the operation`
+ ? i18n.str`You need your access token to perform the operation`
: undefined,
new_token: !form.new_token
- ? i18n.str`cannot be empty`
+ ? i18n.str`Required`
: form.new_token === form.old_token
- ? i18n.str`cannot be the same as the old token`
+ ? i18n.str`Can't be the same as the old token`
: undefined,
repeat_token:
form.new_token !== form.repeat_token
- ? i18n.str`is not the same`
+ ? i18n.str`Is not the same`
: undefined,
- };
+ });
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as Record<string, unknown>)[k] !== undefined,
- );
+ const hasErrors = errors !== undefined;
const { state } = useSessionContext();
@@ -120,7 +119,7 @@ export function DetailPage({
<Input<State>
name="old_token"
label={i18n.str`Current access token`}
- tooltip={i18n.str`access token currently in use`}
+ tooltip={i18n.str`Access token currently in use`}
inputType="password"
/>
<p>
@@ -149,13 +148,13 @@ export function DetailPage({
<Input<State>
name="new_token"
label={i18n.str`New access token`}
- tooltip={i18n.str`next access token to be used`}
+ tooltip={i18n.str`Next access token to be used`}
inputType="password"
/>
<Input<State>
name="repeat_token"
label={i18n.str`Repeat access token`}
- tooltip={i18n.str`confirm the same access token`}
+ tooltip={i18n.str`Confirm the same access token`}
inputType="password"
/>
</Fragment>
@@ -171,7 +170,7 @@ export function DetailPage({
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
index c23e5be17..def8ec821 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
@@ -18,9 +18,7 @@ import {
TalerError,
assertUnreachable,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+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,8 +38,7 @@ interface Props {
export default function Token({ onChange, onCancel }: Props): VNode {
const { i18n } = useTranslationContext();
- const { lib } = useSessionContext();
- const { logIn } = useSessionContext();
+ const { logIn, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const result = useInstanceDetails();
@@ -89,13 +86,11 @@ export default function Token({ onChange, onCancel }: Props): VNode {
});
}
} catch (error) {
- if (error instanceof Error) {
- return setNotif({
- message: i18n.str`Failed to clear token`,
- type: "ERROR",
- description: error.message,
- });
- }
+ return setNotif({
+ message: i18n.str`Failed to clear token`,
+ type: "ERROR",
+ description: error instanceof Error ? error.message : String(error),
+ });
}
}}
onNewToken={async (currentToken, newToken): Promise<void> => {
@@ -137,13 +132,11 @@ export default function Token({ onChange, onCancel }: Props): VNode {
});
}
} catch (error) {
- if (error instanceof Error) {
return setNotif({
message: i18n.str`Failed to set new token`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
- }
}
}}
/>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/create/index.tsx
index 32c92cab0..34361fd56 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/create/index.tsx
@@ -23,12 +23,12 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { NotificationCard } from "../../../../components/menu/index.js";
-import { MerchantBackend } from "../../../../declaration.js";
import { Notification } from "../../../../utils/types.js";
import { useSessionContext } from "../../../../context/session.js";
import { CreatePage } from "./CreatePage.js";
+import { TalerMerchantApi } from "@gnu-taler/taler-util";
-export type Entity = MerchantBackend.TokenFamilies.TokenFamilyAddDetail;
+export type Entity = TalerMerchantApi.TokenFamilyCreateRequest;
interface Props {
onBack?: () => void;
onConfirm: () => void;
@@ -36,8 +36,7 @@ interface Props {
export default function CreateTokenFamily({ onConfirm, onBack }: Props): VNode {
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
return (
<Fragment>
@@ -46,12 +45,26 @@ export default function CreateTokenFamily({ onConfirm, onBack }: Props): VNode {
onBack={onBack}
onCreate={(request) => {
return lib.instance.createTokenFamily(state.token, request)
- .then(() => onConfirm())
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Token familty created successfully`,
+ type: "SUCCESS",
+ });
+ onConfirm();
+ } else {
+ setNotif({
+ message: i18n.str`Could not create token family`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not create token family`,
+ message: i18n.str`Could not create token family`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/Table.tsx
index 1af8a1192..28823e8a1 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/Table.tsx
@@ -23,7 +23,6 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { StateUpdater, useState } from "preact/hooks";
import { format } from "date-fns";
-import { MerchantBackend } from "../../../../declaration.js";
import { TalerMerchantApi } from "@gnu-taler/taler-util";
type Entity = TalerMerchantApi.TokenFamilySummary;
@@ -34,7 +33,7 @@ interface Props {
onSelect: (tokenFamily: Entity) => void;
onUpdate: (
slug: string,
- data: MerchantBackend.TokenFamilies.TokenFamilyPatchDetail,
+ data: TalerMerchantApi.TokenFamilyUpdateRequest,
) => Promise<void>;
onCreate: () => void;
selected?: boolean;
@@ -63,7 +62,7 @@ export function CardTable({
<div class="card-header-icon" aria-label="more options">
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`add token family`}
+ data-tooltip={i18n.str`Add token family`}
>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
@@ -100,7 +99,7 @@ interface TableProps {
onSelect: (tokenFamily: Entity) => void;
onUpdate: (
slug: string,
- data: MerchantBackend.TokenFamilies.TokenFamilyPatchDetail,
+ data: TalerMerchantApi.TokenFamilyUpdateRequest,
) => Promise<void>;
onDelete: (tokenFamily: Entity) => void;
rowSelectionHandler: StateUpdater<string | undefined>;
@@ -111,7 +110,6 @@ function Table({
rowSelectionHandler,
instances,
onSelect,
- onUpdate,
onDelete,
}: TableProps): VNode {
const { i18n } = useTranslationContext();
@@ -191,7 +189,7 @@ function Table({
<div class="buttons is-right">
<span
class="has-tooltip-bottom"
- data-tooltip={i18n.str`go to token family update page`}
+ data-tooltip={i18n.str`Go to token family update page`}
>
<button
class="button is-small is-success "
@@ -203,7 +201,7 @@ function Table({
</span>
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`remove this token family from the database`}
+ data-tooltip={i18n.str`Remove this token family from the database`}
>
<button
class="button is-small is-danger"
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/index.tsx
index 58d071ffc..f01d0b0d7 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/index.tsx
@@ -20,38 +20,32 @@
*/
import {
- ErrorType,
- HttpError,
- useTranslationContext,
-} from "@gnu-taler/web-util/browser";
-import { h, VNode } from "preact";
+ HttpStatusCode,
+ TalerError,
+ TalerMerchantApi,
+ assertUnreachable,
+} from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
import { useState } from "preact/hooks";
+import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
-import { MerchantBackend } from "../../../../declaration.js";
-import {
- useInstanceTokenFamilies,
-} from "../../../../hooks/tokenfamily.js";
-import { Notification } from "../../../../utils/types.js";
-import { CardTable } from "./Table.js";
-import { HttpStatusCode, TalerError, TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util";
-import { useSessionContext } from "../../../../context/session.js";
-import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
import { ConfirmModal } from "../../../../components/modal/index.js";
+import { useSessionContext } from "../../../../context/session.js";
+import { useInstanceTokenFamilies } from "../../../../hooks/tokenfamily.js";
+import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
+import { CardTable } from "./Table.js";
interface Props {
onUnauthorized: () => VNode;
onNotFound: () => VNode;
onCreate: () => void;
onSelect: (slug: string) => void;
- onLoadError: (e: HttpError<MerchantBackend.ErrorDetail>) => VNode;
}
-export default function TokenFamilyList({
- onCreate,
- onSelect,
-}: Props): VNode {
+export default function TokenFamilyList({ onCreate, onSelect }: Props): VNode {
const result = useInstanceTokenFamilies();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { lib, state } = useSessionContext();
@@ -70,7 +64,7 @@ export default function TokenFamilyList({
return <NotFoundPageOrAdminCreate />;
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
default: {
assertUnreachable(result);
@@ -87,14 +81,26 @@ export default function TokenFamilyList({
onCreate={onCreate}
onUpdate={async (slug, fam) => {
try {
- await lib.instance.updateTokenFamily(state.token, slug, fam);
- setNotif({
- message: i18n.str`token family updated successfully`,
- type: "SUCCESS",
- });
+ const resp = await lib.instance.updateTokenFamily(
+ state.token,
+ slug,
+ fam,
+ );
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Token family updated successfully`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not update the token family`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
} catch (error) {
setNotif({
- message: i18n.str`could not update the token family`,
+ message: i18n.str`Could not update the token family`,
type: "ERROR",
description: error instanceof Error ? error.message : undefined,
});
@@ -114,11 +120,22 @@ export default function TokenFamilyList({
onCancel={() => setDeleting(null)}
onConfirm={async (): Promise<void> => {
try {
- await lib.instance.deleteTokenFamily(state.token, deleting.slug);
- setNotif({
- message: i18n.str`Token family "${deleting.name}" (SLUG: ${deleting.slug}) has been deleted`,
- type: "SUCCESS",
- });
+ const resp = await lib.instance.deleteTokenFamily(
+ state.token,
+ deleting.slug,
+ );
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Token family "${deleting.name}" (SLUG: ${deleting.slug}) has been deleted`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Failed to delete token family`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
} catch (error) {
setNotif({
message: i18n.str`Failed to delete token family`,
@@ -130,11 +147,20 @@ export default function TokenFamilyList({
}}
>
<p>
- If you delete the <b>&quot;{deleting.name}&quot;</b> token family (Slug:{" "}
- <b>{deleting.slug}</b>), all issued tokens will become invalid.
+ <i18n.Translate>
+ If you delete the <b>&quot;{deleting.name}&quot;</b> token family
+ (Slug: <b>{deleting.slug}</b>), all issued tokens will become
+ invalid.
+ </i18n.Translate>
</p>
<p class="warning">
- Deleting a token family <b>cannot be undone</b>.
+ <i18n.Translate>
+ Deleting a token family{" "}
+ <b>
+ <i18n.Translate>can't be undone</i18n.Translate>
+ </b>
+ .
+ </i18n.Translate>
</p>
</ConfirmModal>
)}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/UpdatePage.tsx
index 5641d261b..382821f8c 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/UpdatePage.tsx
@@ -23,13 +23,12 @@ import { Duration, TalerMerchantApi } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { h } from "preact";
import { useState } from "preact/hooks";
-import * as yup from "yup";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import { FormErrors, FormProvider } from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
import { InputDate } from "../../../../components/form/InputDate.js";
import { InputDuration } from "../../../../components/form/InputDuration.js";
-import { TokenFamilyUpdateSchema } from "../../../../schemas/index.js";
+import { undefinedIfEmpty } from "../../../../utils/table.js";
type Entity = Omit<TalerMerchantApi.TokenFamilyUpdateRequest, "duration"> & {
duration: Duration,
@@ -52,25 +51,16 @@ function convert(from: TalerMerchantApi.TokenFamilyUpdateRequest) {
export function UpdatePage({ onUpdate, onBack, tokenFamily }: Props) {
const [value, valueHandler] = useState<Partial<Entity>>(convert(tokenFamily));
- let errors: FormErrors<Entity> = {};
-
- try {
- TokenFamilyUpdateSchema.validateSync(value, {
- abortEarly: false,
- });
- } catch (err) {
- if (err instanceof yup.ValidationError) {
- const yupErrors = err.inner as yup.ValidationError[];
- errors = yupErrors.reduce(
- (prev, cur) =>
- !cur.path ? prev : { ...prev, [cur.path]: cur.message },
- {},
- );
- }
- }
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
+ const { i18n } = useTranslationContext();
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
+ name: !value.name ? i18n.str`Required` : undefined,
+ description: !value.description ? i18n.str`Required` : undefined,
+ valid_after: !value.valid_after ? i18n.str`Required` : undefined,
+ valid_before: !value.valid_before ? i18n.str`Required` : undefined,
+ duration: !value.duration ? i18n.str`Required` : undefined,
+ });
+
+ const hasErrors = errors !== undefined;
const submitForm = () => {
if (hasErrors) return Promise.reject();
@@ -84,7 +74,6 @@ export function UpdatePage({ onUpdate, onBack, tokenFamily }: Props) {
return onUpdate(result);
}
- const { i18n } = useTranslationContext();
return (
<div>
@@ -95,7 +84,7 @@ export function UpdatePage({ onUpdate, onBack, tokenFamily }: Props) {
<div class="level-left">
<div class="level-item">
<span class="is-size-4">
- Token Family: <b>{tokenFamily.name}</b>
+ <i18n.Translate>Token Family: <b>{tokenFamily.name}</b></i18n.Translate>
</span>
</div>
</div>
@@ -117,30 +106,30 @@ export function UpdatePage({ onUpdate, onBack, tokenFamily }: Props) {
name="name"
inputType="text"
label={i18n.str`Name`}
- tooltip={i18n.str`user-readable token family name`}
+ tooltip={i18n.str`User-readable token family name`}
/>
<Input<Entity>
name="description"
inputType="multiline"
label={i18n.str`Description`}
- tooltip={i18n.str`token family description for customers`}
+ tooltip={i18n.str`Token family description for customers`}
/>
<InputDate<Entity>
name="valid_after"
label={i18n.str`Valid After`}
- tooltip={i18n.str`token family can issue tokens after this date`}
+ tooltip={i18n.str`Token family can issue tokens after this date`}
withTimestampSupport
/>
<InputDate<Entity>
name="valid_before"
label={i18n.str`Valid Before`}
- tooltip={i18n.str`token family can issue tokens until this date`}
+ tooltip={i18n.str`Token family can issue tokens until this date`}
withTimestampSupport
/>
<InputDuration<Entity>
name="duration"
label={i18n.str`Duration`}
- tooltip={i18n.str`validity duration of a issued token`}
+ tooltip={i18n.str`Validity duration of a issued token`}
withForever
/>
</FormProvider>
@@ -156,7 +145,7 @@ export function UpdatePage({ onUpdate, onBack, tokenFamily }: Props) {
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/index.tsx
index 068235e14..287cc7a51 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/index.tsx
@@ -20,22 +20,25 @@
*/
import {
- ErrorType,
- HttpError,
- useTranslationContext,
+ HttpStatusCode,
+ TalerError,
+ TalerMerchantApi,
+ assertUnreachable,
+} from "@gnu-taler/taler-util";
+import {
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
+import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
+import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
-import { Notification } from "../../../../utils/types.js";
-import { UpdatePage } from "./UpdatePage.js";
-import { HttpStatusCode, TalerError, TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util";
-import { useTokenFamilyDetails } from "../../../../hooks/tokenfamily.js";
import { useSessionContext } from "../../../../context/session.js";
-import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js";
+import { useTokenFamilyDetails } from "../../../../hooks/tokenfamily.js";
+import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
+import { UpdatePage } from "./UpdatePage.js";
type Entity = TalerMerchantApi.TokenFamilyUpdateRequest;
@@ -65,7 +68,7 @@ export default function UpdateTokenFamily({
return <NotFoundPageOrAdminCreate />;
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
default: {
assertUnreachable(result);
@@ -89,13 +92,28 @@ export default function UpdateTokenFamily({
tokenFamily={family}
onBack={onBack}
onUpdate={(data) => {
- return lib.instance.updateTokenFamily(state.token, slug, data)
- .then(onConfirm)
+ return lib.instance
+ .updateTokenFamily(state.token, slug, data)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Token familty updated successfully`,
+ type: "SUCCESS",
+ });
+ onConfirm();
+ } else {
+ setNotif({
+ message: i18n.str`Could not update token family`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not update token family`,
+ message: i18n.str`Could not update token family`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
index 91aabe58e..d3a1d7832 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
@@ -35,6 +35,7 @@ import {
CROCKFORD_BASE32_REGEX,
URL_REGEX,
} from "../../../../utils/constants.js";
+import { undefinedIfEmpty } from "../../../../utils/table.js";
type Entity = TalerMerchantApi.TransferInformation;
@@ -54,26 +55,24 @@ export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
credit_amount: `` as AmountString,
});
- const errors: FormErrors<Entity> = {
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
wtid: !state.wtid
- ? i18n.str`cannot be empty`
+ ? i18n.str`Required`
: !CROCKFORD_BASE32_REGEX.test(state.wtid)
- ? i18n.str`check the id, does not look valid`
- : state.wtid.length !== 52
- ? i18n.str`should have 52 characters, current ${state.wtid.length}`
- : undefined,
- payto_uri: !state.payto_uri ? i18n.str`cannot be empty` : undefined,
- credit_amount: !state.credit_amount ? i18n.str`cannot be empty` : undefined,
+ ? i18n.str`Check the id, does not look valid`
+ : state.wtid.length !== 52
+ ? i18n.str`Must have 52 characters, current ${state.wtid.length}`
+ : undefined,
+ payto_uri: !state.payto_uri ? i18n.str`Required` : undefined,
+ credit_amount: !state.credit_amount ? i18n.str`Required` : undefined,
exchange_url: !state.exchange_url
- ? i18n.str`cannot be empty`
+ ? i18n.str`Required`
: !URL_REGEX.test(state.exchange_url)
- ? i18n.str`URL doesn't have the right format`
- : undefined,
- };
+ ? i18n.str`URL doesn't have the right format`
+ : undefined,
+ });
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
+ const hasErrors = errors !== undefined;
const submitForm = () => {
if (hasErrors) return Promise.reject();
@@ -102,7 +101,7 @@ export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
name="wtid"
label={i18n.str`Wire transfer ID`}
help=""
- tooltip={i18n.str`unique identifier of the wire transfer used by the exchange, must be 52 characters long`}
+ tooltip={i18n.str`Unique identifier of the wire transfer used by the exchange, must be 52 characters long`}
/>
<Input<Entity>
name="exchange_url"
@@ -128,7 +127,7 @@ export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx
index 428476337..2f295fcc8 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx
@@ -38,8 +38,7 @@ interface Props {
}
export default function CreateTransfer({ onConfirm, onBack }: Props): VNode {
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
const instance = useInstanceBankAccounts();
@@ -57,12 +56,26 @@ export default function CreateTransfer({ onConfirm, onBack }: Props): VNode {
onCreate={(request: TalerMerchantApi.TransferInformation) => {
return lib.instance
.informWireTransfer(state.token, request)
- .then(() => onConfirm())
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Wire transfer informed successfully`,
+ type: "SUCCESS",
+ });
+ onConfirm()
+ } else {
+ setNotif({
+ message: i18n.str`Could not inform transfer`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not inform transfer`,
+ message: i18n.str`Could not inform transfer`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
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 927e36cf7..f80c0f53f 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
@@ -81,7 +81,7 @@ export function ListPage({
return d
}}
placeholder={i18n.str`All accounts`}
- tooltip={i18n.str`filter by account address`}
+ tooltip={i18n.str`Filter by account address`}
/>
</FormProvider>
</div>
@@ -92,7 +92,7 @@ export function ListPage({
<li class={isAllTransfers ? "is-active" : ""}>
<div
class="has-tooltip-right"
- data-tooltip={i18n.str`remove all filters`}
+ data-tooltip={i18n.str`Remove all filters`}
>
<a onClick={onShowAll}>
<i18n.Translate>All</i18n.Translate>
@@ -102,7 +102,7 @@ export function ListPage({
<li class={isVerifiedTransfers ? "is-active" : ""}>
<div
class="has-tooltip-right"
- data-tooltip={i18n.str`only show wire transfers confirmed by the merchant`}
+ data-tooltip={i18n.str`Only show wire transfers confirmed by the merchant`}
>
<a onClick={onShowVerified}>
<i18n.Translate>Verified</i18n.Translate>
@@ -112,7 +112,7 @@ export function ListPage({
<li class={isNonVerifiedTransfers ? "is-active" : ""}>
<div
class="has-tooltip-right"
- data-tooltip={i18n.str`only show wire transfers claimed by the exchange`}
+ data-tooltip={i18n.str`Only show wire transfers claimed by the exchange`}
>
<a onClick={onShowUnverified}>
<i18n.Translate>Unverified</i18n.Translate>
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 5687d5e57..29ff69f55 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
@@ -61,7 +61,7 @@ export function CardTable({
<div class="card-header-icon" aria-label="more options">
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`add new transfer`}
+ data-tooltip={i18n.str`Add new transfer`}
>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
@@ -114,10 +114,10 @@ function Table({
{onLoadMoreBefore && (
<button
class="button is-fullwidth"
- data-tooltip={i18n.str`load more transfers before the first one`}
+ data-tooltip={i18n.str`Load more transfers before the first one`}
onClick={onLoadMoreBefore}
>
- <i18n.Translate>load first page</i18n.Translate>
+ <i18n.Translate>Load first page</i18n.Translate>
</button>
)}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -163,7 +163,7 @@ function Table({
{i.verified !== true ? (
<button
class="button is-danger is-small has-tooltip-left"
- data-tooltip={i18n.str`delete selected transfer from the database`}
+ data-tooltip={i18n.str`Delete selected transfer from the database`}
onClick={() => onDelete(i)}
>
<i18n.Translate>Delete</i18n.Translate>
@@ -178,10 +178,10 @@ function Table({
{onLoadMoreAfter && (
<button
class="button is-fullwidth"
- data-tooltip={i18n.str`load more transfers after the last one`}
+ data-tooltip={i18n.str`Load more transfers after the last one`}
onClick={onLoadMoreAfter}
>
- <i18n.Translate>load next page</i18n.Translate>
+ <i18n.Translate>Load next page</i18n.Translate>
</button>
)}
</div>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/update/index.tsx
deleted file mode 100644
index 719f99209..000000000
--- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/update/index.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { h, VNode } from "preact";
-
-export default function UpdateTransfer(): VNode {
- return <div>order transfer page</div>;
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
index cde58967f..c7d38cf32 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
@@ -37,7 +37,6 @@ export type Entity = Omit<Omit<TalerMerchantApi.InstanceReconfigurationMessage,
default_wire_transfer_delay: Duration,
};
-//TalerMerchantApi.InstanceAuthConfigurationMessage
interface Props {
onUpdate: (d: TalerMerchantApi.InstanceReconfigurationMessage) => void;
selected: TalerMerchantApi.QueryInstancesResponse;
@@ -69,40 +68,38 @@ export function UpdatePage({
const { i18n } = useTranslationContext();
- const errors: FormErrors<Entity> = {
- name: !value.name ? i18n.str`required` : undefined,
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
+ name: !value.name ? i18n.str`Required` : undefined,
user_type: !value.user_type
- ? i18n.str`required`
+ ? i18n.str`Required`
: value.user_type !== "business" && value.user_type !== "individual"
- ? i18n.str`should be business or individual`
+ ? i18n.str`Must be business or individual`
: undefined,
default_pay_delay: !value.default_pay_delay
- ? i18n.str`required`
+ ? i18n.str`Required`
: !!value.default_wire_transfer_delay &&
value.default_wire_transfer_delay.d_ms !== "forever" &&
value.default_pay_delay.d_ms !== "forever" &&
value.default_pay_delay.d_ms > value.default_wire_transfer_delay.d_ms ?
- i18n.str`pay delay can't be greater than wire transfer delay` : undefined,
+ i18n.str`Pay delay can't be greater than wire transfer delay` : undefined,
default_wire_transfer_delay: !value.default_wire_transfer_delay
- ? i18n.str`required`
+ ? i18n.str`Required`
: undefined,
address: undefinedIfEmpty({
address_lines:
value.address?.address_lines && value.address?.address_lines.length > 7
- ? i18n.str`max 7 lines`
+ ? i18n.str`Max 7 lines`
: undefined,
}),
jurisdiction: undefinedIfEmpty({
address_lines:
value.address?.address_lines && value.address?.address_lines.length > 7
- ? i18n.str`max 7 lines`
+ ? i18n.str`Max 7 lines`
: undefined,
}),
- };
+ });
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
+ const hasErrors = errors !== undefined;
const submit = async (): Promise<void> => {
const { default_pay_delay, default_wire_transfer_delay, ...rest } = value as Required<Entity>;
@@ -111,7 +108,7 @@ export function UpdatePage({
default_wire_transfer_delay: Duration.toTalerProtocolDuration(default_wire_transfer_delay),
...rest,
}
- await onUpdate(result);
+ onUpdate(result);
};
// const [active, setActive] = useState(false);
@@ -159,7 +156,7 @@ export function UpdatePage({
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
disabled={hasErrors}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
index 9da7f7efb..2b2327eb2 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
@@ -103,11 +103,11 @@ function CommonUpdate(
}
return updateInstance(state.token, d)
.then(onConfirm)
- .catch((error: Error) =>
+ .catch((error) =>
setNotif({
message: i18n.str`Failed to update instance`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
}),
);
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
index 8792aabea..234295174 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
@@ -30,6 +30,7 @@ import {
import { Input } from "../../../../components/form/Input.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { TalerMerchantApi } from "@gnu-taler/taler-util";
+import { undefinedIfEmpty } from "../../../../utils/table.js";
type Entity = TalerMerchantApi.WebhookAddDetails;
@@ -45,26 +46,26 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
const [state, setState] = useState<Partial<Entity>>({});
- const errors: FormErrors<Entity> = {
- webhook_id: !state.webhook_id ? i18n.str`required` : undefined,
- event_type: !state.event_type ? i18n.str`required`
- : state.event_type !== "pay" && state.event_type !== "refund" ? i18n.str`it should be "pay" or "refund"`
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
+ webhook_id: !state.webhook_id ? i18n.str`Required` : undefined,
+ event_type: !state.event_type
+ ? i18n.str`Required`
+ : state.event_type !== "pay" && state.event_type !== "refund"
+ ? i18n.str`Must be "pay" or "refund"`
: undefined,
http_method: !state.http_method
- ? i18n.str`required`
+ ? i18n.str`Required`
: !validMethod.includes(state.http_method)
- ? i18n.str`should be one of '${validMethod.join(", ")}'`
+ ? i18n.str`Must be one of '${validMethod.join(", ")}'`
: undefined,
- url: !state.url ? i18n.str`required` : undefined,
- };
+ url: !state.url ? i18n.str`Required` : undefined,
+ });
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
+ const hasErrors = errors !== undefined;
const submitForm = () => {
if (hasErrors) return Promise.reject();
- return onCreate(state as any);
+ return onCreate(state as TalerMerchantApi.WebhookAddDetails);
};
return (
@@ -88,8 +89,8 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
label={i18n.str`Event`}
values={[
i18n.str`Choose one...`,
- i18n.str`pay`,
- i18n.str`refund`,
+ i18n.str`Pay`,
+ i18n.str`Refund`,
]}
tooltip={i18n.str`The event of the webhook: why the webhook is used`}
/>
@@ -114,28 +115,79 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
/>
<p>
- The text below support <a target="_blank" rel="noreferrer" href="https://mustache.github.io/mustache.5.html">mustache</a> template engine. Any string
- between <pre style={{ display: "inline", padding: 0 }}>&#123;&#123;</pre> and <pre style={{ display: "inline", padding: 0 }}>&#125;&#125;</pre> will
- be replaced with replaced with the value of the corresponding variable.
+ {/* prettier will add some nodes which we don't want because of i18n */}
+ {/* prettier-ignore */}
+ <i18n.Translate>
+ The text below support <a
+ target="_blank"
+ rel="noreferrer"
+ href="https://mustache.github.io/mustache.5.html"
+ >
+ mustache
+ </a> template engine. Any string between <pre style={{ display: "inline", padding: 0 }}>
+ &#123;&#123;
+ </pre> and <pre style={{ display: "inline", padding: 0 }}>
+ &#125;&#125;
+ </pre> will be replaced with replaced with the value of the
+ corresponding variable.
+ </i18n.Translate>
</p>
<p>
- For example <pre style={{ display: "inline", padding: 0 }}>&#123;&#123;contract_terms.amount&#125;&#125;</pre> will be replaced
- with the the order's price
+ {/* prettier will add some nodes which we don't want because of i18n */}
+ {/* prettier-ignore */}
+ <i18n.Translate>
+ For example <pre style={{ display: "inline", padding: 0 }}>
+ &#123;&#123;contract_terms.amount&#125;&#125;
+ </pre> will be replaced with the the order's price
+ </i18n.Translate>
</p>
<p>
- The short list of variables are:
+ <i18n.Translate>
+ The short list of variables are:
+ </i18n.Translate>
</p>
<div class="menu">
-
- <ul class="menu-list" style={{ listStyleType: "disc", marginLeft: 20 }}>
- <li><b>contract_terms.summary:</b> order's description </li>
- <li><b>contract_terms.amount:</b> order's price </li>
- <li><b>order_id:</b> order's unique identification </li>
- {state.event_type === "refund" && <Fragment>
- <li><b>refund_amout:</b> the amount that was being refunded</li>
- <li><b>reason:</b> the reason entered by the merchant staff for granting the refund</li>
- <li><b>timestamp:</b> time of the refund in nanoseconds since 1970</li>
- </Fragment>}
+ <ul
+ class="menu-list"
+ style={{ listStyleType: "disc", marginLeft: 20 }}
+ >
+ <li>
+ <b>contract_terms.summary:</b>{" "}
+ <i18n.Translate>order's description</i18n.Translate>
+ </li>
+ <li>
+ <b>contract_terms.amount:</b>{" "}
+ <i18n.Translate>order's price</i18n.Translate>
+ </li>
+ <li>
+ <b>order_id:</b>{" "}
+ <i18n.Translate>
+ order's unique identification
+ </i18n.Translate>
+ </li>
+ {state.event_type === "refund" && (
+ <Fragment>
+ <li>
+ <b>refund_amout:</b>{" "}
+ <i18n.Translate>
+ the amount that was being refunded
+ </i18n.Translate>
+ </li>
+ <li>
+ <b>reason:</b>{" "}
+ <i18n.Translate>
+ the reason entered by the merchant staff for granting
+ the refund
+ </i18n.Translate>
+ </li>
+ <li>
+ <b>timestamp:</b>{" "}
+ <i18n.Translate>
+ time of the refund in nanoseconds since 1970
+ </i18n.Translate>
+ </li>
+ </Fragment>
+ )}
</ul>
</div>
{/* <Input<Entity>
@@ -163,7 +215,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx
index 70f246ff1..6595879fb 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx
@@ -37,22 +37,35 @@ interface Props {
export default function CreateWebhook({ onConfirm, onBack }: Props): VNode {
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
return (
<>
<NotificationCard notification={notif} />
<CreatePage
onBack={onBack}
- onCreate={(request: TalerMerchantApi.WebhookAddDetails) => {
+ onCreate={async (request: TalerMerchantApi.WebhookAddDetails) => {
return lib.instance.addWebhook(state.token, request)
- .then(() => onConfirm())
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Webhook create successfully`,
+ type: "SUCCESS",
+ });
+ onConfirm()
+ } else {
+ setNotif({
+ message: i18n.str`Could not create the webhook`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not inform template`,
+ message: i18n.str`Could not create webhook`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
index 877bd30e5..2d42ea6a5 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/Table.tsx
@@ -59,7 +59,7 @@ export function CardTable({
<div class="card-header-icon" aria-label="more options">
<span
class="has-tooltip-left"
- data-tooltip={i18n.str`add new webhooks`}
+ data-tooltip={i18n.str`Add new webhooks`}
>
<button class="button is-info" type="button" onClick={onCreate}>
<span class="icon is-small">
@@ -114,10 +114,10 @@ function Table({
{onLoadMoreBefore && (
<button
class="button is-fullwidth"
- data-tooltip={i18n.str`load more webhooks before the first one`}
+ data-tooltip={i18n.str`Load more webhooks before the first one`}
onClick={onLoadMoreBefore}
>
- <i18n.Translate>load first page</i18n.Translate>
+ <i18n.Translate>Load first page</i18n.Translate>
</button>
)}
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
@@ -152,18 +152,11 @@ function Table({
<div class="buttons is-right">
<button
class="button is-danger is-small has-tooltip-left"
- data-tooltip={i18n.str`delete selected webhook from the database`}
+ data-tooltip={i18n.str`Delete selected webhook from the database`}
onClick={() => onDelete(i)}
>
- Delete
+ <i18n.Translate>Delete</i18n.Translate>
</button>
- {/* <button
- class="button is-info is-small has-tooltip-left"
- data-tooltip={i18n.str`test webhook`}
- onClick={() => onNewOrder(i)}
- >
- Test
- </button> */}
</div>
</td>
</tr>
@@ -174,10 +167,10 @@ function Table({
{onLoadMoreAfter && (
<button
class="button is-fullwidth"
- data-tooltip={i18n.str`load more webhooks after the last one`}
+ data-tooltip={i18n.str`Load more webhooks after the last one`}
onClick={onLoadMoreAfter}
>
- <i18n.Translate>load next page</i18n.Translate>
+ <i18n.Translate>Load next page</i18n.Translate>
</button>
)}
</div>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx
index 789b8d73b..0e1c57839 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx
@@ -25,9 +25,7 @@ import {
TalerMerchantApi,
assertUnreachable,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+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";
@@ -48,8 +46,7 @@ interface Props {
export default function ListWebhooks({ onCreate, onSelect }: Props): VNode {
const { i18n } = useTranslationContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const result = useInstanceWebhooks();
if (!result) return <Loading />;
@@ -62,7 +59,7 @@ export default function ListWebhooks({ onCreate, onSelect }: Props): VNode {
return <NotFoundPageOrAdminCreate />;
}
case HttpStatusCode.Unauthorized: {
- return <LoginPage />
+ return <LoginPage />;
}
default: {
assertUnreachable(result);
@@ -85,17 +82,25 @@ export default function ListWebhooks({ onCreate, onSelect }: Props): VNode {
onDelete={(e: TalerMerchantApi.WebhookEntry) => {
return lib.instance
.deleteWebhook(state.token, e.webhook_id)
- .then(() =>
- setNotif({
- message: i18n.str`webhook delete successfully`,
- type: "SUCCESS",
- }),
- )
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Webhook delete successfully`,
+ type: "SUCCESS",
+ });
+ } else {
+ setNotif({
+ message: i18n.str`Could not delete the webhook`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+ }
+ })
.catch((error) =>
setNotif({
- message: i18n.str`could not delete the webhook`,
+ message: i18n.str`Could not delete the webhook`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
}),
);
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
index bcd53ffd0..1c1d0da79 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
@@ -19,6 +19,7 @@
* @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 { useState } from "preact/hooks";
@@ -28,8 +29,8 @@ import {
FormProvider,
} from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
-import { TalerMerchantApi } from "@gnu-taler/taler-util";
import { WithId } from "../../../../declaration.js";
+import { undefinedIfEmpty } from "../../../../utils/table.js";
type Entity = TalerMerchantApi.WebhookPatchDetails & WithId;
@@ -45,23 +46,21 @@ export function UpdatePage({ webhook, onUpdate, onBack }: Props): VNode {
const [state, setState] = useState<Partial<Entity>>(webhook);
- const errors: FormErrors<Entity> = {
- event_type: !state.event_type ? i18n.str`required` : undefined,
+ const errors = undefinedIfEmpty<FormErrors<Entity>>({
+ event_type: !state.event_type ? i18n.str`Required` : undefined,
http_method: !state.http_method
- ? i18n.str`required`
+ ? i18n.str`Required`
: !validMethod.includes(state.http_method)
- ? i18n.str`should be one of '${validMethod.join(", ")}'`
- : undefined,
- url: !state.url ? i18n.str`required` : undefined,
- };
+ ? i18n.str`Must be one of '${validMethod.join(", ")}'`
+ : undefined,
+ url: !state.url ? i18n.str`Required` : undefined,
+ });
- const hasErrors = Object.keys(errors).some(
- (k) => (errors as any)[k] !== undefined,
- );
+ const hasErrors = errors !== undefined;
const submitForm = () => {
if (hasErrors) return Promise.reject();
- return onUpdate(state as any);
+ return onUpdate(state as Entity);
};
return (
@@ -130,7 +129,7 @@ export function UpdatePage({ webhook, onUpdate, onBack }: Props): VNode {
data-tooltip={
hasErrors
? i18n.str`Need to complete marked fields`
- : "confirm operation"
+ : i18n.str`Confirm operation`
}
onClick={submitForm}
>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx
index 6c0466dad..83dea45eb 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx
@@ -29,6 +29,7 @@ import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchan
import { Loading } from "../../../../components/exception/loading.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { useSessionContext } from "../../../../context/session.js";
+import { WithId } from "../../../../declaration.js";
import {
useWebhookDetails,
} from "../../../../hooks/webhooks.js";
@@ -36,7 +37,6 @@ import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
import { UpdatePage } from "./UpdatePage.js";
-import { WithId } from "../../../../declaration.js";
export type Entity = TalerMerchantApi.WebhookPatchDetails & WithId;
@@ -50,8 +50,7 @@ export default function UpdateWebhook({
onConfirm,
onBack,
}: Props): VNode {
- const { lib } = useSessionContext();
- const { state } = useSessionContext();
+ const { state, lib } = useSessionContext();
const result = useWebhookDetails(tid);
const [notif, setNotif] = useState<Notification | undefined>(undefined);
@@ -81,14 +80,29 @@ export default function UpdateWebhook({
<UpdatePage
webhook={{ ...result.body, id: tid }}
onBack={onBack}
- onUpdate={(data) => {
+ onUpdate={async (data) => {
return lib.instance.updateWebhook(state.token, tid, data)
- .then(onConfirm)
+ .then((resp) => {
+ if (resp.type === "ok") {
+ setNotif({
+ message: i18n.str`Webhook updated`,
+ type: "SUCCESS",
+ });
+ onConfirm()
+ } else {
+ setNotif({
+ message: i18n.str`Could not update webhook`,
+ type: "ERROR",
+ description: resp.detail.hint,
+ });
+
+ }
+ })
.catch((error) => {
setNotif({
- message: i18n.str`could not update template`,
+ message: i18n.str`Could not update webhook`,
type: "ERROR",
- description: error.message,
+ description: error instanceof Error ? error.message : String(error),
});
});
}}