diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/Application.tsx')
-rw-r--r-- | packages/merchant-backoffice-ui/src/Application.tsx | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx new file mode 100644 index 000000000..097e98567 --- /dev/null +++ b/packages/merchant-backoffice-ui/src/Application.tsx @@ -0,0 +1,364 @@ +/* + 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 { + CacheEvictor, + TalerMerchantApi, + TalerMerchantInstanceCacheEviction, + TalerMerchantManagementCacheEviction, + assertUnreachable, + canonicalizeBaseUrl, +} from "@gnu-taler/taler-util"; +import { + BrowserHashNavigationProvider, + ConfigResultFail, + MerchantApiProvider, + TalerWalletIntegrationBrowserProvider, + TranslationProvider, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { VNode, h } from "preact"; +import { useEffect, useState } from "preact/hooks"; +import { SWRConfig } from "swr"; +import { Routing } from "./Routing.js"; +import { Loading } from "./components/exception/loading.js"; +import { NotificationCard } from "./components/menu/index.js"; +import { SettingsProvider } from "./context/settings.js"; +import { + revalidateBankAccountDetails, + revalidateInstanceBankAccounts, +} from "./hooks/bank.js"; +import { + revalidateBackendInstances, + revalidateInstanceDetails, + revalidateManagedInstanceDetails, +} from "./hooks/instance.js"; +import { + revalidateInstanceOtpDevices, + revalidateOtpDeviceDetails, +} from "./hooks/otp.js"; +import { + revalidateInstanceProducts, + revalidateProductDetails, +} from "./hooks/product.js"; +import { + revalidateInstanceTemplates, + revalidateTemplateDetails, +} from "./hooks/templates.js"; +import { revalidateInstanceTransfers } from "./hooks/transfer.js"; +import { + revalidateInstanceWebhooks, + revalidateWebhookDetails, +} from "./hooks/webhooks.js"; +import { strings } from "./i18n/strings.js"; +import { + MerchantUiSettings, + buildDefaultBackendBaseURL, + fetchSettings, +} from "./settings.js"; +import { + revalidateInstanceOrders, + revalidateOrderDetails, +} from "./hooks/order.js"; +import { SessionContextProvider } from "./context/session.js"; +const WITH_LOCAL_STORAGE_CACHE = false; + +export function Application(): VNode { + const [settings, setSettings] = useState<MerchantUiSettings>(); + useEffect(() => { + fetchSettings(setSettings); + }, []); + if (!settings) return <Loading />; + + const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL); + return ( + <SettingsProvider value={settings}> + <TranslationProvider + source={strings} + completeness={{ + es: strings["es"].completeness, + de: strings["de"].completeness, + }} + > + <MerchantApiProvider + baseUrl={new URL("./", baseUrl)} + frameOnError={OnConfigError} + evictors={{ + management: swrCacheEvictor, + }} + > + <SessionContextProvider> + <SWRConfig + value={{ + provider: WITH_LOCAL_STORAGE_CACHE + ? localStorageProvider + : undefined, + // normally, do not revalidate + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + revalidateOnMount: undefined, + focusThrottleInterval: undefined, + + // normally, do not refresh + refreshInterval: undefined, + dedupingInterval: 2000, + refreshWhenHidden: false, + refreshWhenOffline: false, + + // ignore errors + shouldRetryOnError: false, + errorRetryCount: 0, + errorRetryInterval: undefined, + + // do not go to loading again if already has data + keepPreviousData: true, + }} + > + <TalerWalletIntegrationBrowserProvider> + <BrowserHashNavigationProvider> + <Routing /> + </BrowserHashNavigationProvider> + </TalerWalletIntegrationBrowserProvider> + </SWRConfig> + </SessionContextProvider> + </MerchantApiProvider> + </TranslationProvider> + </SettingsProvider> + ); +} + +function getInitialBackendBaseURL( + backendFromSettings: string | undefined, +): string { + /** + * For testing purpose + */ + const overrideUrl = + typeof localStorage !== "undefined" + ? localStorage.getItem("merchant-base-url") + : undefined; + let result: string; + + if (overrideUrl) { + // testing/development path + result = overrideUrl; + } else { + // normal path + if (!backendFromSettings) { + console.error( + "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'", + ); + result = buildDefaultBackendBaseURL(); + } else { + result = backendFromSettings; + } + } + try { + return canonicalizeBaseUrl(result); + } catch (e) { + // fall back + return canonicalizeBaseUrl(window.origin); + } +} + +function localStorageProvider(): Map<unknown, unknown> { + const map = new Map(JSON.parse(localStorage.getItem("app-cache") || "[]")); + + window.addEventListener("beforeunload", () => { + const appCache = JSON.stringify(Array.from(map.entries())); + localStorage.setItem("app-cache", appCache); + }); + return map; +} + +function OnConfigError({ + state, +}: { + state: ConfigResultFail<TalerMerchantApi.VersionResponse> | undefined; +}): VNode { + const { i18n } = useTranslationContext(); + if (!state) { + return ( + <i18n.Translate>checking compatibility with server...</i18n.Translate> + ); + } + switch (state.type) { + case "error": { + return ( + <NotificationCard + notification={{ + message: i18n.str`Contacting the server failed`, + description: state.error.message, + details: JSON.stringify(state.error.errorDetail, undefined, 2), + type: "ERROR", + }} + /> + ); + } + case "incompatible": { + return ( + <NotificationCard + notification={{ + message: i18n.str`The server version is not supported`, + description: i18n.str`Supported version "${state.supported}", server version "${state.result.version}".`, + type: "WARN", + }} + /> + ); + } + default: + assertUnreachable(state); + } +} + +const swrCacheEvictor = new (class + implements + CacheEvictor< + TalerMerchantManagementCacheEviction | TalerMerchantInstanceCacheEviction + > +{ + async notifySuccess( + op: + | TalerMerchantManagementCacheEviction + | TalerMerchantInstanceCacheEviction, + ) { + switch (op) { + case TalerMerchantManagementCacheEviction.CREATE_INSTANCE: { + await Promise.all([revalidateBackendInstances()]); + return; + } + case TalerMerchantManagementCacheEviction.UPDATE_INSTANCE: { + await Promise.all([revalidateManagedInstanceDetails()]); + return; + } + case TalerMerchantManagementCacheEviction.DELETE_INSTANCE: { + await Promise.all([revalidateBackendInstances()]); + return; + } + case TalerMerchantInstanceCacheEviction.UPDATE_CURRENT_INSTANCE: { + await Promise.all([revalidateInstanceDetails()]); + return; + } + case TalerMerchantInstanceCacheEviction.DELETE_CURRENT_INSTANCE: { + await Promise.all([revalidateInstanceDetails()]); + return; + } + case TalerMerchantInstanceCacheEviction.CREATE_BANK_ACCOUNT: { + await Promise.all([revalidateInstanceBankAccounts()]); + return; + } + case TalerMerchantInstanceCacheEviction.UPDATE_BANK_ACCOUNT: { + await Promise.all([revalidateBankAccountDetails()]); + return; + } + case TalerMerchantInstanceCacheEviction.DELETE_BANK_ACCOUNT: { + await Promise.all([revalidateInstanceBankAccounts()]); + return; + } + case TalerMerchantInstanceCacheEviction.CREATE_PRODUCT: { + await Promise.all([revalidateInstanceProducts()]); + return; + } + case TalerMerchantInstanceCacheEviction.UPDATE_PRODUCT: { + await Promise.all([revalidateProductDetails()]); + return; + } + case TalerMerchantInstanceCacheEviction.DELETE_PRODUCT: { + await Promise.all([revalidateInstanceProducts()]); + return; + } + case TalerMerchantInstanceCacheEviction.CREATE_TRANSFER: { + await Promise.all([revalidateInstanceTransfers()]); + return; + } + case TalerMerchantInstanceCacheEviction.DELETE_TRANSFER: { + await Promise.all([revalidateInstanceTransfers()]); + return; + } + case TalerMerchantInstanceCacheEviction.CREATE_DEVICE: { + await Promise.all([revalidateInstanceOtpDevices()]); + return; + } + case TalerMerchantInstanceCacheEviction.UPDATE_DEVICE: { + await Promise.all([revalidateOtpDeviceDetails()]); + return; + } + case TalerMerchantInstanceCacheEviction.DELETE_DEVICE: { + await Promise.all([revalidateInstanceOtpDevices()]); + return; + } + case TalerMerchantInstanceCacheEviction.CREATE_TEMPLATE: { + await Promise.all([revalidateInstanceTemplates()]); + return; + } + case TalerMerchantInstanceCacheEviction.UPDATE_TEMPLATE: { + await Promise.all([revalidateTemplateDetails()]); + return; + } + case TalerMerchantInstanceCacheEviction.DELETE_TEMPLATE: { + await Promise.all([revalidateInstanceTemplates()]); + return; + } + case TalerMerchantInstanceCacheEviction.CREATE_WEBHOOK: { + await Promise.all([revalidateInstanceWebhooks()]); + return; + } + case TalerMerchantInstanceCacheEviction.UPDATE_WEBHOOK: { + await Promise.all([revalidateWebhookDetails()]); + return; + } + case TalerMerchantInstanceCacheEviction.DELETE_WEBHOOK: { + await Promise.all([revalidateInstanceWebhooks()]); + return; + } + case TalerMerchantInstanceCacheEviction.CREATE_ORDER: { + await Promise.all([revalidateInstanceOrders()]); + return; + } + case TalerMerchantInstanceCacheEviction.UPDATE_ORDER: { + await Promise.all([revalidateOrderDetails()]); + return; + } + case TalerMerchantInstanceCacheEviction.DELETE_ORDER: { + await Promise.all([revalidateInstanceOrders()]); + return; + } + case TalerMerchantInstanceCacheEviction.LAST: + // case TalerMerchantInstanceCacheEviction.CREATE_TOKENFAMILY:{ + // await Promise.all([ + // reva + // ]) + // return + // } + // case TalerMerchantInstanceCacheEviction.UPDATE_TOKENFAMILY:{ + // await Promise.all([ + // ]) + // return + // } + // case TalerMerchantInstanceCacheEviction.DELETE_TOKENFAMILY:{ + // await Promise.all([ + // ]) + // return + // } + } + } +})(); |