add a taler action from the history page

This commit is contained in:
Sebastian 2021-11-30 17:29:33 -03:00
parent 045a7c0aa1
commit 54d4a1efe0
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
7 changed files with 235 additions and 87 deletions

View File

@ -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, {});

View File

@ -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>
);
}

View File

@ -23,10 +23,11 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { PopupBox } from "../components/styled"; import { ButtonPrimary } from "../components/styled/index";
import { TransactionItem } from "../components/TransactionItem"; import { TransactionItem } from "../components/TransactionItem";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import { AddNewActionView } from "./AddNewActionView";
export function HistoryPage(): VNode { export function HistoryPage(): VNode {
const [transactions, setTransactions] = useState< const [transactions, setTransactions] = useState<
@ -45,6 +46,12 @@ export function HistoryPage(): VNode {
fetchData(); fetchData();
}, []); }, []);
const [addingAction, setAddingAction] = useState(false);
if (addingAction) {
return <AddNewActionView onCancel={() => setAddingAction(false)} />;
}
if (!transactions) { if (!transactions) {
return <div>Loading ...</div>; return <div>Loading ...</div>;
} }
@ -53,6 +60,7 @@ export function HistoryPage(): VNode {
<HistoryView <HistoryView
balances={balanceWithoutError} balances={balanceWithoutError}
list={[...transactions.transactions].reverse()} list={[...transactions.transactions].reverse()}
onAddNewAction={() => setAddingAction(true)}
/> />
); );
} }
@ -65,31 +73,42 @@ function amountToString(c: AmountString): string {
export function HistoryView({ export function HistoryView({
list, list,
balances, balances,
onAddNewAction,
}: { }: {
list: Transaction[]; list: Transaction[];
balances: Balance[]; balances: Balance[];
onAddNewAction: () => void;
}): VNode { }): VNode {
const multiCurrency = balances.length > 1; const multiCurrency = balances.length > 1;
return ( return (
<Fragment> <Fragment>
{balances.length > 0 && ( <header>
<header> {balances.length > 0 ? (
{multiCurrency ? ( <Fragment>
<div class="title"> {multiCurrency ? (
Balance:{" "} <div class="title">
<ul style={{ margin: 0 }}> Balance:{" "}
{balances.map((b, i) => ( <ul style={{ margin: 0 }}>
<li key={i}>{b.available}</li> {balances.map((b, i) => (
))} <li key={i}>{b.available}</li>
</ul> ))}
</div> </ul>
) : ( </div>
<div class="title"> ) : (
Balance: <span>{amountToString(balances[0].available)}</span> <div class="title">
</div> Balance: <span>{amountToString(balances[0].available)}</span>
)} </div>
</header> )}
)} </Fragment>
) : (
<div />
)}
<div>
<ButtonPrimary onClick={onAddNewAction}>
<b>+</b>
</ButtonPrimary>
</div>
</header>
{list.length === 0 ? ( {list.length === 0 ? (
<section data-expanded data-centered> <section data-expanded data-centered>
<p> <p>

View File

@ -22,6 +22,7 @@
import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util"; import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
import { Fragment, h } from "preact"; import { Fragment, h } from "preact";
import { ButtonPrimary, ButtonSuccess } from "../components/styled/index"; import { ButtonPrimary, ButtonSuccess } from "../components/styled/index";
import { actionForTalerUri } from "../utils/index";
export interface Props { export interface Props {
url: string; url: string;
@ -108,50 +109,3 @@ export function TalerActionFound({ url, onDismiss }: Props) {
</Fragment> </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;
}

View File

@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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> { function getJsonIfOk(r: Response): Promise<any> {
@ -164,3 +164,52 @@ export function amountToString(text: AmountJson): string {
return `${amount} ${aj.currency}`; 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;
}

View File

@ -127,6 +127,11 @@ export const Empty = createExample(TestedComponent, {
], ],
}); });
export const EmptyWithNoBalance = createExample(TestedComponent, {
list: [],
balances: [],
});
export const One = createExample(TestedComponent, { export const One = createExample(TestedComponent, {
list: [exampleData.withdraw], list: [exampleData.withdraw],
balances: [ balances: [

View File

@ -21,10 +21,12 @@ import {
Transaction, Transaction,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; 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 { Time } from "../components/Time";
import { TransactionItem } from "../components/TransactionItem"; import { TransactionItem } from "../components/TransactionItem";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { AddNewActionView } from "../popup/AddNewActionView";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
export function HistoryPage(): VNode { export function HistoryPage(): VNode {
@ -37,6 +39,12 @@ export function HistoryPage(): VNode {
NotificationType.WithdrawGroupFinished, NotificationType.WithdrawGroupFinished,
]); ]);
const [addingAction, setAddingAction] = useState(false);
if (addingAction) {
return <AddNewActionView onCancel={() => setAddingAction(false)} />;
}
if (!transactionQuery) { if (!transactionQuery) {
return <div>Loading ...</div>; return <div>Loading ...</div>;
} }
@ -48,6 +56,7 @@ export function HistoryPage(): VNode {
<HistoryView <HistoryView
balances={balanceWithoutError} balances={balanceWithoutError}
list={[...transactionQuery.response.transactions].reverse()} list={[...transactionQuery.response.transactions].reverse()}
onAddNewAction={() => setAddingAction(true)}
/> />
); );
} }
@ -65,9 +74,11 @@ function normalizeToDay(x: number): number {
export function HistoryView({ export function HistoryView({
list, list,
balances, balances,
onAddNewAction,
}: { }: {
list: Transaction[]; list: Transaction[];
balances: Balance[]; balances: Balance[];
onAddNewAction: () => void;
}): VNode { }): VNode {
const byDate = list.reduce((rv, x) => { const byDate = list.reduce((rv, x) => {
const theDate = const theDate =
@ -83,25 +94,34 @@ export function HistoryView({
return ( return (
<Fragment> <Fragment>
{balances.length > 0 && ( <header>
<header> {balances.length > 0 ? (
{balances.length === 1 && ( <Fragment>
<div class="title"> {balances.length === 1 && (
Balance: <span>{amountToString(balances[0].available)}</span> <div class="title">
</div> Balance: <span>{amountToString(balances[0].available)}</span>
)} </div>
{balances.length > 1 && ( )}
<div class="title"> {balances.length > 1 && (
Balance:{" "} <div class="title">
<ul style={{ margin: 0 }}> Balance:{" "}
{balances.map((b, i) => ( <ul style={{ margin: 0 }}>
<li key={i}>{b.available}</li> {balances.map((b, i) => (
))} <li key={i}>{b.available}</li>
</ul> ))}
</div> </ul>
)} </div>
</header> )}
)} </Fragment>
) : (
<div />
)}
<div>
<ButtonPrimary onClick={onAddNewAction}>
<b>+</b>
</ButtonPrimary>
</div>
</header>
<section> <section>
{Object.keys(byDate).map((d, i) => { {Object.keys(byDate).map((d, i) => {
return ( return (