diff options
Diffstat (limited to 'packages')
25 files changed, 251 insertions, 230 deletions
diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs index 8b04155f4..c5ea318e7 100755 --- a/packages/demobank-ui/dev.mjs +++ b/packages/demobank-ui/dev.mjs @@ -18,13 +18,13 @@ import { serve } from "@gnu-taler/web-util/node"; import { initializeDev } from "@gnu-taler/web-util/build"; -const devEntryPoints = ["src/stories.tsx", "src/index.tsx", "src/bank-ui-settings.js"]; +const devEntryPoints = ["src/stories.tsx", "src/index.tsx"]; const build = initializeDev({ type: "development", source: { js: devEntryPoints, - assets: [{ base: "src", files: ["src/index.html"] }], + assets: [{ base: "src", files: ["src/index.html", "src/settings.json"] }], }, destination: "./dist/dev", public: "/app", diff --git a/packages/demobank-ui/src/Routing.tsx b/packages/demobank-ui/src/Routing.tsx index d797a837d..733d55a0f 100644 --- a/packages/demobank-ui/src/Routing.tsx +++ b/packages/demobank-ui/src/Routing.tsx @@ -19,6 +19,7 @@ import { createHashHistory } from "history"; import { Fragment, VNode, h } from "preact"; import { Route, Router, route } from "preact-router"; import { useEffect } from "preact/hooks"; + import { useBackendState } from "./hooks/backend.js"; import { BankFrame } from "./pages/BankFrame.js"; import { WithdrawalOperationPage } from "./pages/WithdrawalOperationPage.js"; @@ -27,7 +28,6 @@ import { PublicHistoriesPage } from "./pages/PublicHistoriesPage.js"; import { RegistrationPage } from "./pages/RegistrationPage.js"; import { AdminHome } from "./pages/admin/AdminHome.js"; import { CreateCashout } from "./pages/business/CreateCashout.js"; -import { bankUiSettings } from "./settings.js"; import { ShowAccountDetails } from "./pages/account/ShowAccountDetails.js"; import { UpdateAccountPassword } from "./pages/account/UpdateAccountPassword.js"; import { RemoveAccount } from "./pages/admin/RemoveAccount.js"; @@ -36,10 +36,14 @@ import { CashoutListForAccount } from "./pages/account/CashoutListForAccount.js" import { ShowCashoutDetails } from "./pages/business/ShowCashoutDetails.js"; import { WireTransfer } from "./pages/WireTransfer.js"; import { AccountPage } from "./pages/AccountPage/index.js"; +import { useSettingsContext } from "./context/settings.js"; +import { useBankCoreApiContext } from "./context/config.js"; export function Routing(): VNode { const history = createHashHistory(); const backend = useBackendState(); + const settings = useSettingsContext(); + const {config} = useBankCoreApiContext(); const { i18n } = useTranslationContext(); if (backend.state.status === "loggedOut") { @@ -50,7 +54,7 @@ export function Routing(): VNode { component={() => ( <Fragment> <div class="sm:mx-auto sm:w-full sm:max-w-sm"> - <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h2> + <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to ${settings.bankName}!`}</h2> </div> <LoginForm @@ -76,7 +80,7 @@ export function Routing(): VNode { /> )} /> - {bankUiSettings.allowRegistrations && + {config.allow_registrations && <Route path="/register" component={() => ( diff --git a/packages/demobank-ui/src/bank-ui-settings.js b/packages/demobank-ui/src/bank-ui-settings.js deleted file mode 100644 index 397fa28c0..000000000 --- a/packages/demobank-ui/src/bank-ui-settings.js +++ /dev/null @@ -1,19 +0,0 @@ -// Values for development environment - -/** - * Global settings for the bank UI. - */ -globalThis.talerBankSettings = { - backendBaseURL: "http://bank.taler.test:1180/", - allowRegistrations: true, - showDemoNav: true, - simplePasswordForRandomAccounts: true, - allowRandomAccountCreation: true, - bankName: "Taler DEVELOPMENT Bank", - // Names and links for other demo sites to show in the navbar - demoSites: [ - ["Exchange", "https://Exchnage.taler.test/"], - ["Bank", "https://bank-ui.taler.test/"], - ["Merchant", "https://merchant.taler.test/"], - ], -}; diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index c787fa713..27898caeb 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -19,35 +19,45 @@ import { getGlobalLogLevel, setGlobalLogLevelFromString } from "@gnu-taler/taler-util"; -import { TranslationProvider } from "@gnu-taler/web-util/browser"; +import { Loading, TranslationProvider } from "@gnu-taler/web-util/browser"; import { Fragment, FunctionalComponent, h } from "preact"; import { SWRConfig } from "swr"; import { BackendStateProvider } from "../context/backend.js"; import { BankCoreApiProvider } from "../context/config.js"; import { strings } from "../i18n/strings.js"; -import { bankUiSettings } from "../settings.js"; +import { BankUiSettings, fetchSettings } from "../settings.js"; import { Routing } from "../Routing.js"; import { BankFrame } from "../pages/BankFrame.js"; +import { useEffect, useState } from "preact/hooks"; +import { SettingsProvider } from "../context/settings.js"; const WITH_LOCAL_STORAGE_CACHE = false; const App: FunctionalComponent = () => { - const baseUrl = getInitialBackendBaseURL(); + const [settings, setSettings] = useState<BankUiSettings>() + useEffect(() => { + fetchSettings(setSettings) + }, []) + if (!settings) return <Loading />; + + const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL); return ( - <TranslationProvider source={strings}> - <BackendStateProvider> - <BankCoreApiProvider baseUrl={baseUrl} frameOnError={BankFrame}> - <SWRConfig - value={{ - provider: WITH_LOCAL_STORAGE_CACHE - ? localStorageProvider - : undefined, - }} - > - <Routing /> - </SWRConfig> - </BankCoreApiProvider> - </BackendStateProvider> - </TranslationProvider > + <SettingsProvider value={settings}> + <TranslationProvider source={strings}> + <BackendStateProvider> + <BankCoreApiProvider baseUrl={baseUrl} frameOnError={BankFrame}> + <SWRConfig + value={{ + provider: WITH_LOCAL_STORAGE_CACHE + ? localStorageProvider + : undefined, + }} + > + <Routing /> + </SWRConfig> + </BankCoreApiProvider> + </BackendStateProvider> + </TranslationProvider > + </SettingsProvider> ); }; @@ -66,7 +76,7 @@ function localStorageProvider(): Map<unknown, unknown> { export default App; -function getInitialBackendBaseURL(): string { +function getInitialBackendBaseURL(backendFromSettings: string | undefined): string { const overrideUrl = typeof localStorage !== "undefined" ? localStorage.getItem("bank-base-url") @@ -75,13 +85,13 @@ function getInitialBackendBaseURL(): string { if (!overrideUrl) { //normal path - if (!bankUiSettings.backendBaseURL) { + if (!backendFromSettings) { console.error( "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'", ); result = window.origin } else { - result = bankUiSettings.backendBaseURL; + result = backendFromSettings; } } else { // testing/development path diff --git a/packages/demobank-ui/src/context/settings.ts b/packages/demobank-ui/src/context/settings.ts new file mode 100644 index 000000000..a14c14d15 --- /dev/null +++ b/packages/demobank-ui/src/context/settings.ts @@ -0,0 +1,44 @@ +/* + This file is part of GNU Taler + (C) 2022 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 { ComponentChildren, createContext, h, VNode } from "preact"; +import { useContext } from "preact/hooks"; +import { BankUiSettings } from "../settings.js"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type Type = BankUiSettings; + +const initial: BankUiSettings = {}; +const Context = createContext<Type>(initial); + +export const useSettingsContext = (): Type => useContext(Context); + +export const SettingsProvider = ({ + children, + value, +}: { + value: BankUiSettings, + children: ComponentChildren; +}): VNode => { + return h(Context.Provider, { + value, + children, + }); +}; diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts index 93d647e73..863b47bf3 100644 --- a/packages/demobank-ui/src/hooks/backend.ts +++ b/packages/demobank-ui/src/hooks/backend.ts @@ -29,7 +29,6 @@ import { useLocalStorage } from "@gnu-taler/web-util/browser"; import { useSWRConfig } from "swr"; -import { bankUiSettings } from "../settings.js"; /** * Has the information to reach and diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts index d0d180a53..c0164d60a 100644 --- a/packages/demobank-ui/src/hooks/circuit.ts +++ b/packages/demobank-ui/src/hooks/circuit.ts @@ -34,9 +34,7 @@ export type TransferCalculation = { }; type EstimatorFunction = ( amount: AmountJson, - currency: string, - sellFee: AmountJson, - sellRate: number, + fee: AmountJson, ) => Promise<TransferCalculation>; type CashoutEstimators = { @@ -73,25 +71,19 @@ export function useEstimator(): CashoutEstimators { const { state } = useBackendState(); const { api } = useBankCoreApiContext(); return { - estimateByCredit: async (fiatAmount, regionalCurrency, fee, rate) => { - // const resp = await api.getConversionInfoAPI().getCashoutRate({ - // credit: amount - // }); - // if (resp.type === "fail") { - // // can't happen - // // not-supported: it should not be able to call this function - // // wrong-calculation: we are using just one parameter - // throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint) - // } - const credit = fiatAmount; - const beforeFee = Amounts.sub(credit, fee).amount; - - // const debit = Amounts.parseOrThrow(resp.body.amount_debit); - //FIXME: remove this when endpoint works - const debit = Amounts.add( - Amounts.zeroOfCurrency(regionalCurrency), - beforeFee - ).amount; + estimateByCredit: async (fiatAmount, fee) => { + const resp = await api.getConversionInfoAPI().getCashoutRate({ + credit: fiatAmount + }); + if (resp.type === "fail") { + // can't happen + // not-supported: it should not be able to call this function + // wrong-calculation: we are using just one parameter + throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint) + } + const credit = Amounts.parseOrThrow(resp.body.amount_credit); + const debit = Amounts.parseOrThrow(resp.body.amount_debit); + const beforeFee = Amounts.add(credit, fee).amount; return { debit, @@ -99,19 +91,20 @@ export function useEstimator(): CashoutEstimators { credit, }; }, - estimateByDebit: async (regionalAmount, fiatCurrency, fee, rate) => { - // const resp = await api.getConversionInfoAPI().getCashoutRate({ debit: amount }); - // if (resp.type === "fail") { - // // can't happen - // // not-supported: it should not be able to call this function - // // wrong-calculation: we are using just one parameter - // throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint) - // } - // const credit = Amounts.parseOrThrow(resp.body.amount_credit); - const debit = regionalAmount; - const _credit = Amounts.parseOrThrow(regionalAmount); - const beforeFee = { ..._credit, currency: fiatCurrency }; - const credit = Amounts.sub(beforeFee, fee).amount; + estimateByDebit: async (regionalAmount, fee) => { + const resp = await api.getConversionInfoAPI().getCashoutRate({ + debit: regionalAmount + }); + if (resp.type === "fail") { + // can't happen + // not-supported: it should not be able to call this function + // wrong-calculation: we are using just one parameter + throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint) + } + const credit = Amounts.parseOrThrow(resp.body.amount_credit); + const debit = Amounts.parseOrThrow(resp.body.amount_debit); + const beforeFee = Amounts.add(credit, fee).amount; + return { debit, beforeFee, diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/preferences.ts index bd48ca680..a1525ac80 100644 --- a/packages/demobank-ui/src/hooks/settings.ts +++ b/packages/demobank-ui/src/hooks/preferences.ts @@ -25,7 +25,7 @@ import { } from "@gnu-taler/taler-util"; import { buildStorageKey, useLocalStorage, useTranslationContext } from "@gnu-taler/web-util/browser"; -interface Settings { +interface Preferences { currentWithdrawalOperationId: string | undefined; showWithdrawalSuccess: boolean; showDemoDescription: boolean; @@ -36,11 +36,11 @@ interface Settings { } -export function getAllBooleanSettings(): Array<keyof Settings> { +export function getAllBooleanPreferences(): Array<keyof Preferences> { return ["fastWithdrawal", "showDebugInfo", "showDemoDescription", "showInstallWallet", "showWithdrawalSuccess"] } -export function getLabelForSetting(k: keyof Settings, i18n: ReturnType<typeof useTranslationContext>["i18n"]): TranslatedString { +export function getLabelForPreferences(k: keyof Preferences, i18n: ReturnType<typeof useTranslationContext>["i18n"]): TranslatedString { switch (k) { case "currentWithdrawalOperationId": return i18n.str`Current withdrawal operation` case "maxWithdrawalAmount": return i18n.str`Max withdrawal amount` @@ -52,8 +52,8 @@ export function getLabelForSetting(k: keyof Settings, i18n: ReturnType<typeof us } } -export const codecForSettings = (): Codec<Settings> => - buildCodecForObject<Settings>() +export const codecForPreferences = (): Codec<Preferences> => + buildCodecForObject<Preferences>() .property("currentWithdrawalOperationId", codecOptional(codecForString())) .property("showWithdrawalSuccess", (codecForBoolean())) .property("showDemoDescription", (codecForBoolean())) @@ -63,7 +63,7 @@ export const codecForSettings = (): Codec<Settings> => .property("maxWithdrawalAmount", codecForNumber()) .build("Settings"); -const defaultSettings: Settings = { +const defaultPreferences: Preferences = { currentWithdrawalOperationId: undefined, showWithdrawalSuccess: true, showDemoDescription: true, @@ -73,21 +73,21 @@ const defaultSettings: Settings = { showDebugInfo: false, }; -const BANK_SETTINGS_KEY = buildStorageKey( - "bank-settings", - codecForSettings(), +const BANK_PREFERENCES_KEY = buildStorageKey( + "bank-preferences", + codecForPreferences(), ); -export function useSettings(): [ - Readonly<Settings>, - <T extends keyof Settings>(key: T, value: Settings[T]) => void, +export function usePreferences(): [ + Readonly<Preferences>, + <T extends keyof Preferences>(key: T, value: Preferences[T]) => void, ] { const { value, update } = useLocalStorage( - BANK_SETTINGS_KEY, - defaultSettings, + BANK_PREFERENCES_KEY, + defaultPreferences, ); - function updateField<T extends keyof Settings>(k: T, v: Settings[T]) { + function updateField<T extends keyof Preferences>(k: T, v: Preferences[T]) { const newValue = { ...value, [k]: v }; update(newValue); } diff --git a/packages/demobank-ui/src/index.html b/packages/demobank-ui/src/index.html index f702f30ea..3cc7f7fd2 100644 --- a/packages/demobank-ui/src/index.html +++ b/packages/demobank-ui/src/index.html @@ -29,8 +29,6 @@ href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" /> <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> <title>Bank</title> - <!-- Optional customization script. --> - <script src="bank-ui-settings.js"></script> <!-- Entry point for the bank SPA. --> <script type="module" src="index.js"></script> <link rel="stylesheet" href="index.css" /> diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx index 0f5236192..cfee684fa 100644 --- a/packages/demobank-ui/src/pages/AccountPage/views.tsx +++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx @@ -18,7 +18,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { Attention } from "@gnu-taler/web-util/browser"; import { Transactions } from "../../components/Transactions/index.js"; -import { useSettings } from "../../hooks/settings.js"; +import { usePreferences } from "../../hooks/preferences.js"; import { PaymentOptions } from "../PaymentOptions.js"; import { State } from "./index.js"; @@ -32,7 +32,7 @@ const IS_PUBLIC_ACCOUNT_ENABLED = false function ShowDemoInfo(): VNode { const { i18n } = useTranslationContext(); - const [settings, updateSettings] = useSettings(); + const [settings, updateSettings] = usePreferences(); if (!settings.showDemoDescription) return <Fragment /> return <Attention title={i18n.str`This is a demo bank`} onClose={() => { updateSettings("showDemoDescription", false); diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index f0baae3a3..5fef04b66 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -20,9 +20,9 @@ import { ComponentChildren, Fragment, VNode, h } from "preact"; import { useEffect, useErrorBoundary } from "preact/hooks"; import { useAccountDetails } from "../hooks/access.js"; import { useBackendState } from "../hooks/backend.js"; -import { getAllBooleanSettings, getLabelForSetting, useSettings } from "../hooks/settings.js"; -import { bankUiSettings } from "../settings.js"; +import { getAllBooleanPreferences, getLabelForPreferences, usePreferences } from "../hooks/preferences.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; +import { useSettingsContext } from "../context/settings.js"; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; @@ -37,7 +37,8 @@ export function BankFrame({ }): VNode { const { i18n } = useTranslationContext(); const backend = useBackendState(); - const [settings, updateSettings] = useSettings(); + const settings = useSettingsContext(); + const [preferences, updatePreferences] = usePreferences(); const [error, resetError] = useErrorBoundary(); @@ -59,12 +60,12 @@ export function BankFrame({ <div class="bg-indigo-600 pb-32"> <Header title="Bank" - iconLinkURL={bankUiSettings.iconLinkURL ?? "#"} + iconLinkURL={settings.iconLinkURL ?? "#"} onLogout={backend.state.status !== "loggedIn" ? undefined : () => { backend.logOut() - updateSettings("currentWithdrawalOperationId", undefined); + updatePreferences("currentWithdrawalOperationId", undefined); }} - sites={bankUiSettings.demoSites ?? []} + sites={settings.demoSites ?? new Array<Array<string>>(new Array<string>())} supportedLangs={["en", "es", "de"]} > <li> @@ -72,18 +73,18 @@ export function BankFrame({ <i18n.Translate>Preferences</i18n.Translate> </div> <ul role="list" class="space-y-1"> - {getAllBooleanSettings().map(set => { - const isOn: boolean = !!settings[set] + {getAllBooleanPreferences().map(set => { + const isOn: boolean = !!preferences[set] return <li class="mt-2 pl-2"> <div class="flex items-center justify-between"> <span class="flex flex-grow flex-col"> <span class="text-sm text-black font-medium leading-6 " id="availability-label"> - {getLabelForSetting(set, i18n)} + {getLabelForPreferences(set, i18n)} </span> </span> <button type="button" data-enabled={isOn} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description" - onClick={() => { updateSettings(set, !isOn); }}> + onClick={() => { updatePreferences(set, !isOn); }}> <span aria-hidden="true" data-enabled={isOn} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span> </button> </div> @@ -130,7 +131,7 @@ export function BankFrame({ } function MaybeShowDebugInfo({ info }: { info: any }): VNode { - const [settings] = useSettings() + const [settings] = usePreferences() if (settings.showDebugInfo) { return <pre class="whitespace-break-spaces "> {info} diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index 018416390..02ec75dbf 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -15,18 +15,14 @@ */ import { TranslatedString } from "@gnu-taler/taler-util"; -import { Notification, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { LocalNotificationBanner, ShowInputErrorLabel, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; -import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser"; import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; -import { bankUiSettings } from "../settings.js"; -import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js"; -import { assertUnreachable } from "./WithdrawalOperationPage.js"; +import { undefinedIfEmpty } from "../utils.js"; import { doAutoFocus } from "./PaytoWireTransferForm.js"; -import { Attention } from "@gnu-taler/web-util/browser"; -import { LocalNotificationBanner } from "@gnu-taler/web-util/browser"; +import { assertUnreachable } from "./WithdrawalOperationPage.js"; /** @@ -40,6 +36,7 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb const { i18n } = useTranslationContext(); const { api } = useBankCoreApiContext(); const [notification, notify, handleError] = useLocalNotification() + const {config} = useBankCoreApiContext(); /** * Register form may be shown in the initialization step. @@ -208,7 +205,7 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb </div>} </form> - {bankUiSettings.allowRegistrations && onRegister && + {config.allow_registrations && onRegister && <p class="mt-10 text-center text-sm text-gray-500 border-t"> <button type="submit" class="flex mt-4 rounded-md bg-blue-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600" diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts index 2d33ff78b..30f7419f0 100644 --- a/packages/demobank-ui/src/pages/OperationState/state.ts +++ b/packages/demobank-ui/src/pages/OperationState/state.ts @@ -21,12 +21,12 @@ import { mutate } from "swr"; import { useBankCoreApiContext } from "../../context/config.js"; import { useWithdrawalDetails } from "../../hooks/access.js"; import { useBackendState } from "../../hooks/backend.js"; -import { useSettings } from "../../hooks/settings.js"; +import { usePreferences } from "../../hooks/preferences.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; import { Props, State } from "./index.js"; export function useComponentState({ currency, onClose }: Props): utils.RecursiveState<State> { - const [settings, updateSettings] = useSettings() + const [settings, updateSettings] = usePreferences() const { state: credentials } = useBackendState() const creds = credentials.status !== "loggedIn" ? undefined : credentials const { api } = useBankCoreApiContext() diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx index e7db566ea..a06147039 100644 --- a/packages/demobank-ui/src/pages/OperationState/views.tsx +++ b/packages/demobank-ui/src/pages/OperationState/views.tsx @@ -20,7 +20,7 @@ import { Fragment, VNode, h } from "preact"; import { useEffect, useMemo, useState } from "preact/hooks"; import { QR } from "../../components/QR.js"; import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser"; -import { useSettings } from "../../hooks/settings.js"; +import { usePreferences } from "../../hooks/preferences.js"; import { undefinedIfEmpty } from "../../utils.js"; import { State } from "./index.js"; import { LocalNotificationBanner } from "@gnu-taler/web-util/browser"; @@ -46,7 +46,7 @@ export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) { export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: doConfirm, busy }: State.NeedConfirmation) { const { i18n } = useTranslationContext() - const [settings] = useSettings() + const [settings] = usePreferences() const [notification, notify, errorHandler] = useLocalNotification() const captchaNumbers = useMemo(() => { @@ -309,7 +309,7 @@ export function AbortedView({ error, onClose }: State.Aborted) { export function ConfirmedView({ error, onClose }: State.Confirmed) { const { i18n } = useTranslationContext(); - const [settings, updateSettings] = useSettings() + const [settings, updateSettings] = usePreferences() return ( <Fragment> diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index 2e756b86d..76d20867e 100644 --- a/packages/demobank-ui/src/pages/PaymentOptions.tsx +++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx @@ -20,7 +20,7 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { PaytoWireTransferForm, doAutoFocus } from "./PaytoWireTransferForm.js"; import { WalletWithdrawForm } from "./WalletWithdrawForm.js"; -import { useSettings } from "../hooks/settings.js"; +import { usePreferences } from "../hooks/preferences.js"; /** * Let the user choose a payment option, @@ -28,7 +28,7 @@ import { useSettings } from "../hooks/settings.js"; */ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJson, goToConfirmOperation: (id: string) => void }): VNode { const { i18n } = useTranslationContext(); - const [settings] = useSettings(); + const [settings] = usePreferences(); const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(); diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index fdaa28bbb..9c3b21097 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -15,18 +15,18 @@ */ import { AccessToken, Logger, TranslatedString } from "@gnu-taler/taler-util"; import { + LocalNotificationBanner, + ShowInputErrorLabel, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser"; import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; -import { bankUiSettings } from "../settings.js"; -import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js"; +import { undefinedIfEmpty } from "../utils.js"; import { getRandomPassword, getRandomUsername } from "./rnd.js"; -import { LocalNotificationBanner } from "@gnu-taler/web-util/browser"; +import { useSettingsContext } from "../context/settings.js"; const logger = new Logger("RegistrationPage"); @@ -38,7 +38,8 @@ export function RegistrationPage({ onCancel: () => void; }): VNode { const { i18n } = useTranslationContext(); - if (!bankUiSettings.allowRegistrations) { + const {config} = useBankCoreApiContext(); + if (!config.allow_registrations) { return ( <p>{i18n.str`Currently, the bank is not accepting new registrations!`}</p> ); @@ -62,6 +63,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on const [email, setEmail] = useState<string | undefined>(); const [repeatPassword, setRepeatPassword] = useState<string | undefined>(); const [notification, notify, handleError] = useLocalNotification() + const settings = useSettingsContext(); const { api } = useBankCoreApiContext() // const { register } = useTestingAPI(); @@ -177,7 +179,8 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on async function doRandomRegistration(tries: number = 3) { const user = getRandomUsername(); - const pass = getRandomPassword(); + + const pass = settings.simplePasswordForRandomAccounts ? "123" : getRandomPassword(); const username = `_${user.first}-${user.second}_` await doRegistrationAndLogin(name, username, pass) onComplete(); @@ -389,7 +392,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on </form> - {bankUiSettings.allowRandomAccountCreation && + {settings.allowRandomAccountCreation && <p class="mt-10 text-center text-sm text-gray-500 border-t"> <button type="submit" class="flex mt-4 w-full justify-center rounded-md bg-green-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index a9a661c25..5eef95f1e 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -32,7 +32,7 @@ import { useState } from "preact/hooks"; import { Attention } from "@gnu-taler/web-util/browser"; import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; -import { useSettings } from "../hooks/settings.js"; +import { usePreferences } from "../hooks/preferences.js"; import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js"; import { OperationState } from "./OperationState/index.js"; import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js"; @@ -50,7 +50,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: { onCancel: () => void; }): VNode { const { i18n } = useTranslationContext(); - const [settings, updateSettings] = useSettings() + const [settings, updateSettings] = usePreferences() const { state: credentials } = useBackendState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials @@ -240,7 +240,7 @@ export function WalletWithdrawForm({ onCancel: () => void; }): VNode { const { i18n } = useTranslationContext(); - const [settings, updateSettings] = useSettings() + const [settings, updateSettings] = usePreferences() return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg"> <div class="px-4 sm:px-0"> diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index 7fec76d2f..be8ff8b58 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -33,7 +33,7 @@ import { useMemo, useState } from "preact/hooks"; import { mutate } from "swr"; import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser"; import { useBankCoreApiContext } from "../context/config.js"; -import { useSettings } from "../hooks/settings.js"; +import { usePreferences } from "../hooks/preferences.js"; import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; import { assertUnreachable } from "./WithdrawalOperationPage.js"; @@ -60,7 +60,7 @@ export function WithdrawalConfirmationQuestion({ withdrawUri, }: Props): VNode { const { i18n } = useTranslationContext(); - const [settings, updateSettings] = useSettings() + const [settings, updateSettings] = usePreferences() const captchaNumbers = useMemo(() => { return { diff --git a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx index 5ed57a0f7..7060b7a98 100644 --- a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx @@ -25,7 +25,7 @@ import { import { Fragment, VNode, h } from "preact"; import { Attention } from "@gnu-taler/web-util/browser"; import { useBankCoreApiContext } from "../context/config.js"; -import { useSettings } from "../hooks/settings.js"; +import { usePreferences } from "../hooks/preferences.js"; import { WithdrawalQRCode } from "./WithdrawalQRCode.js"; const logger = new Logger("AccountPage"); @@ -44,7 +44,7 @@ export function WithdrawalOperationPage({ }); const parsedUri = parseWithdrawUri(uri); const { i18n } = useTranslationContext(); - const [settings, updateSettings] = useSettings(); + const [settings, updateSettings] = usePreferences(); if (!parsedUri) { return <Attention type="danger" title={i18n.str`The Withdrawal URI is not valid`}> diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx index 1838dbda3..c5f4ebc4e 100644 --- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx +++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx @@ -77,7 +77,7 @@ export function CreateCashout({ estimateByCredit: calculateFromCredit, estimateByDebit: calculateFromDebit, } = useEstimator(); - const { api, config } = useBankCoreApiContext() + const { config } = useBankCoreApiContext() const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, amount: "2" }); const [notification, notify, handleError] = useLocalNotification() const info = useConversionInfo(); @@ -108,35 +108,29 @@ export function CreateCashout({ return <ErrorLoading error={info} /> } + const conversionInfo = info.body.conversion_info + if (!conversionInfo) { + return <div>conversion enabled but server replied without conversion_info</div> + } + const account = { balance: Amounts.parseOrThrow(resultAccount.body.balance.amount), balanceIsDebit: resultAccount.body.balance.credit_debit_indicator == "debit", debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold) } - const { fiat_currency, regional_currency, cashout_ratio, cashout_fee } = info.body + const {fiat_currency, regional_currency} = info.body const regionalZero = Amounts.zeroOfCurrency(regional_currency); const fiatZero = Amounts.zeroOfCurrency(fiat_currency); const limit = account.balanceIsDebit ? Amounts.sub(account.debitThreshold, account.balance).amount : Amounts.add(account.balance, account.debitThreshold).amount; - const zeroCalc = { debit: regionalZero, credit: fiatZero, beforeFee: regionalZero }; + const zeroCalc = { debit: regionalZero, credit: fiatZero, beforeFee: fiatZero }; const [calc, setCalc] = useState(zeroCalc); - - const sellRate = Number.parseFloat(cashout_ratio); - const sellFee = !cashout_fee - ? fiatZero - : Amounts.parseOrThrow(cashout_fee); - - if (sellRate === undefined || sellRate < 0) return <div>error rate d - <pre> - {JSON.stringify(info.body, undefined, 2)} - </pre> - </div>; - - const safeSellRate = sellRate - + console.log(calc) + const sellFee = Amounts.parseOrThrow(conversionInfo.cashout_fee); + const sellRate = conversionInfo.cashout_ratio /** * can be in regional currency or fiat currency * depending on the isDebit flag @@ -150,8 +144,8 @@ export function CreateCashout({ await handleError(async () => { if (Amounts.isNonZero(inputAmount)) { const resp = await (form.isDebit ? - calculateFromDebit(inputAmount, fiat_currency, sellFee, safeSellRate) : - calculateFromCredit(inputAmount, regional_currency, sellFee, safeSellRate)); + calculateFromDebit(inputAmount, sellFee) : + calculateFromCredit(inputAmount, sellFee)); setCalc(resp) } }) diff --git a/packages/demobank-ui/src/pages/rnd.ts b/packages/demobank-ui/src/pages/rnd.ts index 32c3a934f..46111425e 100644 --- a/packages/demobank-ui/src/pages/rnd.ts +++ b/packages/demobank-ui/src/pages/rnd.ts @@ -1,5 +1,4 @@ import { createEddsaKeyPair, encodeCrock, getRandomBytes } from "@gnu-taler/taler-util" -import { bankUiSettings } from "../settings.js" const noun = [ @@ -2890,6 +2889,5 @@ export function getRandomUsername(): { first: string, second: string } { } export function getRandomPassword(): string { - if (bankUiSettings.simplePasswordForRandomAccounts) return "123" return encodeCrock(getRandomBytes(16)) }
\ No newline at end of file diff --git a/packages/demobank-ui/src/settings.json b/packages/demobank-ui/src/settings.json new file mode 100644 index 000000000..8d5b149b5 --- /dev/null +++ b/packages/demobank-ui/src/settings.json @@ -0,0 +1,12 @@ +{ + "backendBaseURL": "http://bank.taler.test:1180/", + "showDemoNav": true, + "simplePasswordForRandomAccounts": true, + "allowRandomAccountCreation": true, + "bankName": "Taler DEVELOPMENT Bank", + "demoSites": [ + ["Exchange", "https://Exchnage.taler.test/"], + ["Bank", "https://bank-ui.taler.test/"], + ["Merchant", "https://merchant.taler.test/"] + ] +} diff --git a/packages/demobank-ui/src/settings.ts b/packages/demobank-ui/src/settings.ts index f17d1d511..a9c63857b 100644 --- a/packages/demobank-ui/src/settings.ts +++ b/packages/demobank-ui/src/settings.ts @@ -14,15 +14,16 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { Codec, buildCodecForObject, codecForBoolean, codecForList, codecForString, codecOptional } from "@gnu-taler/taler-util"; + export interface BankUiSettings { backendBaseURL?: string; - allowRegistrations?: boolean; iconLinkURL?: string; showDemoNav?: boolean; simplePasswordForRandomAccounts?: boolean; allowRandomAccountCreation?: boolean; bankName?: string; - demoSites?: [string, string][]; + demoSites?: Array<Array<string>>; } /** @@ -31,7 +32,6 @@ export interface BankUiSettings { const defaultSettings: BankUiSettings = { backendBaseURL: "https://bank.demo.taler.net/demobanks/default/", iconLinkURL: "https://demo.taler.net/", - allowRegistrations: true, bankName: "Taler Bank", showDemoNav: true, simplePasswordForRandomAccounts: true, @@ -45,7 +45,26 @@ const defaultSettings: BankUiSettings = { ], }; -export const bankUiSettings: BankUiSettings = - "talerBankSettings" in globalThis - ? (globalThis as any).talerBankSettings - : defaultSettings; +const codecForBankUISettings = (): Codec<BankUiSettings> => + buildCodecForObject<BankUiSettings>() + .property("allowRandomAccountCreation", codecOptional(codecForBoolean())) + .property("backendBaseURL", codecOptional(codecForString())) + .property("bankName", codecOptional(codecForString())) + .property("demoSites", codecOptional(codecForList(codecForList(codecForString())))) + .property("iconLinkURL", codecOptional(codecForString())) + .property("showDemoNav", codecOptional(codecForBoolean())) + .property("simplePasswordForRandomAccounts", codecOptional(codecForBoolean())) + .build("BankUiSettings"); + +export function fetchSettings(listener: (s: BankUiSettings) => void): void { + fetch("./settings.json") + .then(resp => resp.json()) + .then(json => codecForBankUISettings().decode(json)) + .then(listener) + .catch(e => { + console.log("failed to fetch settings", e) + listener(defaultSettings) + }) +} + + diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 3ef0ff76c..0ecc08b33 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -754,29 +754,21 @@ export const codecForConversionInfo = buildCodecForObject<TalerBankConversionApi.ConversionInfo>() .property("cashin_fee", codecForAmountString()) .property("cashin_min_amount", codecForAmountString()) - .property("cashin_ratio", codecForString()) - // .property("cashin_ratio", codecForDecimalNumber()) - .property( - "cashin_rounding_mode", - codecForEither( - codecForConstString("zero"), - codecForConstString("up"), - codecForConstString("nearest"), - ), - ) + .property("cashin_ratio", codecForDecimalNumber()) + .property("cashin_rounding_mode", codecForEither( + codecForConstString("zero"), + codecForConstString("up"), + codecForConstString("nearest") + )) .property("cashin_tiny_amount", codecForAmountString()) .property("cashout_fee", codecForAmountString()) .property("cashout_min_amount", codecForAmountString()) - .property("cashout_ratio", codecForString()) - // .property("cashout_ratio", codecForDecimalNumber()) - .property( - "cashout_rounding_mode", - codecForEither( - codecForConstString("zero"), - codecForConstString("up"), - codecForConstString("nearest"), - ), - ) + .property("cashout_ratio", codecForDecimalNumber()) + .property("cashout_rounding_mode", codecForEither( + codecForConstString("zero"), + codecForConstString("up"), + codecForConstString("nearest") + )) .property("cashout_tiny_amount", codecForAmountString()) .build("ConversionBankConfig.ConversionInfo"); @@ -792,34 +784,10 @@ export const codecForConversionBankConfig = ) .property("fiat_currency", codecForString()) .property("fiat_currency_specification", codecForCurrencySpecificiation()) - // .property("conversion_info", codecOptional(codecForConversionInfo())) - ////////////////////////// remove this - .property("cashin_fee", codecForAmountString()) - .property("cashin_min_amount", codecForAmountString()) - .property("cashin_ratio", codecForString()) - .property( - "cashin_rounding_mode", - codecForEither( - codecForConstString("zero"), - codecForConstString("up"), - codecForConstString("nearest"), - ), - ) - .property("cashin_tiny_amount", codecForAmountString()) - .property("cashout_fee", codecForAmountString()) - .property("cashout_min_amount", codecForAmountString()) - .property("cashout_ratio", codecForString()) - .property( - "cashout_rounding_mode", - codecForEither( - codecForConstString("zero"), - codecForConstString("up"), - codecForConstString("nearest"), - ), - ) - .property("cashout_tiny_amount", codecForAmountString()) - ////////////////////////// - .build("ConversionBankConfig.IntegrationConfig"); + + .property("conversion_info", codecOptional(codecForConversionInfo())) + .build("ConversionBankConfig.IntegrationConfig") + // export const codecFor = // (): Codec<TalerWireGatewayApi.PublicAccountsResponse> => // buildCodecForObject<TalerWireGatewayApi.PublicAccountsResponse>() @@ -833,7 +801,7 @@ type EddsaSignature = string; type BlindedRsaSignature = string; type Base32 = string; -type DecimalNumber = number; +type DecimalNumber = string; type RsaSignature = string; // The type of a coin's blinded envelope depends on the cipher that is used // for signing with a denomination key. @@ -859,11 +827,10 @@ interface CSCoinEnvelope { // a 256-bit nonce, converted to Crockford Base32. type DenominationBlindingKeyP = string; -const codecForURL = codecForString; -const codecForLibtoolVersion = codecForString; -const codecForCurrencyName = codecForString; -const codecForEddsaSignature = codecForString; -const codecForDecimalNumber = codecForNumber; +const codecForURL = codecForString +const codecForLibtoolVersion = codecForString +const codecForCurrencyName = codecForString +const codecForDecimalNumber = codecForString enum TanChannel { SMS = "sms", @@ -1065,12 +1032,10 @@ export namespace TalerRevenueApi { export namespace TalerBankConversionApi { export interface ConversionInfo { // Exchange rate to buy regional currency from fiat - // cashin_ratio: DecimalNumber; - cashin_ratio: string; + cashin_ratio: DecimalNumber; // Exchange rate to sell regional currency for fiat - // cashout_ratio: DecimalNumber; - cashout_ratio: string; + cashout_ratio: DecimalNumber; // Fee to subtract after applying the cashin ratio. cashin_fee: AmountString; @@ -1097,7 +1062,7 @@ export namespace TalerBankConversionApi { cashout_rounding_mode: "zero" | "up" | "nearest"; } - export interface IntegrationConfig extends ConversionInfo { + export interface IntegrationConfig { // libtool-style representation of the Bank protocol version, see // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning // The format is "current:revision:age". @@ -1120,7 +1085,7 @@ export namespace TalerBankConversionApi { // Extra conversion rate information. // Only present if server opts in to report the static conversion rate. - // conversion_info?: ConversionInfo + conversion_info?: ConversionInfo } export interface CashinConversionResponse { diff --git a/packages/web-util/src/components/Header.tsx b/packages/web-util/src/components/Header.tsx index bde0688dc..0ffc57417 100644 --- a/packages/web-util/src/components/Header.tsx +++ b/packages/web-util/src/components/Header.tsx @@ -3,7 +3,8 @@ import { LangSelector, useTranslationContext } from "../index.browser.js"; import { ComponentChildren, Fragment, VNode, h } from "preact"; import logo from "../assets/logo-2021.svg"; -export function Header({ title, iconLinkURL, sites, supportedLangs, onLogout, children }: { title: string, iconLinkURL: string, children?: ComponentChildren, onLogout: (() => void) | undefined, sites: [string, string][], supportedLangs: string[] }): VNode { +export function Header({ title, iconLinkURL, sites, supportedLangs, onLogout, children }: + { title: string, iconLinkURL: string, children?: ComponentChildren, onLogout: (() => void) | undefined, sites: Array<Array<string>>, supportedLangs: string[] }): VNode { const { i18n } = useTranslationContext(); const [open, setOpen] = useState(false) return <Fragment> @@ -28,7 +29,9 @@ export function Header({ title, iconLinkURL, sites, supportedLangs, onLogout, ch {sites.length !== 0 && <div class="flex flex-1 space-x-4"> {/* <!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */} - {sites.map(([name, url]) => { + {sites.map((site) => { + if (site.length !== 2) return; + const [name, url] = site return <a href={url} class="text-white hover:bg-indigo-500 hover:bg-opacity-75 rounded-md py-2 px-3 text-sm font-medium">{name}</a> })} </div> |