diff options
author | Sebastian <sebasjm@gmail.com> | 2022-08-29 11:32:07 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-08-29 11:32:07 -0300 |
commit | a5f052d69c6457ad0289fdcb56398ea1fabedc2a (patch) | |
tree | e54429481cec80f393204fa972c134b130652ad9 | |
parent | cf894f1dd309e48d8be380c56175219027c84fb7 (diff) |
using CTA for manual withdrawal
21 files changed, 675 insertions, 74 deletions
diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx index 42a365f8c..4499bcdf8 100644 --- a/packages/taler-wallet-webextension/src/NavigationBar.tsx +++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx @@ -85,9 +85,6 @@ export const Pages = { balanceHistory: pageDefinition<{ currency?: string }>( "/balance/history/:currency?", ), - balanceManualWithdraw: pageDefinition<{ amount?: string }>( - "/balance/manual-withdraw/:amount?", - ), balanceDeposit: pageDefinition<{ currency: string }>( "/balance/deposit/:currency", ), @@ -111,12 +108,18 @@ export const Pages = { "/settings/exchange/add/:currency?", ), + invoice: pageDefinition<{ amount?: string }>("/receive/invoice/:amount?"), + cta: pageDefinition<{ action: string }>("/cta/:action"), ctaPay: "/cta/pay", ctaRefund: "/cta/refund", ctaTips: "/cta/tip", ctaWithdraw: "/cta/withdraw", ctaDeposit: "/cta/deposit", + + ctaWithdrawManual: pageDefinition<{ amount?: string }>( + "/cta/manual-withdraw/:amount?", + ), }; export function PopupNavBar({ path = "" }: { path?: string }): VNode { diff --git a/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx b/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx index 5decb1632..b60c86021 100644 --- a/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx +++ b/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx @@ -48,7 +48,9 @@ export function TermsOfServiceSection({ return ( <Fragment> {terms.status === "notfound" && ( - <section> + <section + style={{ justifyContent: "space-around", display: "flex" }} + > <WarningText> <i18n.Translate> Exchange doesn't have terms of service @@ -62,7 +64,9 @@ export function TermsOfServiceSection({ return ( <Fragment> {terms.status === "notfound" && ( - <section> + <section + style={{ justifyContent: "space-around", display: "flex" }} + > <WarningText> <i18n.Translate> Exchange doesn't have terms of service @@ -71,7 +75,9 @@ export function TermsOfServiceSection({ </section> )} {terms.status === "new" && ( - <section> + <section + style={{ justifyContent: "space-around", display: "flex" }} + > <Button variant="contained" color="success" @@ -84,7 +90,9 @@ export function TermsOfServiceSection({ </section> )} {terms.status === "changed" && ( - <section> + <section + style={{ justifyContent: "space-around", display: "flex" }} + > <Button variant="contained" color="success" @@ -102,13 +110,13 @@ export function TermsOfServiceSection({ return ( <Fragment> {ableToReviewTermsOfService && ( - <section> + <section style={{ justifyContent: "space-around", display: "flex" }}> <LinkSuccess upperCased onClick={() => onReview(true)}> <i18n.Translate>Show terms of service</i18n.Translate> </LinkSuccess> </section> )} - <section> + <section style={{ justifyContent: "space-around", display: "flex" }}> <CheckboxOutlined name="terms" enabled={reviewed} @@ -129,7 +137,7 @@ export function TermsOfServiceSection({ return ( <Fragment> {terms.status !== "notfound" && !terms.content && ( - <section> + <section style={{ justifyContent: "space-around", display: "flex" }}> <WarningBox> <i18n.Translate> The exchange reply with a empty terms of service @@ -138,7 +146,7 @@ export function TermsOfServiceSection({ </section> )} {terms.status !== "accepted" && terms.content && ( - <section> + <section style={{ justifyContent: "space-around", display: "flex" }}> {terms.content.type === "xml" && ( <TermsOfService> <ExchangeXmlTos doc={terms.content.document} /> @@ -160,14 +168,14 @@ export function TermsOfServiceSection({ </section> )} {reviewed && ableToReviewTermsOfService && ( - <section> + <section style={{ justifyContent: "space-around", display: "flex" }}> <LinkSuccess upperCased onClick={() => onReview(false)}> <i18n.Translate>Hide terms of service</i18n.Translate> </LinkSuccess> </section> )} {terms.status !== "notfound" && ( - <section> + <section style={{ justifyContent: "space-around", display: "flex" }}> <CheckboxOutlined name="terms" enabled={reviewed} diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index b12e8df3b..7425dbd29 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -23,15 +23,20 @@ import * as wxApi from "../../wxApi.js"; import { Props as TermsOfServiceSectionProps } from "../TermsOfServiceSection.js"; -import { useComponentState } from "./state.js"; +import { useComponentStateFromParams, useComponentStateFromURI } from "./state.js"; import { CompletedView, LoadingExchangeView, LoadingInfoView, LoadingUriView, SuccessView } from "./views.js"; -export interface Props { +export interface PropsFromURI { talerWithdrawUri: string | undefined; cancel: () => Promise<void>; } +export interface PropsFromParams { + amount: string; + cancel: () => Promise<void>; +} + export type State = | State.Loading | State.LoadingUriError @@ -93,4 +98,5 @@ const viewMapping: StateViewMap<State> = { success: SuccessView, }; -export const WithdrawPage = compose("Withdraw", (p: Props) => useComponentState(p, wxApi), viewMapping) +export const WithdrawPageFromURI = compose("WithdrawPageFromURI", (p: PropsFromURI) => useComponentStateFromURI(p, wxApi), viewMapping) +export const WithdrawPageFromParams = compose("WithdrawPageFromParams", (p: PropsFromParams) => useComponentStateFromParams(p, wxApi), viewMapping) diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index 849dd5cca..3b138e74d 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -15,16 +15,210 @@ */ -import { Amounts } from "@gnu-taler/taler-util"; +import { Amounts, parsePaytoUri } from "@gnu-taler/taler-util"; import { TalerError } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { buildTermsOfServiceState } from "../../utils/index.js"; import * as wxApi from "../../wxApi.js"; -import { Props, State } from "./index.js"; +import { PropsFromURI, PropsFromParams, State } from "./index.js"; -export function useComponentState( - { talerWithdrawUri, cancel }: Props, +export function useComponentStateFromParams( + { amount, cancel }: PropsFromParams, + api: typeof wxApi, +): State { + + const [ageRestricted, setAgeRestricted] = useState(0); + + const exchangeHook = useAsyncAsHook(api.listExchanges); + + const exchangeHookDep = + !exchangeHook || exchangeHook.hasError || !exchangeHook.response + ? undefined + : exchangeHook.response; + + const chosenAmount = Amounts.parseOrThrow(amount); + + // get the first exchange with the currency as the default one + const exchange = exchangeHookDep ? exchangeHookDep.exchanges.find(e => e.currency === chosenAmount.currency) : undefined + /** + * For the exchange selected, bring the status of the terms of service + */ + const terms = useAsyncAsHook(async () => { + if (!exchange) return undefined + + const exchangeTos = await api.getExchangeTos(exchange.exchangeBaseUrl, ["text/xml"]); + + const state = buildTermsOfServiceState(exchangeTos); + + return { state }; + }, [exchangeHookDep]); + + /** + * With the exchange and amount, ask the wallet the information + * about the withdrawal + */ + const amountHook = useAsyncAsHook(async () => { + if (!exchange) return undefined + + const info = await api.getExchangeWithdrawalInfo({ + exchangeBaseUrl: exchange.exchangeBaseUrl, + amount: chosenAmount, + tosAcceptedFormat: ["text/xml"], + }); + + const withdrawAmount = { + raw: Amounts.parseOrThrow(info.withdrawalAmountRaw), + effective: Amounts.parseOrThrow(info.withdrawalAmountEffective), + } + + return { amount: withdrawAmount }; + }, [exchangeHookDep]); + + const [reviewing, setReviewing] = useState<boolean>(false); + const [reviewed, setReviewed] = useState<boolean>(false); + + const [withdrawError, setWithdrawError] = useState<TalerError | undefined>( + undefined, + ); + const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false); + const [withdrawCompleted, setWithdrawCompleted] = useState<boolean>(false); + + if (!exchangeHook) return { status: "loading", error: undefined } + if (exchangeHook.hasError) { + return { + status: "loading-uri", + error: exchangeHook, + }; + } + + if (!exchange) { + return { + status: "loading-exchange", + error: { + hasError: true, + operational: false, + message: "ERROR_NO-DEFAULT-EXCHANGE", + }, + }; + } + + async function doWithdrawAndCheckError(): Promise<void> { + if (!exchange) return; + + try { + setDoingWithdraw(true); + + const response = await wxApi.acceptManualWithdrawal( + exchange.exchangeBaseUrl, + Amounts.stringify(amount), + ); + + setWithdrawCompleted(true); + } catch (e) { + if (e instanceof TalerError) { + setWithdrawError(e); + } + } + setDoingWithdraw(false); + } + + if (!amountHook) { + return { status: "loading", error: undefined } + } + if (amountHook.hasError) { + return { + status: "loading-info", + error: amountHook, + }; + } + if (!amountHook.response) { + return { status: "loading", error: undefined }; + } + if (withdrawCompleted) { + return { status: "completed", error: undefined }; + } + + const withdrawalFee = Amounts.sub( + amountHook.response.amount.raw, + amountHook.response.amount.effective, + ).amount; + const toBeReceived = amountHook.response.amount.effective; + + const { state: termsState } = (!terms + ? undefined + : terms.hasError + ? undefined + : terms.response) || { state: undefined }; + + async function onAccept(accepted: boolean): Promise<void> { + if (!termsState || !exchange) return; + + try { + await api.setExchangeTosAccepted( + exchange.exchangeBaseUrl, + accepted ? termsState.version : undefined, + ); + setReviewed(accepted); + } catch (e) { + if (e instanceof Error) { + //FIXME: uncomment this and display error + // setErrorAccepting(e.message); + } + } + } + + const mustAcceptFirst = + termsState !== undefined && + (termsState.status === "changed" || termsState.status === "new"); + + const ageRestrictionOptions: Record<string, string> | undefined = "6:12:18" + .split(":") + .reduce((p, c) => ({ ...p, [c]: `under ${c}` }), {}); + + if (ageRestrictionOptions) { + ageRestrictionOptions["0"] = "Not restricted"; + } + + //TODO: calculate based on exchange info + const ageRestrictionEnabled = false; + const ageRestriction = ageRestrictionEnabled ? { + list: ageRestrictionOptions, + value: String(ageRestricted), + onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)), + } : undefined; + + return { + status: "success", + error: undefined, + exchangeUrl: exchange.exchangeBaseUrl, + toBeReceived, + withdrawalFee, + chosenAmount, + ageRestriction, + doWithdrawal: { + onClick: + doingWithdraw || (mustAcceptFirst && !reviewed) + ? undefined + : doWithdrawAndCheckError, + error: withdrawError, + }, + tosProps: !termsState + ? undefined + : { + onAccept, + onReview: setReviewing, + reviewed: reviewed, + reviewing: reviewing, + terms: termsState, + }, + mustAcceptFirst, + cancel, + }; +} + +export function useComponentStateFromURI( + { talerWithdrawUri, cancel }: PropsFromURI, api: typeof wxApi, ): State { const [ageRestricted, setAgeRestricted] = useState(0); @@ -50,21 +244,6 @@ export function useComponentState( ? undefined : uriInfoHook.response; - // const { amount, thisExchange } = useMemo(() => { - // if (!uriHookDep) - // return { - // amount: undefined, - // thisExchange: undefined, - // thisCurrencyExchanges: [], - // }; - - // const { uriInfo } = uriHookDep; - - // const amount = uriHookDep ? Amounts.parseOrThrow(uriHookDep.amount) : undefined; - // const thisExchange = uriHookDep?.thisExchange; - - // return { amount, thisExchange }; - // }, [uriHookDep]); /** * For the exchange selected, bring the status of the terms of service @@ -118,6 +297,7 @@ export function useComponentState( } const { amount, thisExchange } = uriInfoHook.response + const chosenAmount = Amounts.parseOrThrow(amount); if (!thisExchange) { diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index dd3f6c9c7..c72f906e5 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -27,7 +27,7 @@ import { import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; -import { useComponentState } from "./state.js"; +import { useComponentStateFromURI } from "./state.js"; const exchanges: ExchangeListItem[] = [ { @@ -56,7 +56,7 @@ describe("Withdraw CTA states", () => { it("should tell the user that the URI is missing", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerWithdrawUri: undefined, cancel: async () => { null } }, { + useComponentStateFromURI({ talerWithdrawUri: undefined, cancel: async () => { null } }, { listExchanges: async () => ({ exchanges }), getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ amount: "ARS:2", @@ -88,7 +88,7 @@ describe("Withdraw CTA states", () => { it("should tell the user that there is not known exchange", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { + useComponentStateFromURI({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { listExchanges: async () => ({ exchanges }), getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ amount: "EUR:2", @@ -122,7 +122,7 @@ describe("Withdraw CTA states", () => { it("should be able to withdraw if tos are ok", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { + useComponentStateFromURI({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { listExchanges: async () => ({ exchanges }), getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ amount: "ARS:2", @@ -188,7 +188,7 @@ describe("Withdraw CTA states", () => { it("should be accept the tos before withdraw", async () => { const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { + useComponentStateFromURI({ talerWithdrawUri: "taler-withdraw://", cancel: async () => { null } }, { listExchanges: async () => ({ exchanges }), getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ amount: "ARS:2", diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index 5949076e0..850bd6e8f 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -122,14 +122,17 @@ export function SuccessView(state: State.Success): VNode { <div style={{ display: "flex", + alignItems: "center", }} > <i18n.Translate>Exchange</i18n.Translate> - <SvgIcon - title="Edit" - dangerouslySetInnerHTML={{ __html: editIcon }} - color="black" - /> + <Link> + <SvgIcon + title="Edit" + dangerouslySetInnerHTML={{ __html: editIcon }} + color="black" + /> + </Link> </div> } text={<ExchangeDetails exchange={state.exchangeUrl} />} diff --git a/packages/taler-wallet-webextension/src/popup/Application.tsx b/packages/taler-wallet-webextension/src/popup/Application.tsx index 9ad979c93..a7c574b27 100644 --- a/packages/taler-wallet-webextension/src/popup/Application.tsx +++ b/packages/taler-wallet-webextension/src/popup/Application.tsx @@ -118,7 +118,7 @@ export function Application(): VNode { component={RedirectToWalletPage} /> <Route - path={Pages.balanceManualWithdraw.pattern} + path={Pages.ctaWithdrawManual.pattern} component={RedirectToWalletPage} /> <Route diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 316a1c0e5..b39a7936b 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -37,7 +37,10 @@ import { import { PaymentPage } from "../cta/Payment/index.js"; import { RefundPage } from "../cta/Refund/index.js"; import { TipPage } from "../cta/Tip/index.js"; -import { WithdrawPage } from "../cta/Withdraw/index.js"; +import { + WithdrawPageFromParams, + WithdrawPageFromURI, +} from "../cta/Withdraw/index.js"; import { DepositPage as DepositPageCTA } from "../cta/Deposit/index.js"; import { Pages, WalletNavBar } from "../NavigationBar.js"; import { DeveloperPage } from "./DeveloperPage.js"; @@ -151,7 +154,10 @@ export function Application(): VNode { path={Pages.receiveCash.pattern} component={DestinationSelectionGetCash} goToWalletManualWithdraw={(amount?: string) => - redirectTo(Pages.balanceManualWithdraw({ amount })) + redirectTo(Pages.ctaWithdrawManual({ amount })) + } + goToWalletWalletInvoice={(amount?: string) => + redirectTo(Pages.ctaWithdrawManual({ amount })) } /> <Route @@ -163,12 +169,6 @@ export function Application(): VNode { /> <Route - path={Pages.balanceManualWithdraw.pattern} - component={ManualWithdrawPage} - onCancel={() => redirectTo(Pages.balance)} - /> - - <Route path={Pages.balanceDeposit.pattern} component={DepositPage} onCancel={(currency: string) => { @@ -237,7 +237,7 @@ export function Application(): VNode { path={Pages.ctaPay} component={PaymentPage} goToWalletManualWithdraw={(amount?: string) => - redirectTo(Pages.balanceManualWithdraw({ amount })) + redirectTo(Pages.ctaWithdrawManual({ amount })) } cancel={() => redirectTo(Pages.balance)} /> @@ -253,7 +253,12 @@ export function Application(): VNode { /> <Route path={Pages.ctaWithdraw} - component={WithdrawPage} + component={WithdrawPageFromURI} + cancel={() => redirectTo(Pages.balance)} + /> + <Route + path={Pages.ctaWithdrawManual.pattern} + component={WithdrawPageFromParams} cancel={() => redirectTo(Pages.balance)} /> <Route diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx index b9c83c06e..5320c6fe2 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx @@ -176,7 +176,9 @@ export function CreateManualWithdraw({ return ( <section> <SubTitle> - <i18n.Translate>Manual Withdrawal</i18n.Translate> + <i18n.Translate> + Manual Withdrawal for {state.currency.value} + </i18n.Translate> </SubTitle> <LightText> <i18n.Translate> @@ -212,7 +214,9 @@ export function CreateManualWithdraw({ /> )} <SubTitle> - <i18n.Translate>Manual Withdrawal</i18n.Translate> + <i18n.Translate> + Manual Withdrawal for {state.currency.value} + </i18n.Translate> </SubTitle> <LightText> <i18n.Translate> diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx index c62504538..4952ad225 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx @@ -18,21 +18,17 @@ import { Amounts } from "@gnu-taler/taler-util"; import { styled } from "@linaria/react"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { ErrorMessage } from "../components/ErrorMessage.js"; import { Loading } from "../components/Loading.js"; import { LoadingError } from "../components/LoadingError.js"; import { SelectList } from "../components/SelectList.js"; import { Input, - InputWithLabel, LightText, LinkPrimary, - SubTitle, SvgIcon, } from "../components/styled/index.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; -import { Alert } from "../mui/Alert.js"; import { Button } from "../mui/Button.js"; import { Grid } from "../mui/Grid.js"; import { Paper } from "../mui/Paper.js"; @@ -54,6 +50,7 @@ interface Props { action: "send" | "get"; amount?: string; goToWalletManualWithdraw: (amount: string) => void; + goToWalletWalletInvoice: (amount: string) => void; } type Contact = { @@ -264,6 +261,7 @@ function RowExample({ export function DestinationSelectionGetCash({ amount: initialAmount, goToWalletManualWithdraw, + goToWalletWalletInvoice, }: Props): VNode { const parsedInitialAmount = !initialAmount ? undefined @@ -293,6 +291,7 @@ export function DestinationSelectionGetCash({ description: "account ending with 3454", }, ]; + const previous = previous1; if (!currency) { return ( @@ -331,13 +330,13 @@ export function DestinationSelectionGetCash({ </Grid> <Grid container spacing={1} columns={1}> - {previous2.length > 0 ? ( + {previous.length > 0 ? ( <Fragment> <p>Use previous origins:</p> <Grid item xs={1}> <Paper style={{ padding: 8 }}> <ContactTable> - {previous2.map((info, i) => ( + {previous.map((info, i) => ( <tr key={i}> <td> <RowExample info={info} disabled={invalid} /> @@ -349,9 +348,15 @@ export function DestinationSelectionGetCash({ </Grid> </Fragment> ) : undefined} - <Grid item> - <p>Or specify a new origin for the money</p> - </Grid> + {previous.length > 0 ? ( + <Grid item> + <p>Or specify a new origin for the money</p> + </Grid> + ) : ( + <Grid item> + <p>Specify a origin for the money</p> + </Grid> + )} <Grid item container columns={2} spacing={1}> <Grid item xs={1}> <Paper style={{ padding: 8 }}> @@ -369,7 +374,12 @@ export function DestinationSelectionGetCash({ <Grid item xs={1}> <Paper style={{ padding: 8 }}> <p>From another wallet</p> - <Button disabled>Invoice</Button> + <Button + disabled={invalid} + onClick={async () => goToWalletWalletInvoice(currencyAndAmount)} + > + Invoice + </Button> </Paper> </Grid> </Grid> @@ -409,6 +419,7 @@ export function DestinationSelectionSendCash({ description: "account ending with 3454", }, ]; + const previous = previous1; if (!currency) { return <div>currency not provided</div>; @@ -440,13 +451,13 @@ export function DestinationSelectionSendCash({ </div> <Grid container spacing={1} columns={1}> - {previous2.length > 0 ? ( + {previous.length > 0 ? ( <Fragment> <p>Use previous destinations:</p> <Grid item xs={1}> <Paper style={{ padding: 8 }}> <ContactTable> - {previous2.map((info, i) => ( + {previous.map((info, i) => ( <tr key={i}> <td> <RowExample info={info} disabled={invalid} /> @@ -458,9 +469,15 @@ export function DestinationSelectionSendCash({ </Grid> </Fragment> ) : undefined} - <Grid item> - <p>Or specify a new destination for the money</p> - </Grid> + {previous.length > 0 ? ( + <Grid item> + <p>Or specify a new destination for the money</p> + </Grid> + ) : ( + <Grid item> + <p>Specify a destination for the money</p> + </Grid> + )} <Grid item container columns={2} spacing={1}> <Grid item xs={1}> <Paper style={{ padding: 8 }}> diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts new file mode 100644 index 000000000..2773e66f7 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts @@ -0,0 +1,63 @@ +/* + 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 { Loading } from "../../components/Loading.js"; +import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { compose, StateViewMap } from "../../utils/index.js"; +import { LoadingUriView, ReadyView } from "./views.js"; +import * as wxApi from "../../wxApi.js"; +import { useComponentState } from "./state.js"; + +export interface Props { + p: string; +} + +export type State = + | State.Loading + | State.LoadingUriError + | State.Ready; + +export namespace State { + + export interface Loading { + status: "loading"; + error: undefined; + } + + export interface LoadingUriError { + status: "loading-uri"; + error: HookError; + } + + export interface BaseInfo { + error: undefined; + } + export interface Ready extends BaseInfo { + status: "ready"; + error: undefined; + } +} + +const viewMapping: StateViewMap<State> = { + loading: Loading, + "loading-uri": LoadingUriView, + "ready": ReadyView, +}; + + +export const ComponentName = compose("ComponentName", (p: Props) => useComponentState(p, wxApi), viewMapping) + + diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts new file mode 100644 index 000000000..45b174063 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts @@ -0,0 +1,28 @@ +/* + 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 * as wxApi from "../../wxApi.js"; +import { Props, State } from "./index.js"; + +export function useComponentState( + { p }: Props, + api: typeof wxApi, +): State { + return { + status: "ready", + error: undefined, + } +} diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx new file mode 100644 index 000000000..696e424c4 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx @@ -0,0 +1,29 @@ +/* + 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { createExample } from "../../test-utils.js"; +import { ReadyView } from "./views.js"; + +export default { + title: "example", +}; + +export const Ready = createExample(ReadyView, {}); diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/test.ts b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/test.ts new file mode 100644 index 000000000..631e76d01 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/test.ts @@ -0,0 +1,31 @@ +/* + 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { expect } from "chai"; + +describe("test description", () => { + + it("should assert", () => { + + expect([]).deep.equals([]) + }); +}) + diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx new file mode 100644 index 000000000..5784a7db5 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx @@ -0,0 +1,37 @@ +/* + 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 { h, VNode } from "preact"; +import { LoadingError } from "../../components/LoadingError.js"; +import { useTranslationContext } from "../../context/translation.js"; +import { State } from "./index.js"; + +export function LoadingUriView({ error }: State.LoadingUriError): VNode { + const { i18n } = useTranslationContext(); + + return ( + <LoadingError + title={<i18n.Translate>Could not load</i18n.Translate>} + error={error} + /> + ); +} + +export function ReadyView({ error }: State.Ready): VNode { + const { i18n } = useTranslationContext(); + + return <div />; +} diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts index 1484ef7f4..37fdc8f5e 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts @@ -88,7 +88,7 @@ const viewMapping: StateViewMap<State> = { "ready": ReadyView, }; -export const ExchangeSelectionPage = compose("Tip", (p: Props) => useComponentState(p, wxApi), viewMapping) +export const ExchangeSelectionPage = compose("ExchangeSelectionPage", (p: Props) => useComponentState(p, wxApi), viewMapping) export interface FeeDescription { value: AmountJson; diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/index.ts b/packages/taler-wallet-webextension/src/wallet/Invoice/index.ts new file mode 100644 index 000000000..edb8721ac --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Invoice/index.ts @@ -0,0 +1,62 @@ +/* + 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 { Loading } from "../../components/Loading.js"; +import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { compose, StateViewMap } from "../../utils/index.js"; +import { LoadingUriView, ReadyView } from "./views.js"; +import * as wxApi from "../../wxApi.js"; +import { useComponentState } from "./state.js"; + +export interface Props { + p: string; +} + +export type State = + | State.Loading + | State.LoadingUriError + | State.Ready; + +export namespace State { + + export interface Loading { + status: "loading"; + error: undefined; + } + + export interface LoadingUriError { + status: "loading-uri"; + error: HookError; + } + + export interface BaseInfo { + error: undefined; + } + export interface Ready extends BaseInfo { + status: "ready"; + error: undefined; + } +} + +const viewMapping: StateViewMap<State> = { + loading: Loading, + "loading-uri": LoadingUriView, + "ready": ReadyView, +}; + + +export const InvoicePage = compose("InvoicePage", (p: Props) => useComponentState(p, wxApi), viewMapping) + diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/state.ts b/packages/taler-wallet-webextension/src/wallet/Invoice/state.ts new file mode 100644 index 000000000..45b174063 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Invoice/state.ts @@ -0,0 +1,28 @@ +/* + 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 * as wxApi from "../../wxApi.js"; +import { Props, State } from "./index.js"; + +export function useComponentState( + { p }: Props, + api: typeof wxApi, +): State { + return { + status: "ready", + error: undefined, + } +} diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/stories.tsx b/packages/taler-wallet-webextension/src/wallet/Invoice/stories.tsx new file mode 100644 index 000000000..75f78be1d --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Invoice/stories.tsx @@ -0,0 +1,29 @@ +/* + 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { createExample } from "../../test-utils.js"; +import { ReadyView } from "./views.js"; + +export default { + title: "wallet/invoice", +}; + +export const Ready = createExample(ReadyView, {}); diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/test.ts b/packages/taler-wallet-webextension/src/wallet/Invoice/test.ts new file mode 100644 index 000000000..631e76d01 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Invoice/test.ts @@ -0,0 +1,31 @@ +/* + 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { expect } from "chai"; + +describe("test description", () => { + + it("should assert", () => { + + expect([]).deep.equals([]) + }); +}) + diff --git a/packages/taler-wallet-webextension/src/wallet/Invoice/views.tsx b/packages/taler-wallet-webextension/src/wallet/Invoice/views.tsx new file mode 100644 index 000000000..5784a7db5 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/Invoice/views.tsx @@ -0,0 +1,37 @@ +/* + 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 { h, VNode } from "preact"; +import { LoadingError } from "../../components/LoadingError.js"; +import { useTranslationContext } from "../../context/translation.js"; +import { State } from "./index.js"; + +export function LoadingUriView({ error }: State.LoadingUriError): VNode { + const { i18n } = useTranslationContext(); + + return ( + <LoadingError + title={<i18n.Translate>Could not load</i18n.Translate>} + error={error} + /> + ); +} + +export function ReadyView({ error }: State.Ready): VNode { + const { i18n } = useTranslationContext(); + + return <div />; +} |