add a taler action from the history page
This commit is contained in:
parent
045a7c0aa1
commit
54d4a1efe0
@ -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, {});
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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: [
|
||||||
|
@ -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 (
|
||||||
|
Loading…
Reference in New Issue
Block a user