diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index 69a9a07b2..52dbd4ff6 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -363,14 +363,14 @@ export function PaytoWireTransferForm({ */ export function doAutoFocus(element: HTMLElement | null) { if (element) { - window.requestIdleCallback(() => { + setTimeout(() => { element.focus() element.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }) - }) + }, 100) } } diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx index 2d90b410c..91c5da718 100644 --- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx @@ -150,7 +150,7 @@ export function WithdrawalQRCode({ details={{ account, reserve: data.selected_reserve_pub, - amount: Amounts.parseOrThrow("usd:10.00") + amount: Amounts.parseOrThrow(data.amount) }} onAborted={() => { notifyInfo(i18n.str`Operation canceled`); diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx index 1a7617643..f0a7de53b 100644 --- a/packages/merchant-backoffice-ui/src/Application.tsx +++ b/packages/merchant-backoffice-ui/src/Application.tsx @@ -60,7 +60,7 @@ export function Application(): VNode { * @returns */ function ApplicationStatusRoutes(): VNode { - const { url: backendURL, updateToken, changeBackend } = useBackendContext(); + const { changeBackend, selected: backendSelected } = useBackendContext(); const result = useBackendConfig(); const { i18n } = useTranslationContext(); @@ -69,7 +69,7 @@ function ApplicationStatusRoutes(): VNode { : { currency: "unknown", version: "unknown" }; const ctx = useMemo(() => ({ currency, version }), [currency, version]); - if (!backendURL) { + if (!backendSelected) { return ( diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx index c2a9d3b18..f5372db8d 100644 --- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx +++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx @@ -87,9 +87,9 @@ export enum InstancePaths { bank_update = "/bank/:bid/update", bank_new = "/bank/new", - product_list = "/products", - product_update = "/product/:pid/update", - product_new = "/product/new", + inventory_list = "/inventory", + inventory_update = "/inventory/:pid/update", + inventory_new = "/inventory/new", order_list = "/orders", order_new = "/order/new", @@ -347,42 +347,42 @@ export function InstanceRoutes({ onLoadError={ServerErrorRedirectTo(InstancePaths.error)} /> {/** - * Product pages + * Inventory pages */} { - route(InstancePaths.product_new); + route(InstancePaths.inventory_new); }} onSelect={(id: string) => { - route(InstancePaths.product_update.replace(":pid", id)); + route(InstancePaths.inventory_update.replace(":pid", id)); }} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} /> { - route(InstancePaths.product_list); + route(InstancePaths.inventory_list); }} onBack={() => { - route(InstancePaths.product_list); + route(InstancePaths.inventory_list); }} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} /> { - route(InstancePaths.product_list); + route(InstancePaths.inventory_list); }} onBack={() => { - route(InstancePaths.product_list); + route(InstancePaths.inventory_list); }} /> {/** @@ -405,7 +405,7 @@ export function InstanceRoutes({ path={InstancePaths.bank_update} component={BankAccountUpdatePage} onUnauthorized={LoginPageAccessDenied} - onLoadError={ServerErrorRedirectTo(InstancePaths.product_list)} + onLoadError={ServerErrorRedirectTo(InstancePaths.inventory_list)} onConfirm={() => { route(InstancePaths.bank_list); }} diff --git a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx index 012d14977..1d18685c5 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx @@ -212,10 +212,7 @@ export function InputStock({ withTimestampSupport /> - - name="address" - label={i18n.str`Delivery address`} - > + name="address" label={i18n.str`Warehouse address`}> diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx index 3d5f20c85..402134096 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -49,7 +49,7 @@ export function Sidebar({ isPasswordOk }: Props): VNode { const config = useConfigContext(); - const { url: backendURL } = useBackendContext() + const { url: backendURL, resetBackend } = useBackendContext() const { i18n } = useTranslationContext(); const kycStatus = useInstanceKYCDetails(); const needKYC = kycStatus.ok && kycStatus.data.type === "redirect"; @@ -80,7 +80,7 @@ export function Sidebar({ - {isPasswordOk && admin && !mimic && ( + {admin && !mimic && (
  • Log out -
  • - } + : +
  • + resetBackend()} + > + + + + + Change server + + +
  • + } diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx index cb318906f..b8ac2c9ab 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx @@ -30,11 +30,11 @@ function getInstanceTitle(path: string, id: string): string { return `${id}: Orders`; case InstancePaths.order_new: return `${id}: New order`; - case InstancePaths.product_list: - return `${id}: Products`; - case InstancePaths.product_new: + case InstancePaths.inventory_list: + return `${id}: Inventory`; + case InstancePaths.inventory_new: return `${id}: New product`; - case InstancePaths.product_update: + case InstancePaths.inventory_update: return `${id}: Update product`; case InstancePaths.reserves_new: return `${id}: New reserve`; diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx index 8bebbd298..e91e8c876 100644 --- a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx +++ b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx @@ -146,9 +146,9 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { /> name="minimum_age" - label={i18n.str`Age restricted`} + label={i18n.str`Age restriction`} tooltip={i18n.str`is this product restricted for customer below certain age?`} - help={i18n.str`can be overridden by the order configuration`} + help={i18n.str`minimum age of the buyer`} /> name="unit" @@ -165,7 +165,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { name="stock" label={i18n.str`Stock`} alreadyExist={alreadyExist} - tooltip={i18n.str`product inventory for products with finite supply (for internal use only)`} + tooltip={i18n.str`inventory for products with finite supply (for internal use only)`} /> name="taxes" diff --git a/packages/merchant-backoffice-ui/src/context/backend.ts b/packages/merchant-backoffice-ui/src/context/backend.ts index 056f9a192..d4a9abd5f 100644 --- a/packages/merchant-backoffice-ui/src/context/backend.ts +++ b/packages/merchant-backoffice-ui/src/context/backend.ts @@ -20,35 +20,55 @@ */ import { createContext, h, VNode } from "preact"; -import { useContext } from "preact/hooks"; +import { useContext, useState } from "preact/hooks"; import { LoginToken } from "../declaration.js"; import { useBackendDefaultToken, useBackendURL } from "../hooks/index.js"; +import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; +import { codecForBoolean } from "@gnu-taler/taler-util"; interface BackendContextType { url: string, + selected: boolean; token?: LoginToken; updateToken: (token: LoginToken | undefined) => void; changeBackend: (url: string) => void; + resetBackend: () => void; } const BackendContext = createContext({ url: "", + selected: false, token: undefined, updateToken: () => null, changeBackend: () => null, + resetBackend: () => null, }); +const BACKEND_SELECTED = buildStorageKey("backend-selected", codecForBoolean()); + function useBackendContextState( defaultUrl?: string, ): BackendContextType { - const [url, changeBackend] = useBackendURL(defaultUrl); + const [url, changeBackend2] = useBackendURL(defaultUrl); const [token, updateToken] = useBackendDefaultToken(); + const {value, update} = useLocalStorage(BACKEND_SELECTED) + + function changeBackend(s:string) { + changeBackend2(s) + update(true) + } + + function resetBackend() { + update(false) + } return { url, token, + selected: value ?? false, updateToken, - changeBackend + changeBackend, + resetBackend }; } diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts b/packages/merchant-backoffice-ui/src/declaration.d.ts index c3e6ea3da..dc53e3e83 100644 --- a/packages/merchant-backoffice-ui/src/declaration.d.ts +++ b/packages/merchant-backoffice-ui/src/declaration.d.ts @@ -1327,7 +1327,7 @@ export namespace MerchantBackend { otp_device_id: string; // Human-readable description for the device. - otp_description: string; + otp_device_description: string; // A base64-encoded key otp_key: string; @@ -1341,7 +1341,7 @@ export namespace MerchantBackend { interface OtpDevicePatchDetails { // Human-readable description for the device. - otp_description: string; + otp_device_description: string; // A base64-encoded key otp_key: string | undefined; diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts b/packages/merchant-backoffice-ui/src/hooks/index.ts index ee696779f..498e4eb78 100644 --- a/packages/merchant-backoffice-ui/src/hooks/index.ts +++ b/packages/merchant-backoffice-ui/src/hooks/index.ts @@ -31,7 +31,8 @@ const calculateRootPath = () => { typeof window !== undefined ? window.location.origin + window.location.pathname : "/"; - return rootPath; + + return rootPath.replace("webui/",""); }; const loginTokenCodec = buildCodecForObject() diff --git a/packages/merchant-backoffice-ui/src/hooks/otp.ts b/packages/merchant-backoffice-ui/src/hooks/otp.ts index 3544b4881..93eefeea5 100644 --- a/packages/merchant-backoffice-ui/src/hooks/otp.ts +++ b/packages/merchant-backoffice-ui/src/hooks/otp.ts @@ -30,13 +30,13 @@ const useSWR = _useSWR as unknown as SWRHook; const MOCKED_DEVICES: Record = { "1": { - otp_description: "first device", + otp_device_description: "first device", otp_algorithm: 1, otp_device_id: "1", otp_key: "123", }, "2": { - otp_description: "second device", + otp_device_description: "second device", otp_algorithm: 0, otp_device_id: "2", otp_key: "456", diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts b/packages/merchant-backoffice-ui/src/hooks/product.ts index 8ecaefaa6..e06ea8ed8 100644 --- a/packages/merchant-backoffice-ui/src/hooks/product.ts +++ b/packages/merchant-backoffice-ui/src/hooks/product.ts @@ -26,6 +26,9 @@ import _useSWR, { SWRHook, useSWRConfig } from "swr"; const useSWR = _useSWR as unknown as SWRHook; export interface ProductAPI { + getProduct: ( + id: string, + ) => Promise; createProduct: ( data: MerchantBackend.Products.ProductAddDetail, ) => Promise; @@ -66,7 +69,7 @@ export function useProductAPI(): ProductAPI { data, }); - return await mutateAll(/.*"\/private\/products.*/); + return await mutateAll(/.*\/private\/products.*/); }; const deleteProduct = async (productId: string): Promise => { @@ -88,7 +91,17 @@ export function useProductAPI(): ProductAPI { return await mutateAll(/.*"\/private\/products.*/); }; - return { createProduct, updateProduct, deleteProduct, lockProduct }; + const getProduct = async ( + productId: string, + ): Promise => { + await request(`/private/products/${productId}`, { + method: "GET", + }); + + return + }; + + return { createProduct, updateProduct, deleteProduct, lockProduct, getProduct }; } export function useInstanceProducts(): HttpResponse< diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx index cbfe1d573..db73217ed 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx @@ -66,7 +66,7 @@ export function CardTable({ - Products + Inventory

    Taxes - Profit + Sales Stock @@ -190,18 +190,21 @@ function Table({ src={i.image ? i.image : emptyImage} style={{ border: "solid black 1px", - width: 100, - height: 100, + maxHeight: "2em", + width: "auto", + height: "auto", }} /> rowSelection !== i.id && rowSelectionHandler(i.id) } style={{ cursor: "pointer" }} > - {i.description} + {i.description.length > 30 ? i.description.substring(0, 30) + "..." : i.description} diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx index 85c50e5ed..274a7c2ea 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx @@ -53,7 +53,7 @@ export default function ProductList({ onNotFound, }: Props): VNode { const result = useInstanceProducts(); - const { deleteProduct, updateProduct } = useProductAPI(); + const { deleteProduct, updateProduct, getProduct } = useProductAPI(); const [deleting, setDeleting] = useState(null); const [notif, setNotif] = useState(undefined); @@ -74,11 +74,61 @@ export default function ProductList({ return onNotFound(); return onLoadError(result); } + const [errorId, setErrorId] = useState( + undefined, + ); + + const [productId, setProductId] = useState() + async function testIfProductExistAndSelect(orderId: string | undefined): Promise { + if (!orderId) { + setErrorId(i18n.str`Enter a product id`); + return; + } + try { + await getProduct(orderId); + onSelect(orderId); + setErrorId(undefined); + } catch { + setErrorId(i18n.str`product not found`); + } + } return (
    +
    +
    +
    +
    +
    + setProductId(e.currentTarget.value)} + placeholder={i18n.str`product id`} + /> + {errorId &&

    {errorId}

    } +
    + + + +
    +
    +
    +
    + >([]); const [exchangeQueryError, setExchangeQueryError] = useState< string | undefined @@ -123,19 +125,26 @@ function ViewStep({ { - return fetch(`${reserve.exchange_url}wire`) - .then((r) => r.json()) + if (!reserve.exchange_url) { + return Promise.resolve(); + } + + return request(reserve.exchange_url, "keys") //
    fetch(`${reserve.exchange_url}wire`) .then((r) => { - const wireMethods = r.accounts.map((a: any) => { - const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri); - return (match && match[1]) || ""; - }); + if (r.loading) return; + if (r.ok) { + const wireMethods = r.data.accounts.map((a: any) => { + const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri); + return (match && match[1]) || ""; + }); + } setWireMethods(wireMethods); setCurrentStep(Steps.WIRE_METHOD); return; }) - .catch((r: any) => { - setExchangeQueryError(r.message); + .catch((r: RequestError<{}>) => { + console.log(r.cause) + setExchangeQueryError(r.cause.message); }); }} data-tooltip={ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatePage.tsx index bdc86d226..cebc1ade6 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatePage.tsx @@ -70,8 +70,8 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { : state.otp_key.length !== 32 ? i18n.str`size of the key should be 32` : undefined, - otp_description: !state.otp_description ? i18n.str`required` - : !/[a-zA-Z0-9]*/.test(state.otp_description) + otp_device_description: !state.otp_device_description ? i18n.str`required` + : !/[a-zA-Z0-9]*/.test(state.otp_device_description) ? i18n.str`no valid. only characters and numbers` : undefined, @@ -103,7 +103,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { tooltip={i18n.str`Internal id on the system`} /> - name="otp_description" + name="otp_device_description" label={i18n.str`Descripiton`} tooltip={i18n.str`Useful to identify the device physically`} /> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatedSuccessfully.tsx index 22ae55677..db3842711 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatedSuccessfully.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatedSuccessfully.tsx @@ -77,7 +77,7 @@ export function CreatedSuccessfully({

    diff --git a/packages/merchant-backoffice-ui/src/paths/instance/validators/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/validators/update/UpdatePage.tsx index 585c12e11..79be9802f 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/validators/update/UpdatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/validators/update/UpdatePage.tsx @@ -87,7 +87,7 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode { errors={errors} > - name="otp_description" + name="otp_device_description" label={i18n.str`Description`} tooltip={i18n.str`dddd`} /> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/validators/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/validators/update/index.tsx index 9a27ccfee..52f6c6c29 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/validators/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/validators/update/index.tsx @@ -80,7 +80,7 @@ export default function UpdateValidator({ device={{ id: vid, otp_algorithm: result.data.otp_algorithm, - otp_description: result.data.device_description, + otp_device_description: result.data.device_description, otp_key: undefined, otp_ctr: result.data.otp_ctr }} diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx index 9948307e4..a9e3c3a1b 100644 --- a/packages/merchant-backoffice-ui/src/paths/login/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/login/index.tsx @@ -52,7 +52,7 @@ function cleanUp(s: string): string { } export function LoginPage({ onConfirm }: Props): VNode { - const { url: backendURL, changeBackend } = useBackendContext(); + const { url: backendURL, changeBackend, resetBackend } = useBackendContext(); const { admin, id } = useInstanceContext(); const { requestNewLoginToken } = useCredentialsChecker(); const [token, setToken] = useState(""); @@ -73,10 +73,9 @@ export function LoginPage({ onConfirm }: Props): VNode { }, [backendURL, id, token]) async function changeServer() { - changeBackend("") + resetBackend() } - console.log(admin, id) if (admin && id !== "default") { //admin trying to access another instance return (
    @@ -211,10 +210,7 @@ export function LoginPage({ onConfirm }: Props): VNode { borderTop: 0, }} > - + Change server @@ -304,11 +300,8 @@ export function ConnectionPage({ onConfirm }: { onConfirm: (s: string) => void } borderTop: 0, }} > - - Try again + + Connect
    diff --git a/packages/merchant-backoffice-ui/src/schemas/index.ts b/packages/merchant-backoffice-ui/src/schemas/index.ts index 4be77595b..c97d41204 100644 --- a/packages/merchant-backoffice-ui/src/schemas/index.ts +++ b/packages/merchant-backoffice-ui/src/schemas/index.ts @@ -22,6 +22,7 @@ import { isAfter, isFuture } from "date-fns"; import * as yup from "yup"; import { AMOUNT_REGEX, PAYTO_REGEX } from "../utils/constants.js"; +import { Amounts } from "@gnu-taler/taler-util"; yup.setLocale({ mixed: { @@ -38,7 +39,7 @@ function listOfPayToUrisAreValid(values?: (string | undefined)[]): boolean { } function currencyWithAmountIsValid(value?: string): boolean { - return !!value && AMOUNT_REGEX.test(value); + return !!value && Amounts.parse(value) !== undefined; } function currencyGreaterThan0(value?: string) { if (value) { diff --git a/packages/merchant-backoffice-ui/src/utils/constants.ts b/packages/merchant-backoffice-ui/src/utils/constants.ts index fea9cb7e2..7c4e288b3 100644 --- a/packages/merchant-backoffice-ui/src/utils/constants.ts +++ b/packages/merchant-backoffice-ui/src/utils/constants.ts @@ -25,7 +25,7 @@ export const PAYTO_REGEX = export const PAYTO_WIRE_METHOD_LOOKUP = /payto:\/\/([a-zA-Z][a-zA-Z0-9-.]+)\/.*/; -export const AMOUNT_REGEX = /^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/; +export const AMOUNT_REGEX = /^[a-zA-Z][a-zA-Z]{1,11}:[0-9][0-9,]*\.?[0-9,]*$/; export const INSTANCE_ID_LOOKUP = /\/instances\/([^/]*)\/?$/; diff --git a/packages/web-util/src/index.build.ts b/packages/web-util/src/index.build.ts index 6ee1be20a..0e8e7cec3 100644 --- a/packages/web-util/src/index.build.ts +++ b/packages/web-util/src/index.build.ts @@ -54,7 +54,7 @@ if (GIT_ROOT === "/") { // eslint-disable-next-line no-undef process.exit(1); } -const GIT_HASH = GIT_ROOT === "/" ? undefined : git_hash(); +const GIT_HASH = git_hash(); const buf = fs.readFileSync(path.join(BASE, "package.json")); let _package = JSON.parse(buf.toString("utf-8"));