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";
|
||||
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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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: [
|
||||
|
@ -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 (
|
||||
|
Loading…
Reference in New Issue
Block a user