diff options
Diffstat (limited to 'packages')
10 files changed, 447 insertions, 31 deletions
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts index 130c042b6..522a62316 100644 --- a/packages/taler-wallet-core/src/dev-experiments.ts +++ b/packages/taler-wallet-core/src/dev-experiments.ts @@ -97,7 +97,7 @@ export async function applyDevExperiment( const newRg: DenomLossEventRecord = { amount: "TESTKUDOS:42", currency: "TESTKUDOS", - exchangeBaseUrl: "https://exchange.devexperiment.taler.net/", + exchangeBaseUrl: "https://exchange.test.taler.net/", denomLossEventId: eventId, denomPubHashes: [ encodeCrock(getRandomBytes(64)), diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx index 78a526997..fc917088d 100644 --- a/packages/taler-wallet-webextension/src/NavigationBar.tsx +++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx @@ -127,6 +127,7 @@ export const Pages = { ctaRefund: "/cta/refund", ctaWithdraw: "/cta/withdraw", ctaDeposit: "/cta/deposit", + ctaExperiment: "/cta/experiment", ctaInvoiceCreate: pageDefinition<{ amount?: string }>( "/cta/invoice/create/:amount?", ), @@ -151,7 +152,7 @@ const talerUriActionToPageName: { [TalerUriAction.Restore]: "ctaRecovery", [TalerUriAction.PayTemplate]: "ctaPayTemplate", [TalerUriAction.WithdrawExchange]: "ctaWithdrawManual", - [TalerUriAction.DevExperiment]: undefined, + [TalerUriAction.DevExperiment]: "ctaExperiment", }; export function getPathnameForTalerURI(talerUri: string): string | undefined { diff --git a/packages/taler-wallet-webextension/src/cta/DevExperiment/index.ts b/packages/taler-wallet-webextension/src/cta/DevExperiment/index.ts new file mode 100644 index 000000000..ec09fd9f1 --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/DevExperiment/index.ts @@ -0,0 +1,73 @@ +/* + 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 { ErrorAlertView } from "../../components/CurrentAlerts.js"; +import { Loading } from "../../components/Loading.js"; +import { ErrorAlert } from "../../context/alert.js"; +import { ButtonHandler } from "../../mui/handlers.js"; +import { StateViewMap, compose } from "../../utils/index.js"; +import { useComponentState } from "./state.js"; +import { InsertLostView, InsertPendingRefreshView, UnknownView } from "./views.js"; + +export interface Props { + talerExperimentUri: string | undefined; + onCancel: () => Promise<void>; + onSuccess: () => Promise<void>; +} + +export type State = State.Loading | State.LoadingUriError | State.Unknown | State.InsertLost | State.PendingRefresh; + +export namespace State { + export interface Loading { + status: "loading"; + error: undefined; + } + export interface LoadingUriError { + status: "error"; + error: ErrorAlert; + } + export interface InsertLost { + status: "insertLost"; + error: undefined; + confirm: ButtonHandler; + cancel: () => Promise<void>; + } + export interface PendingRefresh { + status: "pendingRefresh"; + error: undefined; + confirm: ButtonHandler; + cancel: () => Promise<void>; + } + export interface Unknown { + status: "unknown"; + experimentId: string; + error: undefined; + } +} + +const viewMapping: StateViewMap<State> = { + loading: Loading, + error: ErrorAlertView, + pendingRefresh: InsertPendingRefreshView, + insertLost: InsertLostView, + unknown: UnknownView, +}; + +export const DevExperimentPage = compose( + "DevExperiment", + (p: Props) => useComponentState(p), + viewMapping, +); diff --git a/packages/taler-wallet-webextension/src/cta/DevExperiment/state.ts b/packages/taler-wallet-webextension/src/cta/DevExperiment/state.ts new file mode 100644 index 000000000..774a1129d --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/DevExperiment/state.ts @@ -0,0 +1,83 @@ +/* + 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 { parseDevExperimentUri } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useAlertContext } from "../../context/alert.js"; +import { useBackendContext } from "../../context/backend.js"; +import { Props, State } from "./index.js"; + +export function useComponentState({ + talerExperimentUri, + onCancel, + onSuccess, +}: Props): State { + const api = useBackendContext(); + const { pushAlertOnError } = useAlertContext(); + const { i18n } = useTranslationContext(); + + async function doApply(): Promise<void> { + if (!talerExperimentUri) return; + await api.wallet.call(WalletApiOperation.ApplyDevExperiment, { + devExperimentUri: talerExperimentUri + }) + // const resp = await api.wallet.call(WalletApiOperation.CreateDepositGroup, { + // amount: Amounts.stringify(amount), + // depositPaytoUri: uri, + // }); + onSuccess(); + } + const uri = talerExperimentUri === undefined ? undefined : parseDevExperimentUri(talerExperimentUri); + + if (!uri) { + return { + status: "error", + error: { + type: "error", + message: i18n.str`Invalid dev experiment URI.`, + description: i18n.str`URI: ${talerExperimentUri}`, + cause: {}, + context: {}, + }, + }; + } + if (uri.devExperimentId === "insert-denom-loss") { + return { + status: "insertLost", + error: undefined, + confirm: { + onClick: pushAlertOnError(doApply), + }, + cancel: onCancel, + }; + } + if (uri.devExperimentId === "insert-pending-refresh") { + return { + status: "pendingRefresh", + error: undefined, + confirm: { + onClick: pushAlertOnError(doApply), + }, + cancel: onCancel, + }; + } + return { + status: "unknown", + error: undefined, + experimentId: uri.devExperimentId, + } +} diff --git a/packages/taler-wallet-webextension/src/cta/DevExperiment/stories.tsx b/packages/taler-wallet-webextension/src/cta/DevExperiment/stories.tsx new file mode 100644 index 000000000..c9851495f --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/DevExperiment/stories.tsx @@ -0,0 +1,33 @@ +/* + 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 * as tests from "@gnu-taler/web-util/testing"; +import { InsertLostView } from "./views.js"; + +export default { + title: "dev-experiment", +}; + +export const Ready = tests.createExample(InsertLostView, { + status: "insertLost", + confirm: {}, + error: undefined, +}); diff --git a/packages/taler-wallet-webextension/src/cta/DevExperiment/test.ts b/packages/taler-wallet-webextension/src/cta/DevExperiment/test.ts new file mode 100644 index 000000000..d4f2ca8b1 --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/DevExperiment/test.ts @@ -0,0 +1,65 @@ +/* + 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 * as tests from "@gnu-taler/web-util/testing"; +import { expect } from "chai"; +import { createWalletApiMock } from "../../test-utils.js"; +import { Props } from "./index.js"; +import { useComponentState } from "./state.js"; + +describe("DevExperiment CTA states", () => { + it("should tell the user that the URI is missing", async () => { + const { handler, TestingContext } = createWalletApiMock(); + + const props: Props = { + talerExperimentUri: undefined, + onCancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + }; + + const hookBehavior = await tests.hookBehaveLikeThis( + useComponentState, + props, + [ + ({ status }) => { + expect(status).equals("error"); + }, + ({ status, error }) => { + expect(status).equals("error"); + + if (!error) expect.fail(); + // if (!error.hasError) expect.fail(); + // if (error.operational) expect.fail(); + // expect(error.description).eq("ERROR_NO-URI-FOR-DEPOSIT"); + }, + ], + TestingContext, + ); + + expect(hookBehavior).deep.equal({ result: "ok" }); + expect(handler.getCallingQueueState()).eq("empty"); + }); + +}); diff --git a/packages/taler-wallet-webextension/src/cta/DevExperiment/views.tsx b/packages/taler-wallet-webextension/src/cta/DevExperiment/views.tsx new file mode 100644 index 000000000..afad17ad1 --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/DevExperiment/views.tsx @@ -0,0 +1,74 @@ +/* + 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 { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, h, VNode } from "preact"; +import { Amount } from "../../components/Amount.js"; +import { Part } from "../../components/Part.js"; +import { Button } from "../../mui/Button.js"; +import { State } from "./index.js"; + +/** + * + * @author sebasjm + */ + +export function InsertLostView(state: State.InsertLost): VNode { + const { i18n } = useTranslationContext(); + return <Fragment> + <section> + <Part + title={i18n.str`Experiment`} + text={i18n.str`Insert lost denomination`} + /> + </section> + <section> + <Button + variant="contained" + color="success" + onClick={state.confirm.onClick} + > + <i18n.Translate>Apply</i18n.Translate> + </Button> + </section> + </Fragment> +} + +export function InsertPendingRefreshView(state: State.PendingRefresh): VNode { + const { i18n } = useTranslationContext(); + return <Fragment> + <section> + <Part + title={i18n.str`Experiment`} + text={i18n.str`Pending refresh`} + /> + </section> + <section> + <Button + variant="contained" + color="success" + onClick={state.confirm.onClick} + > + <i18n.Translate>Apply</i18n.Translate> + </Button> + </section> + </Fragment> +} + +export function UnknownView(state: State.Unknown): VNode { + return <div>unknown experiment "{state.experimentId}"</div> +} diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 62a519f06..1fc1e46f4 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -83,6 +83,7 @@ import { TransactionPage } from "./Transaction.js"; import { WelcomePage } from "./Welcome.js"; import { WalletActivity } from "../components/WalletActivity.js"; import { EnabledBySettings } from "../components/EnabledBySettings.js"; +import { DevExperimentPage } from "../cta/DevExperiment/index.js"; export function Application(): VNode { const { i18n } = useTranslationContext(); @@ -497,6 +498,18 @@ export function Application(): VNode { </CallToActionTemplate> )} /> + <Route + path={Pages.ctaExperiment} + component={({ talerUri }: { talerUri: string }) => ( + <CallToActionTemplate title={i18n.str`Development experiment`}> + <DevExperimentPage + talerExperimentUri={decodeURIComponent(talerUri)} + onCancel={() => redirectTo(Pages.balanceHistory({}))} + onSuccess={() => redirectTo(Pages.balanceHistory({}))} + /> + </CallToActionTemplate> + )} + /> {/** * NOT FOUND diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index a5fa66cbe..11fb88cf6 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -19,6 +19,7 @@ import { AmountJson, Amounts, AmountString, + DenomLossEventType, MerchantInfo, NotificationType, OrderShortInfo, @@ -1027,36 +1028,108 @@ export function TransactionView({ } if (transaction.type === TransactionType.DenomLoss) { - return ( - <TransactionTemplate - transaction={transaction} - onDelete={onDelete} - onRetry={onRetry} - onAbort={onAbort} - onResume={onResume} - onSuspend={onSuspend} - onCancel={onCancel} - > - <Header - timestamp={transaction.timestamp} - type={i18n.str`Debit`} - total={effective} - kind="negative" - > - <i18n.Translate>Lost</i18n.Translate> - </Header> + switch (transaction.lossEventType) { + case DenomLossEventType.DenomExpired: { + return ( + <TransactionTemplate + transaction={transaction} + onDelete={onDelete} + onRetry={onRetry} + onAbort={onAbort} + onResume={onResume} + onSuspend={onSuspend} + onCancel={onCancel} + > + <Header + timestamp={transaction.timestamp} + type={i18n.str`Debit`} + total={effective} + kind="negative" + > + <i18n.Translate>Lost</i18n.Translate> + </Header> + + <Part + title={i18n.str`Exchange`} + text={transaction.exchangeBaseUrl as TranslatedString} + kind="neutral" + /> + <Part + title={i18n.str`Reason`} + text={i18n.str`Denomination expired.`} + /> + </TransactionTemplate> + ); + } + case DenomLossEventType.DenomVanished: { + return ( + <TransactionTemplate + transaction={transaction} + onDelete={onDelete} + onRetry={onRetry} + onAbort={onAbort} + onResume={onResume} + onSuspend={onSuspend} + onCancel={onCancel} + > + <Header + timestamp={transaction.timestamp} + type={i18n.str`Debit`} + total={effective} + kind="negative" + > + <i18n.Translate>Lost</i18n.Translate> + </Header> + + <Part + title={i18n.str`Exchange`} + text={transaction.exchangeBaseUrl as TranslatedString} + kind="neutral" + /> + <Part + title={i18n.str`Reason`} + text={i18n.str`Denomination vanished.`} + /> + </TransactionTemplate> + ); + } + case DenomLossEventType.DenomUnoffered: { + return ( + <TransactionTemplate + transaction={transaction} + onDelete={onDelete} + onRetry={onRetry} + onAbort={onAbort} + onResume={onResume} + onSuspend={onSuspend} + onCancel={onCancel} + > + <Header + timestamp={transaction.timestamp} + type={i18n.str`Debit`} + total={effective} + kind="negative" + > + <i18n.Translate>Lost</i18n.Translate> + </Header> + + <Part + title={i18n.str`Exchange`} + text={transaction.exchangeBaseUrl as TranslatedString} + kind="neutral" + /> + <Part + title={i18n.str`Reason`} + text={i18n.str`Denomination is unoffered.`} + /> + </TransactionTemplate> + ); + } + default: { + assertUnreachable(transaction.lossEventType) + } + } - <Part - title={i18n.str`Exchange`} - text={transaction.exchangeBaseUrl as TranslatedString} - kind="neutral" - /> - <Part - title={i18n.str`Reason`} - text={transaction.lossEventType as TranslatedString} - /> - </TransactionTemplate> - ); } if (transaction.type === TransactionType.Recoup) { throw Error("recoup transaction not implemented"); diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 44e4c0d48..a78e85aba 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -305,6 +305,7 @@ async function reinitWallet(): Promise<void> { config: { testing: { emitObservabilityEvents: settings.showWalletActivity, + devModeActive: settings.advancedMode, }, features: { allowHttp: settings.walletAllowHttp, |