diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 00c61e8a4..4c931ad04 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,18 +1,17 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "type": "typescript", - "tsconfig": "tsconfig.build.json", - "problemMatcher": [ - "$tsc" - ], - "group": { - "kind": "build", - "isDefault": true, - }, - } - ] -} \ No newline at end of file + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "typescript", + "tsconfig": "tsconfig.build.json", + "problemMatcher": ["$tsc"], + "group": { + "kind": "build", + "isDefault": true + }, + "label": "tsc: build - tsconfig.build.json" + } + ] +} diff --git a/build-system/taler-build-scripts b/build-system/taler-build-scripts index 23538677f..001f5dd08 160000 --- a/build-system/taler-build-scripts +++ b/build-system/taler-build-scripts @@ -1 +1 @@ -Subproject commit 23538677f6c6be2a62f38dc6137ecdd1c76b7b15 +Subproject commit 001f5dd081fc8729ff8def90c4a1c3f93eb8689a diff --git a/ci/Containerfile b/ci/Containerfile index 4e3369830..39bd740a9 100644 --- a/ci/Containerfile +++ b/ci/Containerfile @@ -6,6 +6,7 @@ RUN apt-get update -yq && \ apt-get install -yqq \ git \ python3 \ + codespell \ python3-distutils \ make \ zip \ diff --git a/ci/jobs/0-codespell/dictionary.txt b/ci/jobs/0-codespell/dictionary.txt index b4d643349..95f860504 100644 --- a/ci/jobs/0-codespell/dictionary.txt +++ b/ci/jobs/0-codespell/dictionary.txt @@ -42,3 +42,4 @@ ths updateing wan wih +vie diff --git a/ci/jobs/0-codespell/job.sh b/ci/jobs/0-codespell/job.sh index 58bd07b5a..cb41388c3 100755 --- a/ci/jobs/0-codespell/job.sh +++ b/ci/jobs/0-codespell/job.sh @@ -3,4 +3,4 @@ set -exuo pipefail job_dir=$(dirname "${BASH_SOURCE[0]}") -codespell -I "${job_dir}"/dictionary.txt -S "*.bib,*.bst,*.cls,*.json,*.png,*.svg,*.wav,*.gz,*/templating/test?/**,**/auditor/*.sql,**/templating/mustach**,*.fees,*key,*.tag,*.info,*.latexmkrc,*.ecc,*.jpg,*.zkey,*.sqlite,*/contrib/hellos/**,*/vpn/tests/**,*.priv,*.file,*.tgz,*.woff,*.gif,*.odt,*.fee,*.deflate,*.dat,*.jpeg,*.eps,*.odg,*/m4/ax_lib_postgresql.m4,*/m4/libgcrypt.m4,*.rpath,config.status,ABOUT-NLS,*/doc/texinfo.tex,*.PNG,*.??.json,*.docx,*.ods,*.doc,*.docx,*.xcf,*.xlsx,*.ecc,*.ttf,*.woff2,*.eot,*.ttf,*.eot,*.mp4,*.pptx,*.epgz,*.min.js,**/*.map,**/fonts/**,*.pack.js,*.po,*.bbl,*/afl-tests/*,*/.git/**,*.pdf,*.epub,**/signing-key.asc,**/pnpm-lock.yaml,**/*.svg,**/*.cls,**/rfc.bib,**/*.bst,*/cbdc-es.tex,*/cbdc-it.tex,**/ExchangeSelection/example.ts,*/testcurl/test_tricky.c,*/i18n/strings.ts,*/src/anastasis-data.ts,**/doc/flows/main.de.tex" +codespell -I "${job_dir}"/dictionary.txt -S "*.bib,*.bst,*.cls,*.json,*.png,*.svg,*.wav,*.gz,*/templating/test?/**,**/auditor/*.sql,**/templating/mustach**,*.fees,*key,*.tag,*.info,*.latexmkrc,*.ecc,*.jpg,*.zkey,*.sqlite,*/contrib/hellos/**,*/vpn/tests/**,*.priv,*.file,*.tgz,*.woff,*.gif,*.odt,*.fee,*.deflate,*.dat,*.jpeg,*.eps,*.odg,*/m4/ax_lib_postgresql.m4,*/m4/libgcrypt.m4,*.rpath,config.status,ABOUT-NLS,*/doc/texinfo.tex,*.PNG,*.??.json,*.docx,*.ods,*.doc,*.docx,*.xcf,*.xlsx,*.ecc,*.ttf,*.woff2,*.eot,*.ttf,*.eot,*.mp4,*.pptx,*.epgz,*.min.js,**/*.map,**/fonts/**,*.pack.js,*.po,*.bbl,*/afl-tests/*,*/.git/**,*.pdf,*.epub,**/signing-key.asc,**/pnpm-lock.yaml,**/*.svg,**/*.cls,**/rfc.bib,**/*.bst,*/cbdc-es.tex,*/cbdc-it.tex,**/ExchangeSelection/example.ts,*/testcurl/test_tricky.c,*/i18n/strings.ts,*/src/anastasis-data.ts,**/doc/flows/main.de.tex,*/vendor/**" diff --git a/package.json b/package.json index 9ce6d628e..e7b4b52d7 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "eslint": "^8.29.0", "eslint-config-prettier": "^8.5.0", "nx": "15.0.1", - "prettier": "^2.8.8" + "prettier": "^2.8.8", + "typescript": "^5.2.2" } } diff --git a/packages/aml-backoffice-ui/package.json b/packages/aml-backoffice-ui/package.json index 3fde72b74..a761374df 100644 --- a/packages/aml-backoffice-ui/package.json +++ b/packages/aml-backoffice-ui/package.json @@ -59,7 +59,7 @@ "postcss": "^8.4.23", "postcss-cli": "^10.1.0", "tailwindcss": "^3.3.2", - "typescript": "5.1.3" + "typescript": "5.2.2" }, "pogen": { "domain": "aml-backoffice" diff --git a/packages/aml-backoffice-ui/tsconfig.json b/packages/aml-backoffice-ui/tsconfig.json index cc5bdf396..9826fac07 100644 --- a/packages/aml-backoffice-ui/tsconfig.json +++ b/packages/aml-backoffice-ui/tsconfig.json @@ -1,12 +1,9 @@ { "compilerOptions": { /* Basic Options */ - "target": "ES5", - "module": "ES6", - "lib": [ - "DOM", - "ES2017" - ], + "target": "ES2020", + "module": "Node16", + "lib": ["DOM", "ES2020"], "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, @@ -45,7 +42,5 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */ }, - "include": [ - "src/**/*" - ] -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/anastasis-cli/package.json b/packages/anastasis-cli/package.json index 2bbf32c3b..55af7cc06 100644 --- a/packages/anastasis-cli/package.json +++ b/packages/anastasis-cli/package.json @@ -33,12 +33,12 @@ "@types/node": "^18.11.17", "prettier": "^2.8.8", "rimraf": "^3.0.2", - "typedoc": "^0.24.8", - "typescript": "^5.1.3" + "typedoc": "^0.25.1", + "typescript": "^5.2.2" }, "dependencies": { - "@gnu-taler/taler-util": "workspace:*", "@gnu-taler/anastasis-core": "workspace:*", + "@gnu-taler/taler-util": "workspace:*", "tslib": "^2.5.3" } } diff --git a/packages/anastasis-cli/tsconfig.json b/packages/anastasis-cli/tsconfig.json index 83da34277..7675edfbc 100644 --- a/packages/anastasis-cli/tsconfig.json +++ b/packages/anastasis-cli/tsconfig.json @@ -2,11 +2,11 @@ "compileOnSave": true, "compilerOptions": { "composite": true, - "target": "ES2018", - "module": "ESNext", + "target": "ES2020", + "module": "Node16", "moduleResolution": "Node16", "sourceMap": true, - "lib": ["es6"], + "lib": ["ES2020"], "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "strict": true, diff --git a/packages/anastasis-core/package.json b/packages/anastasis-core/package.json index c3a7106fb..aa182e047 100644 --- a/packages/anastasis-core/package.json +++ b/packages/anastasis-core/package.json @@ -18,7 +18,7 @@ "devDependencies": { "ava": "^4.3.3", "rimraf": "^3.0.2", - "typescript": "^5.1.3" + "typescript": "^5.2.2" }, "dependencies": { "@gnu-taler/taler-util": "workspace:*", diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index 88f3bdbe2..89cf186dd 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -138,7 +138,6 @@ export * as validators from "./validators.js"; export * from "./challenge-feedback-types.js"; const httpLib = createPlatformHttpLib({ - allowHttp: true, enableThrottling: false, }); diff --git a/packages/anastasis-core/tsconfig.json b/packages/anastasis-core/tsconfig.json index 44a159836..a12f2e641 100644 --- a/packages/anastasis-core/tsconfig.json +++ b/packages/anastasis-core/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "target": "ES2020", - "module": "ESNext", + "module": "Node16", "moduleResolution": "Node16", "sourceMap": true, "lib": ["ES2020"], diff --git a/packages/anastasis-webui/package.json b/packages/anastasis-webui/package.json index eff5ba04d..efee9633e 100644 --- a/packages/anastasis-webui/package.json +++ b/packages/anastasis-webui/package.json @@ -44,6 +44,6 @@ "chai": "^4.3.6", "mocha": "^9.2.0", "sass": "1.56.1", - "typescript": "^5.1.3" + "typescript": "^5.2.2" } } diff --git a/packages/anastasis-webui/tsconfig.json b/packages/anastasis-webui/tsconfig.json index 642c34f5a..9e52f2b7e 100644 --- a/packages/anastasis-webui/tsconfig.json +++ b/packages/anastasis-webui/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { /* Basic Options */ - "target": "ES5", - "module": "ES6", + "target": "ES2020", + "module": "Node16", "lib": [ "DOM", - "ES2017" + "ES2020" ], "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ diff --git a/packages/demobank-ui/package.json b/packages/demobank-ui/package.json index a584bbf35..d33fae709 100644 --- a/packages/demobank-ui/package.json +++ b/packages/demobank-ui/package.json @@ -60,7 +60,7 @@ "po2json": "^0.4.5", "preact-render-to-string": "^5.2.6", "sass": "1.56.1", - "typescript": "5.1.3" + "typescript": "5.2.2" }, "pogen": { "domain": "bank" diff --git a/packages/demobank-ui/tsconfig.json b/packages/demobank-ui/tsconfig.json index abb9a9f36..9826fac07 100644 --- a/packages/demobank-ui/tsconfig.json +++ b/packages/demobank-ui/tsconfig.json @@ -1,12 +1,9 @@ { "compilerOptions": { /* Basic Options */ - "target": "ES5", - "module": "ES6", - "lib": [ - "DOM", - "ES2016" - ], + "target": "ES2020", + "module": "Node16", + "lib": ["DOM", "ES2020"], "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, @@ -45,7 +42,5 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */ }, - "include": [ - "src/**/*" - ] -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/idb-bridge/package.json b/packages/idb-bridge/package.json index 1c9c46b02..ce64314ff 100644 --- a/packages/idb-bridge/package.json +++ b/packages/idb-bridge/package.json @@ -29,7 +29,7 @@ "ava": "^5.3.1", "prettier": "^2.8.8", "rimraf": "^5.0.1", - "typescript": "^5.1.6" + "typescript": "^5.2.2" }, "dependencies": { "tslib": "^2.6.0" diff --git a/packages/idb-bridge/src/SqliteBackend.ts b/packages/idb-bridge/src/SqliteBackend.ts index c40281861..a25ec0045 100644 --- a/packages/idb-bridge/src/SqliteBackend.ts +++ b/packages/idb-bridge/src/SqliteBackend.ts @@ -1882,7 +1882,7 @@ export class SqliteBackend implements Backend { } } - clearObjectStore( + async clearObjectStore( btx: DatabaseTransaction, objectStoreName: string, ): Promise { @@ -1906,7 +1906,21 @@ export class SqliteBackend implements Backend { ); } - throw new Error("Method not implemented."); + this._prep(sqlClearObjectStore).run({ + object_store_id: scopeInfo.objectStoreId, + }); + + for (const index of scopeInfo.indexMap.values()) { + let stmt: Sqlite3Statement; + if (index.unique) { + stmt = this._prep(sqlClearUniqueIndexData); + } else { + stmt = this._prep(sqlClearIndexData); + } + stmt.run({ + index_id: index.indexId, + }); + } } } @@ -1963,6 +1977,15 @@ CREATE TABLE IF NOT EXISTS unique_index_data ); `; +const sqlClearObjectStore = ` +DELETE FROM object_data WHERE object_store_id=$object_store_id`; + +const sqlClearIndexData = ` +DELETE FROM index_data WHERE index_id=$index_id`; + +const sqlClearUniqueIndexData = ` +DELETE FROM unique_index_data WHERE index_id=$index_id`; + const sqlListDatabases = ` SELECT name, version FROM databases; `; diff --git a/packages/idb-bridge/src/backend-interface.ts b/packages/idb-bridge/src/backend-interface.ts index 3255261e2..690f92f54 100644 --- a/packages/idb-bridge/src/backend-interface.ts +++ b/packages/idb-bridge/src/backend-interface.ts @@ -144,7 +144,7 @@ export interface IndexMeta { unique: boolean; } -// FIXME: Instead of refering to an object store by name, +// FIXME: Instead of referring to an object store by name, // maybe refer to it via some internal, numeric ID? // This would simplify renaming. export interface Backend { diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts index 8cecba534..f3749c77c 100644 --- a/packages/idb-bridge/src/bridge-idb.ts +++ b/packages/idb-bridge/src/bridge-idb.ts @@ -735,7 +735,9 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { } if (this._closePending) { - throw new InvalidStateError(); + throw new InvalidStateError( + `tried to start transaction on ${this._name}, but a close is pending`, + ); } if (!Array.isArray(storeNames)) { @@ -930,6 +932,9 @@ export class BridgeIDBFactory { // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction for (const otherConn of this.connections) { + if (otherConn._name != db._name) { + continue; + } if (otherConn._closePending) { continue; } diff --git a/packages/idb-bridge/tsconfig.json b/packages/idb-bridge/tsconfig.json index 19e9c2a74..44a27284b 100644 --- a/packages/idb-bridge/tsconfig.json +++ b/packages/idb-bridge/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "composite": true, - "lib": ["es6"], - "module": "ES2020", + "lib": ["ES2020"], + "module": "Node16", "moduleResolution": "Node16", "target": "ES2020", "allowJs": true, diff --git a/packages/merchant-backend-ui/package.json b/packages/merchant-backend-ui/package.json index 23e5007e9..03e64308c 100644 --- a/packages/merchant-backend-ui/package.json +++ b/packages/merchant-backend-ui/package.json @@ -65,6 +65,6 @@ "sirv-cli": "^1.0.11", "ts-node": "^10.9.1", "tslib": "2.5.3", - "typescript": "5.1.3" + "typescript": "5.2.2" } } diff --git a/packages/merchant-backoffice-ui/package.json b/packages/merchant-backoffice-ui/package.json index 3a0c22adb..12aeb4b0e 100644 --- a/packages/merchant-backoffice-ui/package.json +++ b/packages/merchant-backoffice-ui/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@gnu-taler/merchant-backoffice-ui", - "version": "0.0.5", + "version": "0.1.0", "license": "AGPL-3.0-or-later", "type": "module", "scripts": { @@ -41,7 +41,7 @@ "preact": "10.11.3", "preact-router": "3.2.1", "qrcode-generator": "1.4.4", - "swr": "1.3.0", + "swr": "2.2.2", "yup": "^0.32.9" }, "devDependencies": { @@ -75,10 +75,10 @@ "rimraf": "^3.0.2", "sass": "1.56.1", "source-map-support": "^0.5.21", - "typedoc": "^0.24.8", - "typescript": "5.1.3" + "typedoc": "^0.25.1", + "typescript": "5.2.2" }, "pogen": { "domain": "taler-merchant-backoffice" } -} +} \ No newline at end of file diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx index f6a81ff8d..5e82821ae 100644 --- a/packages/merchant-backoffice-ui/src/Application.tsx +++ b/packages/merchant-backoffice-ui/src/Application.tsx @@ -19,19 +19,20 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { HttpStatusCode, LibtoolVersion } from "@gnu-taler/taler-util"; import { ErrorType, TranslationProvider, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; +import { Fragment, VNode, h } from "preact"; import { route } from "preact-router"; -import { useMemo, useState } from "preact/hooks"; +import { useMemo } from "preact/hooks"; import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js"; import { Loading } from "./components/exception/loading.js"; import { - NotificationCard, - NotYetReadyAppMenu, + NotConnectedAppMenu, + NotificationCard } from "./components/menu/index.js"; import { BackendContextProvider, @@ -41,23 +42,24 @@ import { ConfigContextProvider } from "./context/config.js"; import { useBackendConfig } from "./hooks/backend.js"; import { strings } from "./i18n/strings.js"; import LoginPage from "./paths/login/index.js"; -import { HttpStatusCode } from "@gnu-taler/taler-util"; -import { Settings } from "./paths/settings/index.js"; export function Application(): VNode { return ( - // - // ); } +/** + * Check connection testing against /config + * + * @returns + */ function ApplicationStatusRoutes(): VNode { - const { updateLoginStatus, triedToLog } = useBackendContext(); + const { url, updateLoginStatus, triedToLog } = useBackendContext(); const result = useBackendConfig(); const { i18n } = useTranslationContext(); @@ -71,19 +73,10 @@ function ApplicationStatusRoutes(): VNode { : { currency: "unknown", version: "unknown" }; const ctx = useMemo(() => ({ currency, version }), [currency, version]); - const [showSettings, setShowSettings] = useState(false) - - if (showSettings) { - return - setShowSettings(true)} title="UI Settings" /> - - - } - if (!triedToLog) { return ( - setShowSettings(true)} /> + ); @@ -97,7 +90,7 @@ function ApplicationStatusRoutes(): VNode { ) { return ( - setShowSettings(true)} /> + ); @@ -108,7 +101,7 @@ function ApplicationStatusRoutes(): VNode { ) { return ( - setShowSettings(true)} /> + - setShowSettings(true)} /> + - setShowSettings(true)} /> + - setShowSettings(true)} /> + + + + + + + } + return (
diff --git a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx index 277c2b176..46dea98e3 100644 --- a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx +++ b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx @@ -22,7 +22,7 @@ import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser"; import { createHashHistory } from "history"; import { Fragment, h, VNode } from "preact"; import { Router, Route, route } from "preact-router"; -import { useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { NotificationCard, NotYetReadyAppMenu, @@ -35,52 +35,55 @@ import { INSTANCE_ID_LOOKUP } from "./utils/constants.js"; import { HttpStatusCode } from "@gnu-taler/taler-util"; import { Settings } from "./paths/settings/index.js"; +/** + * Check if admin against /management/instances + * @returns + */ export function ApplicationReadyRoutes(): VNode { const { i18n } = useTranslationContext(); + const [unauthorized, setUnauthorized] = useState(false) const { url: backendURL, - updateLoginStatus, - clearAllTokens, + updateLoginStatus: updateLoginStatus2, } = useBackendContext(); + function updateLoginStatus(url: string, token: string | undefined) { + console.log("updateing", url, token) + updateLoginStatus2(url, token) + setUnauthorized(false) + } + const result = useBackendInstancesTestForAdmin(); const clearTokenAndGoToRoot = () => { - clearAllTokens(); route("/"); }; const [showSettings, setShowSettings] = useState(false) + // useEffect(() => { + // setUnauthorized(FF) + // }, [FF]) + const unauthorizedAdmin = !result.loading && !result.ok && result.type === ErrorType.CLIENT && result.status === HttpStatusCode.Unauthorized if (showSettings) { return - setShowSettings(true)} title="UI Settings" onLogout={clearTokenAndGoToRoot} /> - + setShowSettings(true)} title="UI Settings" onLogout={clearTokenAndGoToRoot} isPasswordOk={false} /> + } - if (result.loading) return setShowSettings(true)} title="Loading..." />; - let admin = true; - let instanceNameByBackendURL; + if (result.loading) { + return setShowSettings(true)} title="Loading..." isPasswordOk={false} />; + } - if (!result.ok) { - if ( - result.type === ErrorType.CLIENT && - result.status === HttpStatusCode.Unauthorized - ) { - return ( - - setShowSettings(true)} title="Login" onLogout={clearTokenAndGoToRoot} /> - - - - ); - } + let admin = result.ok || unauthorizedAdmin; + let instanceNameByBackendURL: string | undefined; + + if (!admin) { + // * the testing against admin endpoint failed and it's not + // an authorization problem + // * merchant backend will return this SPA under the main + // endpoint or /instance/ endpoint + // => trying to infer the instance id const path = new URL(backendURL).pathname; const match = INSTANCE_ID_LOOKUP.exec(path); if (!match || !match[1]) { @@ -89,7 +92,7 @@ export function ApplicationReadyRoutes(): VNode { // does not match our pattern return ( - setShowSettings(true)} title="Error" onLogout={clearTokenAndGoToRoot} /> + setShowSettings(true)} title="Error" onLogout={clearTokenAndGoToRoot} isPasswordOk={false} /> + setShowSettings(true)} title="Login" onLogout={clearTokenAndGoToRoot} isPasswordOk={false} /> + + + + } + const history = createHashHistory(); return ( @@ -113,6 +130,11 @@ export function ApplicationReadyRoutes(): VNode { default component={DefaultMainRoute} admin={admin} + onUnauthorized={() => setUnauthorized(true)} + onLoginPass={() => { + console.log("ahora si") + setUnauthorized(false) + }} instanceNameByBackendURL={instanceNameByBackendURL} /> @@ -122,6 +144,8 @@ export function ApplicationReadyRoutes(): VNode { function DefaultMainRoute({ instance, admin, + onUnauthorized, + onLoginPass, instanceNameByBackendURL, url, //from preact-router }: any): VNode { @@ -133,6 +157,8 @@ function DefaultMainRoute({ diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx index 1547442ea..ee8db9a9f 100644 --- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx +++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx @@ -40,6 +40,7 @@ import { import { useInstanceKYCDetails } from "./hooks/instance.js"; import InstanceCreatePage from "./paths/admin/create/index.js"; import InstanceListPage from "./paths/admin/list/index.js"; +import TokenPage from "./paths/instance/token/index.js"; import ListKYCPage from "./paths/instance/kyc/list/index.js"; import OrderCreatePage from "./paths/instance/orders/create/index.js"; import OrderDetailsPage from "./paths/instance/orders/details/index.js"; @@ -47,6 +48,9 @@ import OrderListPage from "./paths/instance/orders/list/index.js"; import ProductCreatePage from "./paths/instance/products/create/index.js"; import ProductListPage from "./paths/instance/products/list/index.js"; import ProductUpdatePage from "./paths/instance/products/update/index.js"; +import BankAccountCreatePage from "./paths/instance/accounts/create/index.js"; +import BankAccountListPage from "./paths/instance/accounts/list/index.js"; +import BankAccountUpdatePage from "./paths/instance/accounts/update/index.js"; import ReservesCreatePage from "./paths/instance/reserves/create/index.js"; import ReservesDetailsPage from "./paths/instance/reserves/details/index.js"; import ReservesListPage from "./paths/instance/reserves/list/index.js"; @@ -58,6 +62,9 @@ import TemplateUpdatePage from "./paths/instance/templates/update/index.js"; 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 ValidatorCreatePage from "./paths/instance/validators/create/index.js"; +import ValidatorListPage from "./paths/instance/validators/list/index.js"; +import ValidatorUpdatePage from "./paths/instance/validators/update/index.js"; import TransferCreatePage from "./paths/instance/transfers/create/index.js"; import TransferListPage from "./paths/instance/transfers/list/index.js"; import InstanceUpdatePage, { @@ -69,11 +76,16 @@ import NotFoundPage from "./paths/notfound/index.js"; import { Notification } from "./utils/types.js"; import { MerchantBackend } from "./declaration.js"; import { Settings } from "./paths/settings/index.js"; +import { dateFormatForSettings, useSettings } from "./hooks/useSettings.js"; export enum InstancePaths { - // details = '/', error = "/error", - update = "/update", + server = "/server", + token = "/token", + + bank_list = "/bank", + bank_update = "/bank/:bid/update", + bank_new = "/bank/new", product_list = "/products", product_update = "/product/:pid/update", @@ -102,11 +114,15 @@ export enum InstancePaths { webhooks_update = "/webhooks/:tid/update", webhooks_new = "/webhooks/new", - settings = "/settings", + validators_list = "/validators", + validators_update = "/validators/:vid/update", + validators_new = "/validators/new", + + settings = "/interface", } // eslint-disable-next-line @typescript-eslint/no-empty-function -const noop = () => {}; +const noop = () => { }; export enum AdminPaths { list_instances = "/instances", @@ -118,6 +134,8 @@ export interface Props { id: string; admin?: boolean; path: string; + onUnauthorized: () => void; + onLoginPass: () => void; setInstanceName: (s: string) => void; } @@ -125,40 +143,29 @@ export function InstanceRoutes({ id, admin, path, + onUnauthorized, + onLoginPass, setInstanceName, }: Props): VNode { - const [_, updateDefaultToken] = useBackendDefaultToken(); + const [defaultToken, updateDefaultToken] = useBackendDefaultToken(); const [token, updateToken] = useBackendInstanceToken(id); - const { - updateLoginStatus: changeBackend, - addTokenCleaner, - clearAllTokens, - } = useBackendContext(); - const cleaner = useCallback(() => { - updateToken(undefined); - }, [id]); const { i18n } = useTranslationContext(); type GlobalNotifState = (Notification & { to: string }) | undefined; const [globalNotification, setGlobalNotification] = useState(undefined); - useEffect(() => { - addTokenCleaner(cleaner); - }, [addTokenCleaner, cleaner]); - const changeToken = (token?: string) => { if (admin) { updateToken(token); } else { updateDefaultToken(token); } + onLoginPass() }; - const updateLoginStatus = (url: string, token?: string) => { - changeBackend(url); - if (!token) return; - changeToken(token); - }; + // const updateLoginStatus = (url: string, token?: string) => { + // changeToken(token); + // }; const value = useMemo( () => ({ id, token, admin, changeToken }), @@ -192,18 +199,17 @@ export function InstanceRoutes({ }; } - const LoginPageAccessDenied = () => ( - - - - - ); + // const LoginPageAccessDeniend = onUnauthorized + const LoginPageAccessDenied = () => { + onUnauthorized() + return + } function IfAdminCreateDefaultOr(Next: FunctionComponent) { return function IfAdminCreateDefaultOrImpl(props?: T) { @@ -234,8 +240,10 @@ export function InstanceRoutes({ } const clearTokenAndGoToRoot = () => { - clearAllTokens(); route("/"); + // clear all tokens + updateToken(undefined) + updateDefaultToken(undefined) }; return ( @@ -244,11 +252,12 @@ export function InstanceRoutes({ instance={id} admin={admin} onShowSettings={() => { - route("/settings") + route(InstancePaths.settings) }} path={path} onLogout={clearTokenAndGoToRoot} setInstanceName={setInstanceName} + isPasswordOk={defaultToken !== undefined} /> @@ -308,7 +317,7 @@ export function InstanceRoutes({ * Update instance page */} { route(`/`); @@ -321,6 +330,19 @@ export function InstanceRoutes({ onUnauthorized={LoginPageAccessDenied} onLoadError={ServerErrorRedirectTo(InstancePaths.error)} /> + {/** + * Update instance page + */} + { + route(`/`); + }} + onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} + onUnauthorized={LoginPageAccessDenied} + onLoadError={ServerErrorRedirectTo(InstancePaths.error)} + /> {/** * Product pages */} @@ -328,7 +350,7 @@ export function InstanceRoutes({ path={InstancePaths.product_list} component={ProductListPage} onUnauthorized={LoginPageAccessDenied} - onLoadError={ServerErrorRedirectTo(InstancePaths.update)} + onLoadError={ServerErrorRedirectTo(InstancePaths.server)} onCreate={() => { route(InstancePaths.product_new); }} @@ -360,6 +382,45 @@ export function InstanceRoutes({ route(InstancePaths.product_list); }} /> + {/** + * Bank pages + */} + { + route(InstancePaths.bank_new); + }} + onSelect={(id: string) => { + route(InstancePaths.bank_update.replace(":bid", id)); + }} + onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} + /> + { + route(InstancePaths.bank_list); + }} + onBack={() => { + route(InstancePaths.bank_list); + }} + onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} + /> + { + route(InstancePaths.bank_list); + }} + onBack={() => { + route(InstancePaths.bank_list); + }} + /> {/** * Order pages */} @@ -373,7 +434,7 @@ export function InstanceRoutes({ route(InstancePaths.order_details.replace(":oid", id)); }} onUnauthorized={LoginPageAccessDenied} - onLoadError={ServerErrorRedirectTo(InstancePaths.update)} + onLoadError={ServerErrorRedirectTo(InstancePaths.server)} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} /> { - route(InstancePaths.order_list); + onConfirm={(orderId: string) => { + route(InstancePaths.order_details.replace(":oid", orderId)); }} onBack={() => { route(InstancePaths.order_list); @@ -404,7 +465,7 @@ export function InstanceRoutes({ component={TransferListPage} onUnauthorized={LoginPageAccessDenied} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} - onLoadError={ServerErrorRedirectTo(InstancePaths.update)} + onLoadError={ServerErrorRedirectTo(InstancePaths.server)} onCreate={() => { route(InstancePaths.transfers_new); }} @@ -427,7 +488,7 @@ export function InstanceRoutes({ component={WebhookListPage} onUnauthorized={LoginPageAccessDenied} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} - onLoadError={ServerErrorRedirectTo(InstancePaths.update)} + onLoadError={ServerErrorRedirectTo(InstancePaths.server)} onCreate={() => { route(InstancePaths.webhooks_new); }} @@ -458,6 +519,45 @@ export function InstanceRoutes({ route(InstancePaths.webhooks_list); }} /> + {/** + * Validator pages + */} + { + route(InstancePaths.validators_new); + }} + onSelect={(id: string) => { + route(InstancePaths.validators_update.replace(":vid", id)); + }} + /> + { + route(InstancePaths.validators_list); + }} + onUnauthorized={LoginPageAccessDenied} + onLoadError={ServerErrorRedirectTo(InstancePaths.validators_list)} + onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} + onBack={() => { + route(InstancePaths.validators_list); + }} + /> + { + route(InstancePaths.validators_list); + }} + onBack={() => { + route(InstancePaths.validators_list); + }} + /> {/** * Templates pages */} @@ -466,7 +566,7 @@ export function InstanceRoutes({ component={TemplateListPage} onUnauthorized={LoginPageAccessDenied} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} - onLoadError={ServerErrorRedirectTo(InstancePaths.update)} + onLoadError={ServerErrorRedirectTo(InstancePaths.server)} onCreate={() => { route(InstancePaths.templates_new); }} @@ -535,7 +635,7 @@ export function InstanceRoutes({ component={ReservesListPage} onUnauthorized={LoginPageAccessDenied} onNotFound={IfAdminCreateDefaultOr(NotFoundPage)} - onLoadError={ServerErrorRedirectTo(InstancePaths.update)} + onLoadError={ServerErrorRedirectTo(InstancePaths.server)} onSelect={(id: string) => { route(InstancePaths.reserves_details.replace(":rid", id)); }} @@ -590,7 +690,7 @@ function AdminInstanceUpdatePage({ const { updateLoginStatus: changeBackend } = useBackendContext(); const updateLoginStatus = (url: string, token?: string): void => { changeBackend(url); - if (token) changeToken(token); + changeToken(token); }; const value = useMemo( () => ({ id, token, admin: true, changeToken }), @@ -607,20 +707,20 @@ function AdminInstanceUpdatePage({ const notif = error.type === ErrorType.TIMEOUT ? { - message: i18n.str`The request to the backend take too long and was cancelled`, - description: i18n.str`Diagnostic from ${error.info.url} is '${error.message}'`, - type: "ERROR" as const, - } + message: i18n.str`The request to the backend take too long and was cancelled`, + description: i18n.str`Diagnostic from ${error.info.url} is '${error.message}'`, + type: "ERROR" as const, + } : { - message: i18n.str`The backend reported a problem: HTTP status #${error.status}`, - description: i18n.str`Diagnostic from ${error.info.url} is '${error.message}'`, - details: - error.type === ErrorType.CLIENT || + message: i18n.str`The backend reported a problem: HTTP status #${error.status}`, + description: i18n.str`Diagnostic from ${error.info.url} is '${error.message}'`, + details: + error.type === ErrorType.CLIENT || error.type === ErrorType.SERVER - ? error.payload.detail - : undefined, - type: "ERROR" as const, - }; + ? error.payload.detail + : undefined, + type: "ERROR" as const, + }; return ( @@ -650,7 +750,8 @@ function AdminInstanceUpdatePage({ function KycBanner(): VNode { const kycStatus = useInstanceKYCDetails(); const { i18n } = useTranslationContext(); - const today = format(new Date(), "yyyy-MM-dd"); + const [settings] = useSettings(); + const today = format(new Date(), dateFormatForSettings(settings)); const [lastHide, setLastHide] = useLocalStorage("kyc-last-hide"); const hasBeenHidden = today === lastHide; const needsToBeShown = kycStatus.ok && kycStatus.data.type === "redirect"; diff --git a/packages/merchant-backoffice-ui/src/components/exception/login.tsx b/packages/merchant-backoffice-ui/src/components/exception/login.tsx index f2f94a7c5..4fa440fc7 100644 --- a/packages/merchant-backoffice-ui/src/components/exception/login.tsx +++ b/packages/merchant-backoffice-ui/src/components/exception/login.tsx @@ -93,7 +93,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { e.keyCode === 13 @@ -186,7 +186,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { e.keyCode === 13 diff --git a/packages/merchant-backoffice-ui/src/components/form/InputDate.tsx b/packages/merchant-backoffice-ui/src/components/form/InputDate.tsx index 1f41c3564..a398629dc 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputDate.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputDate.tsx @@ -20,16 +20,18 @@ */ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { format } from "date-fns"; -import { h, VNode } from "preact"; +import { ComponentChildren, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { DatePicker } from "../picker/DatePicker.js"; import { InputProps, useField } from "./useField.js"; +import { dateFormatForSettings, useSettings } from "../../hooks/useSettings.js"; export interface Props extends InputProps { readonly?: boolean; expand?: boolean; //FIXME: create separated components InputDate and InputTimestamp withTimestampSupport?: boolean; + side?: ComponentChildren; } export function InputDate({ @@ -41,9 +43,11 @@ export function InputDate({ tooltip, expand, withTimestampSupport, + side, }: Props): VNode { const [opened, setOpened] = useState(false); const { i18n } = useTranslationContext(); + const [settings] = useSettings() const { error, required, value, onChange } = useField(name); @@ -51,14 +55,14 @@ export function InputDate({ if (!value) { strValue = withTimestampSupport ? "unknown" : ""; } else if (value instanceof Date) { - strValue = format(value, "yyyy/MM/dd"); + strValue = format(value, dateFormatForSettings(settings)); } else if (value.t_s) { strValue = value.t_s === "never" ? withTimestampSupport ? "never" : "" - : format(new Date(value.t_s * 1000), "yyyy/MM/dd"); + : format(new Date(value.t_s * 1000), dateFormatForSettings(settings)); } return ( @@ -142,6 +146,7 @@ export function InputDate({ )} + {side}
extends InputProps { isValid?: (e: any) => boolean; } +// type Entity = PaytoUriGeneric // https://datatracker.ietf.org/doc/html/rfc8905 type Entity = { // iban, bitcoin, x-taler-bank. it defined the format target: string; // path1 if the first field to be used - path1: string; + path1?: string; // path2 if the second field to be used, optional path2?: string; - // options of the payto uri - options: { + // params of the payto uri + params: { "receiver-name"?: string; sender?: string; message?: string; @@ -52,13 +52,6 @@ type Entity = { instruction?: string; [name: string]: string | undefined; }; - auth: { - type: "unset" | "basic" | "none"; - url?: string; - username?: string; - password?: string; - repeat?: string; - }; }; function isEthereumAddress(address: string) { @@ -171,14 +164,10 @@ const targets = [ "bitcoin", "ethereum", ]; -const accountAuthType = ["none", "basic"]; const noTargetValue = targets[0]; -const defaultTarget: Partial = { +const defaultTarget: Entity = { target: noTargetValue, - options: {}, - auth: { - type: "unset" as const, - }, + params: {}, }; export function InputPaytoForm({ @@ -187,110 +176,91 @@ export function InputPaytoForm({ label, tooltip, }: Props): VNode { - const { value: paytos, onChange, required } = useField(name); + const { value: initialValueStr, onChange } = useField(name); - const [value, valueHandler] = useState>(defaultTarget); - - let payToPath; - if (value.target === "iban" && value.path1) { - payToPath = `/${value.path1.toUpperCase()}`; - } else if (value.path1) { - if (value.path2) { - payToPath = `/${value.path1}/${value.path2}`; - } else { - payToPath = `/${value.path1}`; - } + const initialPayto = parsePaytoUri(initialValueStr ?? "") + const paths = !initialPayto ? [] : initialPayto.targetPath.split("/") + const initialPath1 = paths.length >= 1 ? paths[0] : undefined; + const initialPath2 = paths.length >= 2 ? paths[1] : undefined; + const initial: Entity = initialPayto === undefined ? defaultTarget : { + target: initialPayto.targetType, + params: initialPayto.params, + path1: initialPath1, + path2: initialPath2, } + const [value, setValue] = useState>(initial) + const { i18n } = useTranslationContext(); - const ops = value.options ?? {}; - const url = tryUrl(`payto://${value.target}${payToPath}`); - if (url) { - Object.keys(ops).forEach((opt_key) => { - const opt_value = ops[opt_key]; - if (opt_value) url.searchParams.set(opt_key, opt_value); - }); - } - const paytoURL = !url ? "" : url.href; - const errors: FormErrors = { target: - value.target === noTargetValue && !paytos.length + value.target === noTargetValue ? i18n.str`required` : undefined, path1: !value.path1 ? i18n.str`required` : value.target === "iban" - ? validateIBAN(value.path1, i18n) - : value.target === "bitcoin" - ? validateBitcoin(value.path1, i18n) - : value.target === "ethereum" - ? validateEthereum(value.path1, i18n) - : undefined, + ? validateIBAN(value.path1, i18n) + : value.target === "bitcoin" + ? validateBitcoin(value.path1, i18n) + : value.target === "ethereum" + ? validateEthereum(value.path1, i18n) + : undefined, path2: value.target === "x-taler-bank" ? !value.path2 ? i18n.str`required` : undefined : undefined, - options: undefinedIfEmpty({ - "receiver-name": !value.options?.["receiver-name"] + params: undefinedIfEmpty({ + "receiver-name": !value.params?.["receiver-name"] ? i18n.str`required` : undefined, }), - auth: !value.auth - ? undefined - : undefinedIfEmpty({ - username: - value.auth.type === "basic" && !value.auth.username - ? i18n.str`required` - : undefined, - password: - value.auth.type === "basic" && !value.auth.password - ? i18n.str`required` - : undefined, - repeat: - value.auth.type === "basic" && !value.auth.repeat - ? i18n.str`required` - : value.auth.repeat !== value.auth.password - ? i18n.str`is not the same` - : undefined, - }), }; const hasErrors = Object.keys(errors).some( (k) => (errors as any)[k] !== undefined, ); + const str = hasErrors || !value.target ? undefined : stringifyPaytoUri({ + targetType: value.target, + targetPath: value.path2 ? `${value.path1}/${value.path2}` : (value.path1 ?? ""), + params: value.params ?? {} as any, + isKnown: false, + }) + useEffect(() => { + onChange(str as any) + }, [str]) - const submit = useCallback((): void => { - const accounts: MerchantBackend.Instances.MerchantBankAccount[] = paytos; - const alreadyExists = - accounts.findIndex((x) => x.payto_uri === paytoURL) !== -1; - if (!alreadyExists) { - const newValue: MerchantBackend.Instances.MerchantBankAccount = { - payto_uri: paytoURL, - }; - if (value.auth) { - if (value.auth.url) { - newValue.credit_facade_url = value.auth.url; - } - if (value.auth.type === "none") { - newValue.credit_facade_credentials = { - type: "none", - }; - } - if (value.auth.type === "basic") { - newValue.credit_facade_credentials = { - type: "basic", - username: value.auth.username ?? "", - password: value.auth.password ?? "", - }; - } - } - onChange([newValue, ...accounts] as any); - } - valueHandler(defaultTarget); - }, [value]); + // const submit = useCallback((): void => { + // // const accounts: MerchantBackend.BankAccounts.AccountAddDetails[] = paytos; + // // const alreadyExists = + // // accounts.findIndex((x) => x.payto_uri === paytoURL) !== -1; + // // if (!alreadyExists) { + // const newValue: MerchantBackend.BankAccounts.AccountAddDetails = { + // payto_uri: paytoURL, + // }; + // if (value.auth) { + // if (value.auth.url) { + // newValue.credit_facade_url = value.auth.url; + // } + // if (value.auth.type === "none") { + // newValue.credit_facade_credentials = { + // type: "none", + // }; + // } + // if (value.auth.type === "basic") { + // newValue.credit_facade_credentials = { + // type: "basic", + // username: value.auth.username ?? "", + // password: value.auth.password ?? "", + // }; + // } + // } + // onChange(newValue as any); + // // } + // // valueHandler(defaultTarget); + // }, [value]); //FIXME: translating plural singular return ( @@ -299,11 +269,11 @@ export function InputPaytoForm({ name="tax" errors={errors} object={value} - valueHandler={valueHandler} + valueHandler={setValue} > name="target" - label={i18n.str`Target type`} + label={i18n.str`Account type`} tooltip={i18n.str`Method to use for wire transfer`} values={targets} toStr={(v) => (v === noTargetValue ? i18n.str`Choose one...` : v)} @@ -400,150 +370,15 @@ export function InputPaytoForm({ {value.target !== noTargetValue && ( - - { - // if (str === "unset") { - // return "Without change"; - // } - if (str === "none") return "Without authentication"; - return "Username and password"; - }} - /> - {value.auth?.type === "basic" ? ( - - - - - - ) : undefined} - - {/* v.toUpperCase()} - addonAfter={ - - {showKey ? ( - - ) : ( - - )} - - } - side={ - - - - } - /> */} )} - {/** - * Show the values in the list - */} -
- - {value.target !== noTargetValue && ( -
- -
- )} ); } -function tryUrl(s: string): URL | undefined { - try { - return new URL(s); - } catch (e) { - return undefined; - } -} diff --git a/packages/merchant-backoffice-ui/src/components/form/InputSearchProduct.tsx b/packages/merchant-backoffice-ui/src/components/form/InputSearchOnList.tsx similarity index 65% rename from packages/merchant-backoffice-ui/src/components/form/InputSearchProduct.tsx rename to packages/merchant-backoffice-ui/src/components/form/InputSearchOnList.tsx index 1c1fcb907..be5800d14 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputSearchProduct.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputSearchOnList.tsx @@ -22,32 +22,41 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import emptyImage from "../../assets/empty.png"; -import { MerchantBackend, WithId } from "../../declaration.js"; import { FormErrors, FormProvider } from "./FormProvider.js"; import { InputWithAddon } from "./InputWithAddon.js"; +import { TranslatedString } from "@gnu-taler/taler-util"; -type Entity = MerchantBackend.Products.ProductDetail & WithId; +type Entity = { + id: string, + description: string; + image?: string; + extra?: string; +}; -export interface Props { - selected?: Entity; - onChange: (p?: Entity) => void; - products: (MerchantBackend.Products.ProductDetail & WithId)[]; +export interface Props { + selected?: T; + onChange: (p?: T) => void; + label: TranslatedString; + list: T[]; + withImage?: boolean; } -interface ProductSearch { +interface Search { name: string; } -export function InputSearchProduct({ +export function InputSearchOnList({ selected, onChange, - products, -}: Props): VNode { - const [prodForm, setProdName] = useState>({ + label, + list, + withImage, +}: Props): VNode { + const [nameForm, setNameForm] = useState>({ name: "", }); - const errors: FormErrors = { + const errors: FormErrors = { name: undefined, }; const { i18n } = useTranslationContext(); @@ -55,15 +64,17 @@ export function InputSearchProduct({ if (selected) { return (
-
-

- -

-
+ {withImage && +
+

+ +

+
+ }

- Product id: {selected.id} + ID: {selected.id}

Description:{" "} @@ -84,15 +95,15 @@ export function InputSearchProduct({ } return ( - + errors={errors} - object={prodForm} - valueHandler={setProdName} + object={nameForm} + valueHandler={setNameForm} > - + name="name" - label={i18n.str`Product`} - tooltip={i18n.str`search products by it's description or id`} + label={label} + tooltip={i18n.str`enter description or id`} addonAfter={ @@ -100,13 +111,14 @@ export function InputSearchProduct({ } >

- { - setProdName({ name: "" }); + setNameForm({ name: "" }); onChange(p); }} + withImage={!!withImage} />
@@ -114,13 +126,14 @@ export function InputSearchProduct({ ); } -interface ProductListProps { +interface DropdownListProps { name?: string; - onSelect: (p: MerchantBackend.Products.ProductDetail & WithId) => void; - list: (MerchantBackend.Products.ProductDetail & WithId)[]; + onSelect: (p: T) => void; + list: T[]; + withImage: boolean; } -function ProductList({ name, onSelect, list }: ProductListProps) { +function DropdownList({ name, onSelect, list, withImage }: DropdownListProps) { const { i18n } = useTranslationContext(); if (!name) { /* FIXME @@ -149,7 +162,7 @@ function ProductList({ name, onSelect, list }: ProductListProps) { {!filtered.length ? ( ) : ( @@ -161,18 +174,20 @@ function ProductList({ name, onSelect, list }: ProductListProps) { style={{ cursor: "pointer" }} >
-
-
- + {withImage && +
+
+ +
-
+ }

- {p.id} {p.price} + {p.id} {p.extra !== undefined ? {p.extra} : undefined}
{p.description}

diff --git a/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx index 61ddf3c84..f95dfcd05 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx @@ -56,7 +56,7 @@ export function InputToggle({ return (
-
-
+

diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx index cdbae4ae0..cb318906f 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx @@ -24,7 +24,7 @@ import { Sidebar } from "./SideBar.js"; function getInstanceTitle(path: string, id: string): string { switch (path) { - case InstancePaths.update: + case InstancePaths.server: return `${id}: Settings`; case InstancePaths.order_list: return `${id}: Orders`; @@ -50,6 +50,12 @@ function getInstanceTitle(path: string, id: string): string { return `${id}: New webhook`; case InstancePaths.webhooks_update: return `${id}: Update webhook`; + case InstancePaths.validators_list: + return `${id}: Validators`; + case InstancePaths.validators_new: + return `${id}: New validator`; + case InstancePaths.validators_update: + return `${id}: Update validators`; case InstancePaths.templates_new: return `${id}: New template`; case InstancePaths.templates_update: @@ -58,6 +64,10 @@ function getInstanceTitle(path: string, id: string): string { return `${id}: Templates`; case InstancePaths.templates_use: return `${id}: Use template`; + case InstancePaths.settings: + return `${id}: Interface`; + case InstancePaths.settings: + return `${id}: Interface`; default: return ""; } @@ -77,6 +87,7 @@ interface MenuProps { onLogout?: () => void; onShowSettings: () => void; setInstanceName: (s: string) => void; + isPasswordOk: boolean; } function WithTitle({ @@ -100,14 +111,15 @@ export function Menu({ path, admin, setInstanceName, + isPasswordOk }: MenuProps): VNode { const [mobileOpen, setMobileOpen] = useState(false); const titleWithSubtitle = title ? title : !admin - ? getInstanceTitle(path, instance) - : getAdminTitle(path, instance); + ? getInstanceTitle(path, instance) + : getAdminTitle(path, instance); const adminInstance = instance === "default"; const mimic = admin && !adminInstance; return ( @@ -129,14 +141,15 @@ export function Menu({ mimic={mimic} instance={instance} mobile={mobileOpen} + isPasswordOk={isPasswordOk} /> )} {mimic && (