/* 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 */ import { useState, useEffect, useCallback } from "preact/hooks"; import { Props, State } from "./index.js"; import { ExchangeEntryStatus, TalerConfigResponse, TranslatedString, canonicalizeBaseUrl } from "@gnu-taler/taler-util"; import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { RecursiveState } from "../../utils/index.js"; import { HttpResponse, useApiContext } from "@gnu-taler/web-util/browser"; import { alertFromError } from "../../context/alert.js"; import { withSafe } from "../../mui/handlers.js"; export function useComponentState({ onBack, currency, noDebounce }: Props): RecursiveState { const [verified, setVerified] = useState< { url: string; config: TalerConfigResponse } | undefined >(undefined); const api = useBackendContext(); const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {}), ); const walletExchanges = !hook ? [] : hook.hasError ? [] : hook.response.exchanges const used = walletExchanges.filter(e => e.exchangeEntryStatus === ExchangeEntryStatus.Used); const preset = walletExchanges.filter(e => e.exchangeEntryStatus === ExchangeEntryStatus.Preset); if (!verified) { return (): State => { const { request } = useApiContext(); const ccc = useCallback(async (str: string) => { const c = canonicalizeBaseUrl(str) const found = used.findIndex((e) => e.exchangeBaseUrl === c); if (found !== -1) { throw Error("This exchange is already active") } const result = await request(c, "/keys") return result }, [used]) const { result, value: url, update, error: requestError } = useDebounce>(ccc, noDebounce ?? false) const [inputError, setInputError] = useState() return { status: "verify", error: undefined, onCancel: onBack, expectedCurrency: currency, onAccept: async () => { if (!url || !result || !result.ok) return; setVerified({ url, config: result.data }) }, result, knownExchanges: preset.map(e => new URL(e.exchangeBaseUrl)), url: { value: url ?? "", error: inputError ?? requestError, onInput: withSafe(update, (e) => { setInputError(e.message) }) }, }; } } async function onConfirm() { if (!verified) return; await api.wallet.call(WalletApiOperation.AddExchange, { exchangeBaseUrl: canonicalizeBaseUrl(verified.url), forceUpdate: true, }); onBack(); } return { status: "confirm", error: undefined, onCancel: onBack, onConfirm, url: verified.url }; } function useDebounce( onTrigger: (v: string) => Promise, disabled: boolean, ): { loading: boolean; error?: string; value: string | undefined; result: T | undefined; update: (s: string) => void; } { const [value, setValue] = useState(); const [dirty, setDirty] = useState(false); const [loading, setLoading] = useState(false); const [result, setResult] = useState(undefined); const [error, setError] = useState(undefined); const [handler, setHandler] = useState(undefined); if (!disabled) { useEffect(() => { if (!value) return; clearTimeout(handler); const h = setTimeout(async () => { setDirty(true); setLoading(true); try { const result = await onTrigger(value); setResult(result); setError(undefined); setLoading(false); } catch (e) { const errorMessage = e instanceof Error ? e.message : `unknown error: ${e}`; setError(errorMessage); setLoading(false); setResult(undefined); } }, 500); setHandler(h); }, [value, setHandler, onTrigger]); } return { error: dirty ? error : undefined, loading: loading, result: result, value: value, update: disabled ? onTrigger : setValue , }; }