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";
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>

View File

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

View File

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

View File

@ -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: [

View File

@ -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 (