taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit c6f5d2f5b0bebe6e2b762e2f83f11f29a1ca09ed
parent 586d68c9daccb116c7b47d56ff95571c04586d9d
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon,  8 Jul 2024 15:36:20 -0300

check error on rest api and some i18n strings

Diffstat:
Mpackages/merchant-backoffice-ui/src/Routing.tsx | 3+--
Mpackages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx | 33++++++++++++++++-----------------
Mpackages/merchant-backoffice-ui/src/hooks/bank.ts | 19++++++-------------
Mpackages/merchant-backoffice-ui/src/hooks/instance.ts | 44++++++++++++++++++++++----------------------
Mpackages/merchant-backoffice-ui/src/hooks/order.ts | 14++++++--------
Mpackages/merchant-backoffice-ui/src/hooks/otp.ts | 14++++++--------
Mpackages/merchant-backoffice-ui/src/hooks/product.ts | 16+++++++---------
Mpackages/merchant-backoffice-ui/src/hooks/templates.ts | 17+++++++----------
Mpackages/merchant-backoffice-ui/src/hooks/tokenfamily.ts | 15+++++++--------
Mpackages/merchant-backoffice-ui/src/hooks/transfer.ts | 7+++----
Mpackages/merchant-backoffice-ui/src/hooks/webhooks.ts | 14++++++--------
Mpackages/merchant-backoffice-ui/src/paths/admin/create/InstanceCreatedSuccessfully.tsx | 14+++++++++++---
Mpackages/merchant-backoffice-ui/src/paths/admin/create/index.tsx | 13++++++++++---
Mpackages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx | 3+--
Mpackages/merchant-backoffice-ui/src/paths/admin/list/index.tsx | 83+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx | 10++++------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx | 31+++++++++++++++++--------------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx | 16+++++++---------
Mpackages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx | 9++++-----
Dpackages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx | 83-------------------------------------------------------------------------------
Dpackages/merchant-backoffice-ui/src/paths/instance/details/index.tsx | 90-------------------------------------------------------------------------------
Dpackages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx | 87-------------------------------------------------------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/index.stories.ts | 1-
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx | 9+++++++--
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx | 31++++++++++++++++++-------------
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx | 35++++++++++++++++++++---------------
Mpackages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx | 31++++++++++++++++++++++---------
Mpackages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx | 35++++++++++++++++++++---------------
Mpackages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx | 3+--
Mpackages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx | 22++++++++++++++++++----
Mpackages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx | 87++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx | 22++++++++++++++++++----
Mpackages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx | 38+++++++++++++++++++++++---------------
Mpackages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx | 27++++++++++++++++++++-------
Mpackages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx | 61++++++++++++++++++++++++++++++++++++-------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx | 3+--
Mpackages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx | 49++++++++++++++++++++++++++++++++-----------------
Mpackages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx | 5++++-
Mpackages/merchant-backoffice-ui/src/paths/instance/token/index.tsx | 3+--
Mpackages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/create/index.tsx | 19++++++++++++++++---
Mpackages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/Table.tsx | 1-
Mpackages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/list/index.tsx | 69+++++++++++++++++++++++++++++++++++++++++++++------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/tokenfamilies/update/index.tsx | 42++++++++++++++++++++++++++++++------------
Mpackages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx | 19++++++++++++++++---
Mpackages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx | 23++++++++++++++++++-----
Mpackages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx | 29+++++++++++++++++------------
Mpackages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx | 22++++++++++++++++++----
Mpackages/merchant-backoffice-ui/src/paths/login/index.tsx | 3+--
Mpackages/merchant-backoffice-ui/src/paths/notfound/index.tsx | 2+-
49 files changed, 655 insertions(+), 671 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/Routing.tsx b/packages/merchant-backoffice-ui/src/Routing.tsx @@ -73,7 +73,6 @@ import WebhookCreatePage from "./paths/instance/webhooks/create/index.js"; import WebhookListPage from "./paths/instance/webhooks/list/index.js"; import WebhookUpdatePage from "./paths/instance/webhooks/update/index.js"; import { LoginPage } from "./paths/login/index.js"; -import { NotFoundPage } from "./paths/notfound/index.js"; import { Settings } from "./paths/settings/index.js"; import { Notification } from "./utils/types.js"; @@ -580,7 +579,7 @@ export function Routing(_p: Props): VNode { * Example pages */} <Route path="/loading" component={Loading} /> - <Route default component={NotFoundPage} /> + <Route default component={Redirect} to={InstancePaths.order_list} /> </Router> </Fragment> ); diff --git a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx @@ -66,7 +66,7 @@ function isEthereumAddress(address: string) { return checkAddressChecksum(address); } -function checkAddressChecksum(address: string) { +function checkAddressChecksum(_address: string) { //TODO implement ethereum checksum return true; } @@ -179,20 +179,6 @@ function validateIBAN_path1( return undefined; } -// const targets = ['ach', 'bic', 'iban', 'upi', 'bitcoin', 'ilp', 'void', 'x-taler-bank'] -const targets = [ - "Choose one...", - "iban", - "x-taler-bank", - "bitcoin", - "ethereum", -]; -const noTargetValue = targets[0]; -const defaultTarget: Entity = { - target: noTargetValue, - params: {}, -}; - export function InputPaytoForm<T>({ name, readonly, @@ -202,6 +188,21 @@ export function InputPaytoForm<T>({ const { value: initialValueStr, onChange } = useField<T>(name); const initialPayto = parsePaytoUri(initialValueStr ?? ""); + const { i18n } = useTranslationContext(); + + const targets = [ + i18n.str`Choose one...`, + i18n.str`iban`, + i18n.str`x-taler-bank`, + i18n.str`bitcoin`, + i18n.str`ethereum`, + ]; + const noTargetValue = targets[0]; + const defaultTarget: Entity = { + target: noTargetValue, + params: {}, + }; + const paths = !initialPayto ? [] : initialPayto.targetPath.split("/"); const initialPath1 = paths.length >= 1 ? paths[0] : undefined; @@ -234,8 +235,6 @@ export function InputPaytoForm<T>({ } }, [initialValueStr]); - const { i18n } = useTranslationContext(); - const errors: FormErrors<Entity> = { target: value.target === noTargetValue ? i18n.str`required` : undefined, path1: !value.path1 diff --git a/packages/merchant-backoffice-ui/src/hooks/bank.ts b/packages/merchant-backoffice-ui/src/hooks/bank.ts @@ -13,7 +13,6 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { useMerchantApiContext } from "@gnu-taler/web-util/browser"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 import { @@ -35,15 +34,12 @@ export function revalidateInstanceBankAccounts() { ); } export function useInstanceBankAccounts() { - const { state: session } = useSessionContext(); - const { - lib: { instance }, - } = useSessionContext(); + const { state, lib } = useSessionContext(); // const [offset, setOffset] = useState<string | undefined>(); async function fetcher([token, _bid]: [AccessToken, string]) { - return await instance.listBankAccounts(token, { + return await lib.instance.listBankAccounts(token, { // limit: PAGINATED_LIST_REQUEST, // offset: bid, // order: "dec", @@ -53,7 +49,7 @@ export function useInstanceBankAccounts() { const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listBankAccounts">, TalerHttpError - >([session.token, "offset", "listBankAccounts"], fetcher); + >([state.token, "offset", "listBankAccounts"], fetcher); if (error) return error; if (data === undefined) return undefined; @@ -74,19 +70,16 @@ export function revalidateBankAccountDetails() { ); } export function useBankAccountDetails(h_wire: string) { - const { state: session } = useSessionContext(); - const { - lib: { instance }, - } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([token, wireId]: [AccessToken, string]) { - return await instance.getBankAccountDetails(token, wireId); + return await lib.instance.getBankAccountDetails(token, wireId); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getBankAccountDetails">, TalerHttpError - >([session.token, h_wire, "getBankAccountDetails"], fetcher); + >([state.token, h_wire, "getBankAccountDetails"], fetcher); if (data) return data; if (error) return error; diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts b/packages/merchant-backoffice-ui/src/hooks/instance.ts @@ -15,31 +15,34 @@ */ // FIX default import https://github.com/microsoft/TypeScript/issues/49189 -import { AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } from "@gnu-taler/taler-util"; +import { + AccessToken, + TalerHttpError, + TalerMerchantManagementResultByMethod, +} from "@gnu-taler/taler-util"; import _useSWR, { SWRHook, mutate } from "swr"; import { useSessionContext } from "../context/session.js"; const useSWR = _useSWR as unknown as SWRHook; - export function revalidateInstanceDetails() { return mutate( - (key) => Array.isArray(key) && key[key.length - 1] === "getCurrentInstanceDetails", + (key) => + Array.isArray(key) && key[key.length - 1] === "getCurrentInstanceDetails", undefined, { revalidate: true }, ); } export function useInstanceDetails() { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([token]: [AccessToken]) { - return await instance.getCurrentInstanceDetails(token); + return await lib.instance.getCurrentInstanceDetails(token); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getCurrentInstanceDetails">, TalerHttpError - >([session.token, "getCurrentInstanceDetails"], fetcher); + >([state.token, "getCurrentInstanceDetails"], fetcher); if (data) return data; if (error) return error; @@ -48,29 +51,28 @@ export function useInstanceDetails() { export function revalidateInstanceKYCDetails() { return mutate( - (key) => Array.isArray(key) && key[key.length - 1] === "getCurrentIntanceKycStatus", + (key) => + Array.isArray(key) && + key[key.length - 1] === "getCurrentIntanceKycStatus", undefined, { revalidate: true }, ); } export function useInstanceKYCDetails() { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([token]: [AccessToken]) { - return await instance.getCurrentIntanceKycStatus(token, {}); + return await lib.instance.getCurrentIntanceKycStatus(token, {}); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getCurrentIntanceKycStatus">, TalerHttpError - >([session.token, "getCurrentIntanceKycStatus"], fetcher); + >([state.token, "getCurrentIntanceKycStatus"], fetcher); if (data) return data; if (error) return error; return undefined; - - } export function revalidateManagedInstanceDetails() { @@ -81,17 +83,16 @@ export function revalidateManagedInstanceDetails() { ); } export function useManagedInstanceDetails(instanceId: string) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([token, instanceId]: [AccessToken, string]) { - return await instance.getInstanceDetails(token, instanceId); + return await lib.instance.getInstanceDetails(token, instanceId); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getInstanceDetails">, TalerHttpError - >([session.token, instanceId, "getInstanceDetails"], fetcher); + >([state.token, instanceId, "getInstanceDetails"], fetcher); if (data) return data; if (error) return error; @@ -106,17 +107,16 @@ export function revalidateBackendInstances() { ); } export function useBackendInstances() { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([token]: [AccessToken]) { - return await instance.listInstances(token); + return await lib.instance.listInstances(token); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listInstances">, TalerHttpError - >([session.token, "listInstances"], fetcher); + >([state.token, "listInstances"], fetcher); if (data) return data; if (error) return error; diff --git a/packages/merchant-backoffice-ui/src/hooks/order.ts b/packages/merchant-backoffice-ui/src/hooks/order.ts @@ -32,17 +32,16 @@ export function revalidateOrderDetails() { ); } export function useOrderDetails(oderId: string) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([dId, token]: [string, AccessToken]) { - return await instance.getOrderDetails(token, dId); + return await lib.instance.getOrderDetails(token, dId); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getOrderDetails">, TalerHttpError - >([oderId, session.token, "getOrderDetails"], fetcher); + >([oderId, state.token, "getOrderDetails"], fetcher); if (data) return data; if (error) return error; @@ -68,13 +67,12 @@ export function useInstanceOrders( args?: InstanceOrderFilter, updatePosition: (d: string | undefined) => void = () => { }, ) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); // const [offset, setOffset] = useState<string | undefined>(args?.position); async function fetcher([token, o, p, r, w, d]: [AccessToken, string, boolean, boolean, boolean, AbsoluteTime]) { - return await instance.listOrders(token, { + return await lib.instance.listOrders(token, { limit: PAGINATED_LIST_REQUEST, offset: o, order: "dec", @@ -88,7 +86,7 @@ export function useInstanceOrders( const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listOrders">, TalerHttpError - >([session.token, args?.position, args?.paid, args?.refunded, args?.wired, args?.date, "listOrders"], fetcher); + >([state.token, args?.position, args?.paid, args?.refunded, args?.wired, args?.date, "listOrders"], fetcher); if (error) return error; if (data === undefined) return undefined; diff --git a/packages/merchant-backoffice-ui/src/hooks/otp.ts b/packages/merchant-backoffice-ui/src/hooks/otp.ts @@ -28,13 +28,12 @@ export function revalidateInstanceOtpDevices() { ); } export function useInstanceOtpDevices() { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); // const [offset, setOffset] = useState<string | undefined>(); async function fetcher([token, _bid]: [AccessToken, string]) { - return await instance.listOtpDevices(token, { + return await lib.instance.listOtpDevices(token, { // limit: PAGINATED_LIST_REQUEST, // offset: bid, // order: "dec", @@ -44,7 +43,7 @@ export function useInstanceOtpDevices() { const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listOtpDevices">, TalerHttpError - >([session.token, "offset", "listOtpDevices"], fetcher); + >([state.token, "offset", "listOtpDevices"], fetcher); if (error) return error; if (data === undefined) return undefined; @@ -62,17 +61,16 @@ export function revalidateOtpDeviceDetails() { ); } export function useOtpDeviceDetails(deviceId: string) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([dId, token]: [string, AccessToken]) { - return await instance.getOtpDeviceDetails(token, dId); + return await lib.instance.getOtpDeviceDetails(token, dId); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getOtpDeviceDetails">, TalerHttpError - >([deviceId, session.token, "getOtpDeviceDetails"], fetcher); + >([deviceId, state.token, "getOtpDeviceDetails"], fetcher); if (data) return data; if (error) return error; diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts b/packages/merchant-backoffice-ui/src/hooks/product.ts @@ -36,13 +36,12 @@ export function revalidateInstanceProducts() { ); } export function useInstanceProducts() { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); const [offset, setOffset] = useState<number | undefined>(); async function fetcher([token, bid]: [AccessToken, number]) { - const list = await instance.listProducts(token, { + const list = await lib.instance.listProducts(token, { limit: PAGINATED_LIST_REQUEST, offset: bid === undefined ? undefined: String(bid), order: "dec", @@ -52,7 +51,7 @@ export function useInstanceProducts() { } const all: Array<ProductWithId | undefined> = await Promise.all( list.body.products.map(async (c) => { - const r = await instance.getProductDetails(token, c.product_id); + const r = await lib.instance.getProductDetails(token, c.product_id); if (r.type === "fail") { return undefined; } @@ -68,7 +67,7 @@ export function useInstanceProducts() { OperationOk<{ products: ProductWithId[] }> | TalerMerchantManagementErrorsByMethod<"listProducts">, TalerHttpError - >([session.token, offset, "listProductsWithId"], fetcher); + >([state.token, offset, "listProductsWithId"], fetcher); if (error) return error; if (data === undefined) return undefined; @@ -85,17 +84,16 @@ export function revalidateProductDetails() { ); } export function useProductDetails(productId: string) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([pid, token]: [string, AccessToken]) { - return await instance.getProductDetails(token, pid); + return await lib.instance.getProductDetails(token, pid); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getProductDetails">, TalerHttpError - >([productId, session.token, "getProductDetails"], fetcher); + >([productId, state.token, "getProductDetails"], fetcher); if (data) return data; if (error) return error; diff --git a/packages/merchant-backoffice-ui/src/hooks/templates.ts b/packages/merchant-backoffice-ui/src/hooks/templates.ts @@ -20,7 +20,6 @@ import { PAGINATED_LIST_REQUEST } from "../utils/constants.js"; import { AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } from "@gnu-taler/taler-util"; import _useSWR, { SWRHook, mutate } from "swr"; import { useSessionContext } from "../context/session.js"; -import { buildPaginatedResult } from "./webhooks.js"; const useSWR = _useSWR as unknown as SWRHook; @@ -35,13 +34,12 @@ export function revalidateInstanceTemplates() { ); } export function useInstanceTemplates() { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); - const [offset, setOffset] = useState<string | undefined>(); + const [offset] = useState<string | undefined>(); async function fetcher([token, bid]: [AccessToken, string]) { - return await instance.listTemplates(token, { + return await lib.instance.listTemplates(token, { limit: PAGINATED_LIST_REQUEST, offset: bid, order: "dec", @@ -51,7 +49,7 @@ export function useInstanceTemplates() { const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listTemplates">, TalerHttpError - >([session.token, offset, "listTemplates"], fetcher); + >([state.token, offset, "listTemplates"], fetcher); if (error) return error; if (data === undefined) return undefined; @@ -70,17 +68,16 @@ export function revalidateTemplateDetails() { ); } export function useTemplateDetails(templateId: string) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([tid, token]: [string, AccessToken]) { - return await instance.getTemplateDetails(token, tid); + return await lib.instance.getTemplateDetails(token, tid); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getTemplateDetails">, TalerHttpError - >([templateId, session.token, "getTemplateDetails"], fetcher); + >([templateId, state.token, "getTemplateDetails"], fetcher); if (data) return data; if (error) return error; diff --git a/packages/merchant-backoffice-ui/src/hooks/tokenfamily.ts b/packages/merchant-backoffice-ui/src/hooks/tokenfamily.ts @@ -21,12 +21,12 @@ import _useSWR, { SWRHook } from "swr"; const useSWR = _useSWR as unknown as SWRHook; export function useInstanceTokenFamilies() { - const { state: session, lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); // const [offset, setOffset] = useState<number | undefined>(); - async function fetcher([token, bid]: [AccessToken, number]) { - return await instance.listTokenFamilies(token, { + async function fetcher([token, _bid]: [AccessToken, number]) { + return await lib.instance.listTokenFamilies(token, { // limit: PAGINATED_LIST_REQUEST, // offset: bid === undefined ? undefined: String(bid), // order: "dec", @@ -36,7 +36,7 @@ export function useInstanceTokenFamilies() { const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listTokenFamilies">, TalerHttpError - >([session.token, "offset", "listTokenFamilies"], fetcher); + >([state.token, "offset", "listTokenFamilies"], fetcher); if (error) return error; if (data === undefined) return undefined; @@ -46,17 +46,16 @@ export function useInstanceTokenFamilies() { } export function useTokenFamilyDetails(tokenFamilySlug: string) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([slug, token]: [string, AccessToken]) { - return await instance.getTokenFamilyDetails(token, slug); + return await lib.instance.getTokenFamilyDetails(token, slug); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getTokenFamilyDetails">, TalerHttpError - >([tokenFamilySlug, session.token, "getTokenFamilyDetails"], fetcher); + >([tokenFamilySlug, state.token, "getTokenFamilyDetails"], fetcher); if (error) return error; if (data === undefined) return undefined; diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.ts b/packages/merchant-backoffice-ui/src/hooks/transfer.ts @@ -39,13 +39,12 @@ export function useInstanceTransfers( args?: InstanceTransferFilter, updatePosition: (id: string | undefined) => void = (() => { }), ) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); // const [offset, setOffset] = useState<string | undefined>(args?.position); async function fetcher([token, o, p, v]: [AccessToken, string, string, boolean]) { - return await instance.listWireTransfers(token, { + return await lib.instance.listWireTransfers(token, { paytoURI: p, verified: v, limit: PAGINATED_LIST_REQUEST, @@ -57,7 +56,7 @@ export function useInstanceTransfers( const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listWireTransfers">, TalerHttpError - >([session.token, args?.position, args?.payto_uri, args?.verified, "listWireTransfers"], fetcher); + >([state.token, args?.position, args?.payto_uri, args?.verified, "listWireTransfers"], fetcher); if (error) return error; if (data === undefined) return undefined; diff --git a/packages/merchant-backoffice-ui/src/hooks/webhooks.ts b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts @@ -32,13 +32,12 @@ export function revalidateInstanceWebhooks() { ); } export function useInstanceWebhooks() { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); // const [offset, setOffset] = useState<string | undefined>(); async function fetcher([token, _bid]: [AccessToken, string]) { - return await instance.listWebhooks(token, { + return await lib.instance.listWebhooks(token, { // limit: PAGINATED_LIST_REQUEST, // offset: bid, // order: "dec", @@ -48,7 +47,7 @@ export function useInstanceWebhooks() { const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listWebhooks">, TalerHttpError - >([session.token, "offset", "listWebhooks"], fetcher); + >([state.token, "offset", "listWebhooks"], fetcher); if (error) return error; if (data === undefined) return undefined; @@ -100,17 +99,16 @@ export function revalidateWebhookDetails() { ); } export function useWebhookDetails(webhookId: string) { - const { state: session } = useSessionContext(); - const { lib: { instance } } = useSessionContext(); + const { state, lib } = useSessionContext(); async function fetcher([hookId, token]: [string, AccessToken]) { - return await instance.getWebhookDetails(token, hookId); + return await lib.instance.getWebhookDetails(token, hookId); } const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"getWebhookDetails">, TalerHttpError - >([webhookId, session.token, "getWebhookDetails"], fetcher); + >([webhookId, state.token, "getWebhookDetails"], fetcher); if (data) return data; if (error) return error; diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/InstanceCreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/InstanceCreatedSuccessfully.tsx @@ -20,6 +20,7 @@ import { h, VNode } from "preact"; import { CreatedSuccessfully } from "../../../components/notifications/CreatedSuccessfully.js"; import { Entity } from "./index.js"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; export function InstanceCreatedSuccessfully({ entity, @@ -28,11 +29,14 @@ export function InstanceCreatedSuccessfully({ entity: Entity; onConfirm: () => void; }): VNode { + const { i18n } = useTranslationContext(); return ( <CreatedSuccessfully onConfirm={onConfirm}> <div class="field is-horizontal"> <div class="field-label is-normal"> - <label class="label">ID</label> + <label class="label"> + <i18n.Translate>ID</i18n.Translate> + </label> </div> <div class="field-body is-flex-grow-3"> <div class="field"> @@ -44,7 +48,9 @@ export function InstanceCreatedSuccessfully({ </div> <div class="field is-horizontal"> <div class="field-label is-normal"> - <label class="label">Business Name</label> + <label class="label"> + <i18n.Translate>Business Name</i18n.Translate> + </label> </div> <div class="field-body is-flex-grow-3"> <div class="field"> @@ -56,7 +62,9 @@ export function InstanceCreatedSuccessfully({ </div> <div class="field is-horizontal"> <div class="field-label is-normal"> - <label class="label">Access token</label> + <label class="label"> + <i18n.Translate>Access token</i18n.Translate> + </label> </div> <div class="field-body is-flex-grow-3"> <div class="field"> diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx @@ -38,8 +38,7 @@ export type Entity = TalerMerchantApi.InstanceConfigurationMessage; export default function Create({ onBack, onConfirm, forceId }: Props): VNode { const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); - const { lib } = useSessionContext(); - const { state, logIn } = useSessionContext(); + const { lib, state, logIn } = useSessionContext(); return ( <Fragment> @@ -53,7 +52,15 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode { ) => { if (state.status !== "loggedIn") return; try { - await lib.instance.createInstance(state.token, d); + const resp = await lib.instance.createInstance(state.token, d); + if (resp.type === "fail") { + setNotif({ + message: i18n.str`Failed to create instance`, + type: "ERROR", + description: resp.detail.hint, + }); + return; + } if (d.auth.token) { //if auth has been updated, request a new access token const result = await lib.authenticate.createAccessTokenBearer( diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx @@ -151,8 +151,7 @@ function Table({ onPurge, }: TableProps): VNode { const { i18n } = useTranslationContext(); - const { lib } = useSessionContext(); - const { impersonate } = useSessionContext(); + const { lib, impersonate } = useSessionContext(); return ( <div class="table-container"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx @@ -19,10 +19,13 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { HttpStatusCode, TalerError, TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util"; import { - useTranslationContext -} from "@gnu-taler/web-util/browser"; + HttpStatusCode, + TalerError, + TalerMerchantApi, + assertUnreachable, +} from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { ErrorLoadingMerchant } from "../../../components/ErrorLoadingMerchant.js"; @@ -41,31 +44,29 @@ interface Props { instances: TalerMerchantApi.Instance[]; } -export default function Instances({ - onCreate, - onUpdate, -}: Props): VNode { +export default function Instances({ onCreate, onUpdate }: Props): VNode { const result = useBackendInstances(); - const [deleting, setDeleting] = - useState<TalerMerchantApi.Instance | null>(null); - const [purging, setPurging] = - useState<TalerMerchantApi.Instance | null>(null); + const [deleting, setDeleting] = useState<TalerMerchantApi.Instance | null>( + null, + ); + const [purging, setPurging] = useState<TalerMerchantApi.Instance | null>( + null, + ); const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); - const { lib } = useSessionContext(); - const { state } = useSessionContext(); + const { state, lib } = useSessionContext(); - 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.Unauthorized: { - return <LoginPage /> + return <LoginPage />; } default: { - assertUnreachable(result.case) + assertUnreachable(result.case); } } } @@ -90,12 +91,22 @@ export default function Instances({ return; } try { - await lib.instance.deleteInstance(state.token, deleting.id); - // pushNotification({message: 'delete_success', type: 'SUCCESS' }) - setNotif({ - message: i18n.str`Instance "${deleting.name}" (ID: ${deleting.id}) has been deleted`, - type: "SUCCESS", - }); + const resp = await lib.instance.deleteInstance( + state.token, + deleting.id, + ); + if (resp.type === "ok") { + setNotif({ + message: i18n.str`Instance "${deleting.name}" (ID: ${deleting.id}) has been deleted`, + type: "SUCCESS", + }); + } else { + setNotif({ + message: i18n.str`Failed to delete instance`, + type: "ERROR", + description: resp.detail.hint, + }); + } } catch (error) { setNotif({ message: i18n.str`Failed to delete instance`, @@ -117,11 +128,25 @@ export default function Instances({ return; } try { - await lib.instance.deleteInstance(state.token, purging.id, { purge: true }); - setNotif({ - message: i18n.str`Instance '${purging.name}' (ID: ${purging.id}) has been disabled`, - type: "SUCCESS", - }); + const resp = await lib.instance.deleteInstance( + state.token, + purging.id, + { + purge: true, + }, + ); + if (resp.type === "ok") { + setNotif({ + message: i18n.str`Instance '${purging.name}' (ID: ${purging.id}) has been purge`, + type: "SUCCESS", + }); + } else { + setNotif({ + message: i18n.str`Failed to purge instance`, + type: "ERROR", + description: resp.detail.hint, + }); + } } catch (error) { setNotif({ message: i18n.str`Failed to purge instance`, 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 @@ -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,7 +59,7 @@ 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") { 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 @@ -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 />; @@ -82,23 +81,27 @@ 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 + onDelete={async (e: TalerMerchantApi.BankAccountEntry) => { + return lib.instance .deleteBankAccount(state.token, e.h_wire) - .then(() => - setNotif({ - message: i18n.str`bank account delete successfully`, - type: "SUCCESS", - }), - ) + .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`, 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 @@ -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", }, }); @@ -114,7 +114,7 @@ export function UpdatePage({ : undefinedIfEmpty({ type: replacingAccountId && - // @ts-ignore + // @ts-expect-error unedit is not in facade creds state.credit_facade_credentials?.type === "unedit" ? i18n.str`required` : undefined, @@ -150,7 +150,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 +164,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 }); } }; @@ -293,9 +291,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 +332,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> 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 @@ -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,7 +82,7 @@ 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") { @@ -106,7 +105,7 @@ export default function UpdateValidator({ }} onReplace={async (prev, next) => { try { - const created = await api.instance.addBankAccount( + const created = await lib.instance.addBankAccount( state.token, next, ); @@ -127,7 +126,7 @@ export default function UpdateValidator({ return; } try { - const deleted = await api.instance.deleteBankAccount( + const deleted = await lib.instance.deleteBankAccount( state.token, prev.h_wire, ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx @@ -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 @@ -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 @@ -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 @@ -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/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,11 @@ 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(); const inventoryResult = useInstanceProducts(); + const { i18n } = useTranslationContext(); if (!detailsResult) return <Loading />; if (detailsResult instanceof TalerError) { @@ -104,6 +105,10 @@ export default function OrderCreate({ onConfirm, onBack }: Props): VNode { setNotif({ message: "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, }); } }) 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 @@ -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,14 +80,22 @@ 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`, 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 @@ -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); } } } @@ -160,12 +157,20 @@ 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`, @@ -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/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,14 +51,27 @@ 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, }); 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 @@ -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"; @@ -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); @@ -83,18 +80,26 @@ export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode { onSelect={(e) => { onSelect(e.otp_device_id); }} - onDelete={(e: TalerMerchantApi.OtpDeviceEntry) => { + onDelete={async (e: TalerMerchantApi.OtpDeviceEntry) => { return lib.instance .deleteOtpDevice(state.token, e.otp_device_id) - .then(() => - setNotif({ - message: i18n.str`validator delete successfully`, - type: "SUCCESS", - }), - ) + .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 validator`, + message: i18n.str`could not delete the device`, type: "ERROR", description: error.message, }), 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 @@ -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(); 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 @@ -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,8 +44,23 @@ 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`, 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 @@ -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); @@ -96,11 +94,23 @@ 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`, @@ -108,7 +118,7 @@ export default function ProductList({ 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 cannot be undone. + </i18n.Translate> </p> </ConfirmModal> )} 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 @@ -48,8 +48,7 @@ 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(); @@ -79,10 +78,25 @@ 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, }); 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 @@ -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>>({ @@ -100,11 +97,15 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { : undefined, description: !state.description ? i18n.str`should not be empty` : undefined, amount: !state.amount - ? state.amount_editable ? undefined : i18n.str`required` + ? state.amount_editable + ? undefined + : i18n.str`required` : !parsedPrice ? i18n.str`not valid` : 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 @@ -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> )} 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 @@ -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,9 +43,24 @@ 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`, 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 @@ -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 { - 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"; @@ -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,8 +84,8 @@ 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`} @@ -97,11 +98,10 @@ export default function ListTemplates({ // result.isFirstPage ? undefined: result.loadFirst // } // onLoadMoreAfter={result.isLastPage ? undefined : result.loadNext} - + templates={result.body.templates} onLoadMoreBefore={undefined} onLoadMoreAfter={undefined} - onCreate={onCreate} onSelect={(e) => { onSelect(e.template_id); @@ -113,9 +113,8 @@ export default function ListTemplates({ onQR(e.template_id); }} onDelete={(e: TalerMerchantApi.TemplateEntry) => { - setDeleting(e) - } - } + setDeleting(e); + }} /> {deleting && ( @@ -127,11 +126,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,7 +153,8 @@ export default function ListTemplates({ }} > <p> - If you delete the template <b>&quot;{deleting.template_description}&quot;</b> (ID:{" "} + If you delete the template{" "} + <b>&quot;{deleting.template_description}&quot;</b> (ID:{" "} <b>{deleting.template_id}</b>) you may loose information </p> <p class="warning"> 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 @@ -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, 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 @@ -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,11 +79,26 @@ 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`, 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 @@ -82,7 +82,6 @@ export default function TemplateUsePage({ onCreateOrder={( request: TalerMerchantApi.UsingTemplateDetails, ) => { - return lib.instance.useTemplateCreateOrder(tid, request) .then((res) => { if (res.type === "ok") { @@ -91,6 +90,10 @@ export default function TemplateUsePage({ setNotif({ 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, }); } }) diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx @@ -40,8 +40,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(); 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 @@ -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,7 +45,21 @@ 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 succesfully`, + 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`, 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 @@ -110,7 +110,6 @@ function Table({ rowSelectionHandler, instances, onSelect, - onUpdate, onDelete, }: TableProps): VNode { const { i18n } = useTranslationContext(); 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 @@ -19,11 +19,13 @@ * @author Christian Blättler */ -import { HttpStatusCode, TalerError, TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util"; import { - HttpError, - 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 +33,7 @@ import { Loading } from "../../../../components/exception/loading.js"; import { NotificationCard } from "../../../../components/menu/index.js"; import { ConfirmModal } from "../../../../components/modal/index.js"; import { useSessionContext } from "../../../../context/session.js"; -import { - useInstanceTokenFamilies, -} from "../../../../hooks/tokenfamily.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"; @@ -45,10 +45,7 @@ interface Props { onCreate: () => void; onSelect: (slug: string) => void; } -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(); @@ -67,7 +64,7 @@ export default function TokenFamilyList({ return <NotFoundPageOrAdminCreate />; } case HttpStatusCode.Unauthorized: { - return <LoginPage /> + return <LoginPage />; } default: { assertUnreachable(result); @@ -84,11 +81,23 @@ 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`, @@ -111,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`, @@ -127,8 +147,9 @@ 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. + If you delete the <b>&quot;{deleting.name}&quot;</b> token family + (Slug: <b>{deleting.slug}</b>), all issued tokens will become + invalid. </p> <p class="warning"> Deleting a token family <b>cannot be undone</b>. 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 @@ -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,8 +92,23 @@ 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 succesfully`, + 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`, 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 @@ -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,7 +56,21 @@ 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`, 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 @@ -37,20 +37,33 @@ 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, }); 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 @@ -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,12 +82,20 @@ 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`, 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 @@ -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); @@ -83,10 +82,25 @@ export default function UpdateWebhook({ onBack={onBack} onUpdate={(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, }); diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx @@ -42,8 +42,7 @@ const tokenRequest = { export function LoginPage(_p: Props): VNode { const [token, setToken] = useState(""); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const { state, logIn } = useSessionContext(); - const { lib } = useSessionContext(); + const { lib, state, logIn } = useSessionContext(); const { i18n } = useTranslationContext(); diff --git a/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx b/packages/merchant-backoffice-ui/src/paths/notfound/index.tsx @@ -31,7 +31,7 @@ import { import InstanceCreatePage from "../../paths/admin/create/index.js"; import { InstancePaths } from "../../Routing.js"; -export function NotFoundPage(): VNode { +function NotFoundPage(): VNode { return ( <div> <p>That page doesn&apos;t exist.</p>