diff options
Diffstat (limited to 'packages/taler-wallet-webextension')
7 files changed, 235 insertions, 87 deletions
diff --git a/packages/taler-wallet-webextension/src/popup/AddNewActionView.stories.tsx b/packages/taler-wallet-webextension/src/popup/AddNewActionView.stories.tsx new file mode 100644 index 000000000..6ee56ef77 --- /dev/null +++ b/packages/taler-wallet-webextension/src/popup/AddNewActionView.stories.tsx @@ -0,0 +1,33 @@ +/* + This file is part of GNU Taler + (C) 2021 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"; +import { AddNewActionView as TestedComponent } from "./AddNewActionView"; + +export default { + title: "popup/add new action", + component: TestedComponent, + argTypes: { + setDeviceName: () => Promise.resolve(), + }, +}; + +export const Initial = createExample(TestedComponent, {}); diff --git a/packages/taler-wallet-webextension/src/popup/AddNewActionView.tsx b/packages/taler-wallet-webextension/src/popup/AddNewActionView.tsx new file mode 100644 index 000000000..876b1a83c --- /dev/null +++ b/packages/taler-wallet-webextension/src/popup/AddNewActionView.tsx @@ -0,0 +1,68 @@ +import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { + Button, + ButtonSuccess, + InputWithLabel, +} from "../components/styled/index"; +import { actionForTalerUri } from "../utils/index"; + +export interface Props { + onCancel: () => void; +} + +function buttonLabelByTalerType(type: TalerUriType): string { + switch (type) { + case TalerUriType.TalerNotifyReserve: + return "Open reserve page"; + case TalerUriType.TalerPay: + return "Open pay page"; + case TalerUriType.TalerRefund: + return "Open refund page"; + case TalerUriType.TalerTip: + return "Open tip page"; + case TalerUriType.TalerWithdraw: + return "Open withdraw page"; + } + return ""; +} + +export function AddNewActionView({ onCancel }: Props): VNode { + const [url, setUrl] = useState(""); + const uriType = classifyTalerUri(url); + + return ( + <Fragment> + <section> + <InputWithLabel + invalid={url !== "" && uriType === TalerUriType.Unknown} + > + <label>GNU Taler URI</label> + <div> + <input + style={{ width: "100%" }} + type="text" + value={url} + placeholder="taler://pay/...." + onInput={(e) => setUrl(e.currentTarget.value)} + /> + </div> + </InputWithLabel> + </section> + <footer> + <Button onClick={onCancel}>Back</Button> + {uriType !== TalerUriType.Unknown && ( + <ButtonSuccess + onClick={() => { + // eslint-disable-next-line no-undef + chrome.tabs.create({ url: actionForTalerUri(uriType, url) }); + }} + > + {buttonLabelByTalerType(uriType)} + </ButtonSuccess> + )} + </footer> + </Fragment> + ); +} diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx index b23b4781f..f897299d8 100644 --- a/packages/taler-wallet-webextension/src/popup/History.tsx +++ b/packages/taler-wallet-webextension/src/popup/History.tsx @@ -23,10 +23,11 @@ import { } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; -import { PopupBox } from "../components/styled"; +import { ButtonPrimary } from "../components/styled/index"; import { TransactionItem } from "../components/TransactionItem"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import * as wxApi from "../wxApi"; +import { AddNewActionView } from "./AddNewActionView"; export function HistoryPage(): VNode { const [transactions, setTransactions] = useState< @@ -45,6 +46,12 @@ export function HistoryPage(): VNode { fetchData(); }, []); + const [addingAction, setAddingAction] = useState(false); + + if (addingAction) { + return <AddNewActionView onCancel={() => setAddingAction(false)} />; + } + if (!transactions) { return <div>Loading ...</div>; } @@ -53,6 +60,7 @@ export function HistoryPage(): VNode { <HistoryView balances={balanceWithoutError} list={[...transactions.transactions].reverse()} + onAddNewAction={() => setAddingAction(true)} /> ); } @@ -65,31 +73,42 @@ function amountToString(c: AmountString): string { export function HistoryView({ list, balances, + onAddNewAction, }: { list: Transaction[]; balances: Balance[]; + onAddNewAction: () => void; }): VNode { const multiCurrency = balances.length > 1; return ( <Fragment> - {balances.length > 0 && ( - <header> - {multiCurrency ? ( - <div class="title"> - Balance:{" "} - <ul style={{ margin: 0 }}> - {balances.map((b, i) => ( - <li key={i}>{b.available}</li> - ))} - </ul> - </div> - ) : ( - <div class="title"> - Balance: <span>{amountToString(balances[0].available)}</span> - </div> - )} - </header> - )} + <header> + {balances.length > 0 ? ( + <Fragment> + {multiCurrency ? ( + <div class="title"> + Balance:{" "} + <ul style={{ margin: 0 }}> + {balances.map((b, i) => ( + <li key={i}>{b.available}</li> + ))} + </ul> + </div> + ) : ( + <div class="title"> + Balance: <span>{amountToString(balances[0].available)}</span> + </div> + )} + </Fragment> + ) : ( + <div /> + )} + <div> + <ButtonPrimary onClick={onAddNewAction}> + <b>+</b> + </ButtonPrimary> + </div> + </header> {list.length === 0 ? ( <section data-expanded data-centered> <p> diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx index b2220e37b..40e9111fb 100644 --- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx +++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx @@ -22,6 +22,7 @@ import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util"; import { Fragment, h } from "preact"; import { ButtonPrimary, ButtonSuccess } from "../components/styled/index"; +import { actionForTalerUri } from "../utils/index"; export interface Props { url: string; @@ -108,50 +109,3 @@ export function TalerActionFound({ url, onDismiss }: Props) { </Fragment> ); } - -function actionForTalerUri( - uriType: TalerUriType, - talerUri: string, -): string | undefined { - switch (uriType) { - case TalerUriType.TalerWithdraw: - return makeExtensionUrlWithParams("static/wallet.html#/withdraw", { - talerWithdrawUri: talerUri, - }); - case TalerUriType.TalerPay: - return makeExtensionUrlWithParams("static/wallet.html#/pay", { - talerPayUri: talerUri, - }); - case TalerUriType.TalerTip: - return makeExtensionUrlWithParams("static/wallet.html#/tip", { - talerTipUri: talerUri, - }); - case TalerUriType.TalerRefund: - return makeExtensionUrlWithParams("static/wallet.html#/refund", { - talerRefundUri: talerUri, - }); - case TalerUriType.TalerNotifyReserve: - // FIXME: implement - break; - default: - console.warn( - "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.", - ); - break; - } - return undefined; -} - -function makeExtensionUrlWithParams( - url: string, - params?: { [name: string]: string | undefined }, -): string { - const innerUrl = new URL(chrome.extension.getURL("/" + url)); - if (params) { - const hParams = Object.keys(params) - .map((k) => `${k}=${params[k]}`) - .join("&"); - innerUrl.hash = innerUrl.hash + "?" + hParams; - } - return innerUrl.href; -} diff --git a/packages/taler-wallet-webextension/src/utils/index.ts b/packages/taler-wallet-webextension/src/utils/index.ts index 15081f920..8eb89d58f 100644 --- a/packages/taler-wallet-webextension/src/utils/index.ts +++ b/packages/taler-wallet-webextension/src/utils/index.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson, Amounts, GetExchangeTosResult } from "@gnu-taler/taler-util"; +import { AmountJson, Amounts, GetExchangeTosResult, TalerUriType } from "@gnu-taler/taler-util"; function getJsonIfOk(r: Response): Promise<any> { @@ -164,3 +164,52 @@ export function amountToString(text: AmountJson): string { return `${amount} ${aj.currency}`; } +export function actionForTalerUri( + uriType: TalerUriType, + talerUri: string, +): string | undefined { + switch (uriType) { + case TalerUriType.TalerWithdraw: + return makeExtensionUrlWithParams("static/wallet.html#/withdraw", { + talerWithdrawUri: talerUri, + }); + case TalerUriType.TalerPay: + return makeExtensionUrlWithParams("static/wallet.html#/pay", { + talerPayUri: talerUri, + }); + case TalerUriType.TalerTip: + return makeExtensionUrlWithParams("static/wallet.html#/tip", { + talerTipUri: talerUri, + }); + case TalerUriType.TalerRefund: + return makeExtensionUrlWithParams("static/wallet.html#/refund", { + talerRefundUri: talerUri, + }); + case TalerUriType.TalerNotifyReserve: + // FIXME: implement + break; + default: + console.warn( + "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.", + ); + break; + } + return undefined; +} + +function makeExtensionUrlWithParams( + url: string, + params?: { [name: string]: string | undefined }, +): string { + // eslint-disable-next-line no-undef + const innerUrl = new URL(chrome.extension.getURL("/" + url)); + if (params) { + const hParams = Object.keys(params) + .map((k) => `${k}=${params[k]}`) + .join("&"); + innerUrl.hash = innerUrl.hash + "?" + hParams; + } + return innerUrl.href; +} + + diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx index 0f471ac30..ce4b0fb74 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx @@ -127,6 +127,11 @@ export const Empty = createExample(TestedComponent, { ], }); +export const EmptyWithNoBalance = createExample(TestedComponent, { + list: [], + balances: [], +}); + export const One = createExample(TestedComponent, { list: [exampleData.withdraw], balances: [ diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index bc8ef734a..58db0360b 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -21,10 +21,12 @@ import { Transaction, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; -import { DateSeparator } from "../components/styled"; +import { useState } from "preact/hooks"; +import { ButtonPrimary, DateSeparator } from "../components/styled"; import { Time } from "../components/Time"; import { TransactionItem } from "../components/TransactionItem"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; +import { AddNewActionView } from "../popup/AddNewActionView"; import * as wxApi from "../wxApi"; export function HistoryPage(): VNode { @@ -37,6 +39,12 @@ export function HistoryPage(): VNode { NotificationType.WithdrawGroupFinished, ]); + const [addingAction, setAddingAction] = useState(false); + + if (addingAction) { + return <AddNewActionView onCancel={() => setAddingAction(false)} />; + } + if (!transactionQuery) { return <div>Loading ...</div>; } @@ -48,6 +56,7 @@ export function HistoryPage(): VNode { <HistoryView balances={balanceWithoutError} list={[...transactionQuery.response.transactions].reverse()} + onAddNewAction={() => setAddingAction(true)} /> ); } @@ -65,9 +74,11 @@ function normalizeToDay(x: number): number { export function HistoryView({ list, balances, + onAddNewAction, }: { list: Transaction[]; balances: Balance[]; + onAddNewAction: () => void; }): VNode { const byDate = list.reduce((rv, x) => { const theDate = @@ -83,25 +94,34 @@ export function HistoryView({ return ( <Fragment> - {balances.length > 0 && ( - <header> - {balances.length === 1 && ( - <div class="title"> - Balance: <span>{amountToString(balances[0].available)}</span> - </div> - )} - {balances.length > 1 && ( - <div class="title"> - Balance:{" "} - <ul style={{ margin: 0 }}> - {balances.map((b, i) => ( - <li key={i}>{b.available}</li> - ))} - </ul> - </div> - )} - </header> - )} + <header> + {balances.length > 0 ? ( + <Fragment> + {balances.length === 1 && ( + <div class="title"> + Balance: <span>{amountToString(balances[0].available)}</span> + </div> + )} + {balances.length > 1 && ( + <div class="title"> + Balance:{" "} + <ul style={{ margin: 0 }}> + {balances.map((b, i) => ( + <li key={i}>{b.available}</li> + ))} + </ul> + </div> + )} + </Fragment> + ) : ( + <div /> + )} + <div> + <ButtonPrimary onClick={onAddNewAction}> + <b>+</b> + </ButtonPrimary> + </div> + </header> <section> {Object.keys(byDate).map((d, i) => { return ( |