reserveCreated new design
This commit is contained in:
parent
c33ed91971
commit
a994009d2f
@ -16,12 +16,31 @@
|
|||||||
|
|
||||||
import { URLSearchParams } from "./url.js";
|
import { URLSearchParams } from "./url.js";
|
||||||
|
|
||||||
interface PaytoUri {
|
export type PaytoUri = PaytoUriUnknown | PaytoUriIBAN | PaytoUriTalerBank;
|
||||||
|
|
||||||
|
interface PaytoUriGeneric {
|
||||||
targetType: string;
|
targetType: string;
|
||||||
targetPath: string;
|
targetPath: string;
|
||||||
params: { [name: string]: string };
|
params: { [name: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PaytoUriUnknown extends PaytoUriGeneric {
|
||||||
|
isKnown: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaytoUriIBAN extends PaytoUriGeneric {
|
||||||
|
isKnown: true;
|
||||||
|
targetType: 'iban',
|
||||||
|
iban: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaytoUriTalerBank extends PaytoUriGeneric {
|
||||||
|
isKnown: true;
|
||||||
|
targetType: 'x-taler-bank',
|
||||||
|
host: string;
|
||||||
|
account: string;
|
||||||
|
}
|
||||||
|
|
||||||
const paytoPfx = "payto://";
|
const paytoPfx = "payto://";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,9 +82,33 @@ export function parsePaytoUri(s: string): PaytoUri | undefined {
|
|||||||
params[v] = k;
|
params[v] = k;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (targetType === 'x-taler-bank') {
|
||||||
|
const parts = targetPath.split('/')
|
||||||
|
const host = parts[0]
|
||||||
|
const account = parts[1]
|
||||||
|
return {
|
||||||
|
targetPath,
|
||||||
|
targetType,
|
||||||
|
params,
|
||||||
|
isKnown: true,
|
||||||
|
host, account,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
if (targetType === 'iban') {
|
||||||
|
return {
|
||||||
|
isKnown: true,
|
||||||
|
targetPath,
|
||||||
|
targetType,
|
||||||
|
params,
|
||||||
|
iban: targetPath
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
targetPath,
|
targetPath,
|
||||||
targetType,
|
targetType,
|
||||||
params,
|
params,
|
||||||
|
isKnown: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,10 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { i18n } from "@gnu-taler/taler-util";
|
import { i18n } from "@gnu-taler/taler-util";
|
||||||
import { ComponentChildren, JSX, h } from "preact";
|
import { ComponentChildren, h, VNode } from "preact";
|
||||||
import Match from "preact-router/match";
|
import Match from "preact-router/match";
|
||||||
import { useDevContext } from "./context/devContext";
|
|
||||||
import { PopupNavigation } from "./components/styled";
|
import { PopupNavigation } from "./components/styled";
|
||||||
|
import { useDevContext } from "./context/devContext";
|
||||||
|
|
||||||
export enum Pages {
|
export enum Pages {
|
||||||
welcome = "/welcome",
|
welcome = "/welcome",
|
||||||
@ -59,7 +59,7 @@ interface TabProps {
|
|||||||
children?: ComponentChildren;
|
children?: ComponentChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tab(props: TabProps): JSX.Element {
|
function Tab(props: TabProps): VNode {
|
||||||
let cssClass = "";
|
let cssClass = "";
|
||||||
if (props.current?.startsWith(props.target)) {
|
if (props.current?.startsWith(props.target)) {
|
||||||
cssClass = "active";
|
cssClass = "active";
|
||||||
|
@ -14,8 +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 { JSX } from "preact/jsx-runtime";
|
import { h, VNode } from "preact";
|
||||||
import { h } from "preact";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -30,7 +29,7 @@ export function Checkbox({
|
|||||||
onToggle,
|
onToggle,
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
}: Props): JSX.Element {
|
}: Props): VNode {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
|
@ -14,9 +14,8 @@
|
|||||||
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 { JSX } from "preact/jsx-runtime";
|
|
||||||
import { Outlined, StyledCheckboxLabel } from "./styled/index";
|
import { Outlined, StyledCheckboxLabel } from "./styled/index";
|
||||||
import { h } from "preact";
|
import { h, VNode } from "preact";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -47,7 +46,7 @@ export function CheckboxOutlined({
|
|||||||
enabled,
|
enabled,
|
||||||
onToggle,
|
onToggle,
|
||||||
label,
|
label,
|
||||||
}: Props): JSX.Element {
|
}: Props): VNode {
|
||||||
return (
|
return (
|
||||||
<Outlined>
|
<Outlined>
|
||||||
<StyledCheckboxLabel onClick={onToggle}>
|
<StyledCheckboxLabel onClick={onToggle}>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JSX, h } from "preact";
|
import { h, VNode } from "preact";
|
||||||
|
|
||||||
export function DebugCheckbox({
|
export function DebugCheckbox({
|
||||||
enabled,
|
enabled,
|
||||||
@ -22,7 +22,7 @@ export function DebugCheckbox({
|
|||||||
}: {
|
}: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
}): JSX.Element {
|
}): VNode {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
|
@ -15,8 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
||||||
import { h } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { JSX } from "preact/jsx-runtime";
|
|
||||||
import { PageLink } from "../renderHtml";
|
import { PageLink } from "../renderHtml";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -24,51 +23,47 @@ interface Props {
|
|||||||
diagnostics: WalletDiagnostics | undefined;
|
diagnostics: WalletDiagnostics | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Diagnostics({
|
export function Diagnostics({ timedOut, diagnostics }: Props): VNode {
|
||||||
timedOut,
|
|
||||||
diagnostics,
|
|
||||||
}: Props): JSX.Element | null {
|
|
||||||
if (timedOut) {
|
if (timedOut) {
|
||||||
return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>;
|
return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (diagnostics) {
|
if (diagnostics) {
|
||||||
if (diagnostics.errors.length === 0) {
|
if (diagnostics.errors.length === 0) {
|
||||||
return null;
|
return <Fragment />;
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
borderLeft: "0.5em solid red",
|
|
||||||
paddingLeft: "1em",
|
|
||||||
paddingTop: "0.2em",
|
|
||||||
paddingBottom: "0.2em",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p>Problems detected:</p>
|
|
||||||
<ol>
|
|
||||||
{diagnostics.errors.map((errMsg) => (
|
|
||||||
<li key={errMsg}>{errMsg}</li>
|
|
||||||
))}
|
|
||||||
</ol>
|
|
||||||
{diagnostics.firefoxIdbProblem ? (
|
|
||||||
<p>
|
|
||||||
Please check in your <code>about:config</code> settings that you
|
|
||||||
have IndexedDB enabled (check the preference name{" "}
|
|
||||||
<code>dom.indexedDB.enabled</code>).
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{diagnostics.dbOutdated ? (
|
|
||||||
<p>
|
|
||||||
Your wallet database is outdated. Currently automatic migration is
|
|
||||||
not supported. Please go{" "}
|
|
||||||
<PageLink pageName="/reset-required">here</PageLink> to reset the
|
|
||||||
wallet database.
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderLeft: "0.5em solid red",
|
||||||
|
paddingLeft: "1em",
|
||||||
|
paddingTop: "0.2em",
|
||||||
|
paddingBottom: "0.2em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>Problems detected:</p>
|
||||||
|
<ol>
|
||||||
|
{diagnostics.errors.map((errMsg) => (
|
||||||
|
<li key={errMsg}>{errMsg}</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
{diagnostics.firefoxIdbProblem ? (
|
||||||
|
<p>
|
||||||
|
Please check in your <code>about:config</code> settings that you
|
||||||
|
have IndexedDB enabled (check the preference name{" "}
|
||||||
|
<code>dom.indexedDB.enabled</code>).
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
{diagnostics.dbOutdated ? (
|
||||||
|
<p>
|
||||||
|
Your wallet database is outdated. Currently automatic migration is
|
||||||
|
not supported. Please go{" "}
|
||||||
|
<PageLink pageName="/reset-required">here</PageLink> to reset the
|
||||||
|
wallet database.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <p>Running diagnostics ...</p>;
|
return <p>Running diagnostics ...</p>;
|
||||||
|
@ -14,9 +14,8 @@
|
|||||||
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 { h } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useRef, useState } from "preact/hooks";
|
import { useRef, useState } from "preact/hooks";
|
||||||
import { JSX } from "preact/jsx-runtime";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
@ -31,31 +30,35 @@ export function EditableText({
|
|||||||
onChange,
|
onChange,
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
}: Props): JSX.Element {
|
}: Props): VNode {
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
let InputText;
|
let InputText;
|
||||||
if (!editing) {
|
if (!editing) {
|
||||||
InputText = () => (
|
InputText = function InputToEdit(): VNode {
|
||||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
return (
|
||||||
<p>{value}</p>
|
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
<button onClick={() => setEditing(true)}>edit</button>
|
<p>{value}</p>
|
||||||
</div>
|
<button onClick={() => setEditing(true)}>edit</button>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
InputText = () => (
|
InputText = function InputEditing(): VNode {
|
||||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
return (
|
||||||
<input value={value} ref={ref} type="text" id={`text-${name}`} />
|
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
<button
|
<input value={value} ref={ref} type="text" id={`text-${name}`} />
|
||||||
onClick={() => {
|
<button
|
||||||
if (ref.current)
|
onClick={() => {
|
||||||
onChange(ref.current.value).then((r) => setEditing(false));
|
if (ref.current)
|
||||||
}}
|
onChange(ref.current.value).then(() => setEditing(false));
|
||||||
>
|
}}
|
||||||
confirm
|
>
|
||||||
</button>
|
confirm
|
||||||
</div>
|
</button>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -13,12 +13,10 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
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/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
import { Fragment, VNode } from "preact";
|
import { Fragment, VNode, h } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { JSXInternal } from "preact/src/jsx";
|
|
||||||
import { h } from "preact";
|
|
||||||
|
|
||||||
export function ExchangeXmlTos({ doc }: { doc: Document }) {
|
export function ExchangeXmlTos({ doc }: { doc: Document }): VNode {
|
||||||
const termsNode = doc.querySelector("[ids=terms-of-service]");
|
const termsNode = doc.querySelector("[ids=terms-of-service]");
|
||||||
if (!termsNode) {
|
if (!termsNode) {
|
||||||
return (
|
return (
|
||||||
@ -70,7 +68,7 @@ function renderChild(child: Element): VNode {
|
|||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div style={{ color: "red", display: "hidden" }}>
|
<div style={{ color: "red", display: "hidden" }}>
|
||||||
unknown tag {child.nodeName} <a></a>
|
unknown tag {child.nodeName}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -81,10 +79,10 @@ function renderChild(child: Element): VNode {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function AnchorWithOpenState(
|
function AnchorWithOpenState(
|
||||||
props: JSXInternal.HTMLAttributes<HTMLAnchorElement>,
|
props: h.JSX.HTMLAttributes<HTMLAnchorElement>,
|
||||||
) {
|
): VNode {
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
function doClick(e: JSXInternal.TargetedMouseEvent<HTMLAnchorElement>) {
|
function doClick(e: h.JSX.TargetedMouseEvent<HTMLAnchorElement>): void {
|
||||||
setOpen(!open);
|
setOpen(!open);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,8 @@
|
|||||||
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 { JSX } from "preact/jsx-runtime";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { NiceSelect } from "./styled/index";
|
import { NiceSelect } from "./styled/index";
|
||||||
import { h } from "preact";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value?: string;
|
value?: string;
|
||||||
@ -34,13 +33,12 @@ export function SelectList({
|
|||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
list,
|
list,
|
||||||
canBeNull,
|
|
||||||
onChange,
|
onChange,
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
}: Props): JSX.Element {
|
}: Props): VNode {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Fragment>
|
||||||
<label
|
<label
|
||||||
htmlFor={`text-${name}`}
|
htmlFor={`text-${name}`}
|
||||||
style={{ marginLeft: "0.5em", fontWeight: "bold" }}
|
style={{ marginLeft: "0.5em", fontWeight: "bold" }}
|
||||||
@ -84,6 +82,6 @@ export function SelectList({
|
|||||||
{description}
|
{description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
41
packages/taler-wallet-webextension/src/components/Time.tsx
Normal file
41
packages/taler-wallet-webextension/src/components/Time.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2016 GNUnet e.V.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Timestamp } from "@gnu-taler/taler-util";
|
||||||
|
import { formatISO, format } from "date-fns";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
|
||||||
|
export function Time({
|
||||||
|
timestamp,
|
||||||
|
format: formatString,
|
||||||
|
}: {
|
||||||
|
timestamp: Timestamp | undefined;
|
||||||
|
format: string;
|
||||||
|
}): VNode {
|
||||||
|
return (
|
||||||
|
<time
|
||||||
|
dateTime={
|
||||||
|
!timestamp || timestamp.t_ms === "never"
|
||||||
|
? undefined
|
||||||
|
: formatISO(timestamp.t_ms)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!timestamp || timestamp.t_ms === "never"
|
||||||
|
? "never"
|
||||||
|
: format(timestamp.t_ms, formatString)}
|
||||||
|
</time>
|
||||||
|
);
|
||||||
|
}
|
@ -20,8 +20,7 @@ import {
|
|||||||
Transaction,
|
Transaction,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { format, formatDistance } from "date-fns";
|
import { h, VNode } from "preact";
|
||||||
import { h } from "preact";
|
|
||||||
import imageBank from "../../static/img/ri-bank-line.svg";
|
import imageBank from "../../static/img/ri-bank-line.svg";
|
||||||
import imageHandHeart from "../../static/img/ri-hand-heart-line.svg";
|
import imageHandHeart from "../../static/img/ri-hand-heart-line.svg";
|
||||||
import imageRefresh from "../../static/img/ri-refresh-line.svg";
|
import imageRefresh from "../../static/img/ri-refresh-line.svg";
|
||||||
@ -36,11 +35,12 @@ import {
|
|||||||
LargeText,
|
LargeText,
|
||||||
LightText,
|
LightText,
|
||||||
} from "./styled/index";
|
} from "./styled/index";
|
||||||
|
import { Time } from "./Time";
|
||||||
|
|
||||||
export function TransactionItem(props: {
|
export function TransactionItem(props: {
|
||||||
tx: Transaction;
|
tx: Transaction;
|
||||||
multiCurrency: boolean;
|
multiCurrency: boolean;
|
||||||
}): JSX.Element {
|
}): VNode {
|
||||||
const tx = props.tx;
|
const tx = props.tx;
|
||||||
switch (tx.type) {
|
switch (tx.type) {
|
||||||
case TransactionType.Withdrawal:
|
case TransactionType.Withdrawal:
|
||||||
@ -125,10 +125,7 @@ export function TransactionItem(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
|
function TransactionLayout(props: TransactionLayoutProps): VNode {
|
||||||
const date = new Date(props.timestamp.t_ms);
|
|
||||||
const dateStr = format(date, "dd MMM, hh:mm");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HistoryRow href={Pages.transaction.replace(":tid", props.id)}>
|
<HistoryRow href={Pages.transaction.replace(":tid", props.id)}>
|
||||||
<img src={props.iconPath} />
|
<img src={props.iconPath} />
|
||||||
@ -146,7 +143,9 @@ function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
|
|||||||
Waiting for confirmation
|
Waiting for confirmation
|
||||||
</LightText>
|
</LightText>
|
||||||
)}
|
)}
|
||||||
<SmallLightText style={{ marginTop: 5 }}>{dateStr}</SmallLightText>
|
<SmallLightText style={{ marginTop: 5 }}>
|
||||||
|
<Time timestamp={props.timestamp} format="dd MMM, hh:mm" />
|
||||||
|
</SmallLightText>
|
||||||
</Column>
|
</Column>
|
||||||
<TransactionAmount
|
<TransactionAmount
|
||||||
pending={props.pending}
|
pending={props.pending}
|
||||||
@ -177,7 +176,7 @@ interface TransactionAmountProps {
|
|||||||
multiCurrency: boolean;
|
multiCurrency: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TransactionAmount(props: TransactionAmountProps): JSX.Element {
|
function TransactionAmount(props: TransactionAmountProps): VNode {
|
||||||
const [currency, amount] = props.amount.split(":");
|
const [currency, amount] = props.amount.split(":");
|
||||||
let sign: string;
|
let sign: string;
|
||||||
switch (props.debitCreditIndicator) {
|
switch (props.debitCreditIndicator) {
|
||||||
|
@ -85,7 +85,7 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
table td {
|
table td {
|
||||||
padding: 5px 10px;
|
padding: 5px 5px;
|
||||||
}
|
}
|
||||||
table tr {
|
table tr {
|
||||||
border-bottom: 1px solid black;
|
border-bottom: 1px solid black;
|
||||||
@ -328,7 +328,8 @@ const ButtonVariant = styled(Button)`
|
|||||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ButtonPrimary = styled(ButtonVariant)`
|
export const ButtonPrimary = styled(ButtonVariant)<{ small?: boolean }>`
|
||||||
|
font-size: ${({ small }) => (small ? "small" : "inherit")};
|
||||||
background-color: rgb(66, 184, 221);
|
background-color: rgb(66, 184, 221);
|
||||||
`;
|
`;
|
||||||
export const ButtonBoxPrimary = styled(ButtonBox)`
|
export const ButtonBoxPrimary = styled(ButtonBox)`
|
||||||
@ -501,29 +502,40 @@ export const Input = styled.div<{ invalid?: boolean }>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const InputWithLabel = styled.div<{ invalid?: boolean }>`
|
export const InputWithLabel = styled.div<{ invalid?: boolean }>`
|
||||||
|
/* display: flex; */
|
||||||
|
|
||||||
& label {
|
& label {
|
||||||
display: block;
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 0.5em;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
color: ${({ invalid }) => (!invalid ? "inherit" : "red")};
|
color: ${({ invalid }) => (!invalid ? "inherit" : "red")};
|
||||||
}
|
}
|
||||||
& > div {
|
|
||||||
position: relative;
|
& div {
|
||||||
|
line-height: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
top: 0px;
|
}
|
||||||
bottom: 0px;
|
& div > span {
|
||||||
|
background-color: lightgray;
|
||||||
& > div {
|
box-sizing: border-box;
|
||||||
position: absolute;
|
border-bottom-left-radius: 0.25em;
|
||||||
background-color: lightgray;
|
border-top-left-radius: 0.25em;
|
||||||
padding: 5px;
|
height: 2em;
|
||||||
margin: 2px;
|
display: inline-block;
|
||||||
}
|
padding-left: 0.5em;
|
||||||
|
padding-right: 0.5em;
|
||||||
& > input {
|
align-items: center;
|
||||||
flex: 1;
|
display: flex;
|
||||||
padding: 5px;
|
}
|
||||||
border-color: ${({ invalid }) => (!invalid ? "inherit" : "red")};
|
& input {
|
||||||
}
|
border-width: 1px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 2em;
|
||||||
|
/* border-color: lightgray; */
|
||||||
|
border-bottom-right-radius: 0.25em;
|
||||||
|
border-top-right-radius: 0.25em;
|
||||||
|
border-color: ${({ invalid }) => (!invalid ? "lightgray" : "red")};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -535,6 +547,7 @@ export const ErrorBox = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
/* margin: 0.5em; */
|
/* margin: 0.5em; */
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
margin: 1em;
|
||||||
/* width: 100%; */
|
/* width: 100%; */
|
||||||
color: #721c24;
|
color: #721c24;
|
||||||
background: #f8d7da;
|
background: #f8d7da;
|
||||||
@ -592,6 +605,8 @@ export const PopupNavigation = styled.div<{ devMode?: boolean }>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const image = `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`;
|
||||||
|
|
||||||
export const NiceSelect = styled.div`
|
export const NiceSelect = styled.div`
|
||||||
& > select {
|
& > select {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
@ -600,11 +615,18 @@ export const NiceSelect = styled.div`
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-image: none;
|
|
||||||
|
background-image: ${image};
|
||||||
|
background-position: right 0.5rem center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 1.5em 1.5em;
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
|
||||||
flex: 1;
|
border-radius: 0.25rem;
|
||||||
padding: 0.5em 1em;
|
font-size: 1em;
|
||||||
|
padding: 0.5em 3em 0.5em 1em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,27 +635,6 @@ export const NiceSelect = styled.div`
|
|||||||
/* width: 10em; */
|
/* width: 10em; */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: "\u25BC";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
cursor: pointer;
|
|
||||||
pointer-events: none;
|
|
||||||
-webkit-transition: 0.25s all ease;
|
|
||||||
-o-transition: 0.25s all ease;
|
|
||||||
transition: 0.25s all ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover::after {
|
|
||||||
/* color: #f39c12; */
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-expand {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Outlined = styled.div`
|
export const Outlined = styled.div`
|
||||||
|
@ -36,7 +36,7 @@ import {
|
|||||||
PreparePayResult,
|
PreparePayResult,
|
||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { h, Fragment, JSX, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { LogoHeader } from "../components/LogoHeader";
|
import { LogoHeader } from "../components/LogoHeader";
|
||||||
import { Part } from "../components/Part";
|
import { Part } from "../components/Part";
|
||||||
@ -100,7 +100,7 @@ const doPayment = async (
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PayPage({ talerPayUri }: Props): JSX.Element {
|
export function PayPage({ talerPayUri }: Props): VNode {
|
||||||
const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(
|
const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
@ -159,7 +159,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
|||||||
return <span>Loading payment information ...</span>;
|
return <span>Loading payment information ...</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = async () => {
|
const onClick = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const res = await doPayment(payStatus);
|
const res = await doPayment(payStatus);
|
||||||
setPayResult(res);
|
setPayResult(res);
|
||||||
@ -198,7 +198,7 @@ export function PaymentRequestView({
|
|||||||
onClick,
|
onClick,
|
||||||
payErrMsg,
|
payErrMsg,
|
||||||
balance,
|
balance,
|
||||||
}: PaymentRequestViewProps) {
|
}: PaymentRequestViewProps): VNode {
|
||||||
let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw);
|
let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw);
|
||||||
const contractTerms: ContractTerms = payStatus.contractTerms;
|
const contractTerms: ContractTerms = payStatus.contractTerms;
|
||||||
|
|
||||||
@ -225,7 +225,7 @@ export function PaymentRequestView({
|
|||||||
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
|
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Alternative() {
|
function Alternative(): VNode {
|
||||||
const [showQR, setShowQR] = useState<boolean>(false);
|
const [showQR, setShowQR] = useState<boolean>(false);
|
||||||
const privateUri =
|
const privateUri =
|
||||||
payStatus.status !== PreparePayResultType.AlreadyConfirmed
|
payStatus.status !== PreparePayResultType.AlreadyConfirmed
|
||||||
@ -246,7 +246,7 @@ export function PaymentRequestView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ButtonsSection() {
|
function ButtonsSection(): VNode {
|
||||||
if (payResult) {
|
if (payResult) {
|
||||||
if (payResult.type === ConfirmPayResultType.Pending) {
|
if (payResult.type === ConfirmPayResultType.Pending) {
|
||||||
return (
|
return (
|
||||||
@ -257,7 +257,7 @@ export function PaymentRequestView({
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return <Fragment />;
|
||||||
}
|
}
|
||||||
if (payErrMsg) {
|
if (payErrMsg) {
|
||||||
return (
|
return (
|
||||||
@ -392,7 +392,7 @@ export function PaymentRequestView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function amountToString(text: AmountLike) {
|
function amountToString(text: AmountLike): string {
|
||||||
const aj = Amounts.jsonifyAmount(text);
|
const aj = Amounts.jsonifyAmount(text);
|
||||||
const amount = Amounts.stringifyValue(aj, 2);
|
const amount = Amounts.stringifyValue(aj, 2);
|
||||||
return `${amount} ${aj.currency}`;
|
return `${amount} ${aj.currency}`;
|
||||||
|
@ -20,12 +20,11 @@
|
|||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as wxApi from "../wxApi";
|
import { Amounts, ApplyRefundResponse } from "@gnu-taler/taler-util";
|
||||||
import { AmountView } from "../renderHtml";
|
import { h, VNode } from "preact";
|
||||||
import { ApplyRefundResponse, Amounts } from "@gnu-taler/taler-util";
|
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { JSX } from "preact/jsx-runtime";
|
import { AmountView } from "../renderHtml";
|
||||||
import { h } from "preact";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
talerRefundUri?: string;
|
talerRefundUri?: string;
|
||||||
@ -33,7 +32,7 @@ interface Props {
|
|||||||
export interface ViewProps {
|
export interface ViewProps {
|
||||||
applyResult: ApplyRefundResponse;
|
applyResult: ApplyRefundResponse;
|
||||||
}
|
}
|
||||||
export function View({ applyResult }: ViewProps) {
|
export function View({ applyResult }: ViewProps): VNode {
|
||||||
return (
|
return (
|
||||||
<section class="main">
|
<section class="main">
|
||||||
<h1>GNU Taler Wallet</h1>
|
<h1>GNU Taler Wallet</h1>
|
||||||
@ -58,7 +57,7 @@ export function View({ applyResult }: ViewProps) {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export function RefundPage({ talerRefundUri }: Props): JSX.Element {
|
export function RefundPage({ talerRefundUri }: Props): VNode {
|
||||||
const [applyResult, setApplyResult] = useState<
|
const [applyResult, setApplyResult] = useState<
|
||||||
ApplyRefundResponse | undefined
|
ApplyRefundResponse | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
@ -71,9 +70,10 @@ export function RefundPage({ talerRefundUri }: Props): JSX.Element {
|
|||||||
const result = await wxApi.applyRefund(talerRefundUri);
|
const result = await wxApi.applyRefund(talerRefundUri);
|
||||||
setApplyResult(result);
|
setApplyResult(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
if (e instanceof Error) {
|
||||||
setErrMsg(e.message);
|
setErrMsg(e.message);
|
||||||
console.log("err message", e.message);
|
console.log("err message", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
doFetch();
|
doFetch();
|
||||||
|
@ -20,12 +20,11 @@
|
|||||||
* @author Florian Dold <dold@taler.net>
|
* @author Florian Dold <dold@taler.net>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from "preact/hooks";
|
|
||||||
import { PrepareTipResult } from "@gnu-taler/taler-util";
|
import { PrepareTipResult } from "@gnu-taler/taler-util";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { AmountView } from "../renderHtml";
|
import { AmountView } from "../renderHtml";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
import { JSX } from "preact/jsx-runtime";
|
|
||||||
import { h } from "preact";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
talerTipUri?: string;
|
talerTipUri?: string;
|
||||||
@ -35,7 +34,11 @@ export interface ViewProps {
|
|||||||
onAccept: () => void;
|
onAccept: () => void;
|
||||||
onIgnore: () => void;
|
onIgnore: () => void;
|
||||||
}
|
}
|
||||||
export function View({ prepareTipResult, onAccept, onIgnore }: ViewProps) {
|
export function View({
|
||||||
|
prepareTipResult,
|
||||||
|
onAccept,
|
||||||
|
onIgnore,
|
||||||
|
}: ViewProps): VNode {
|
||||||
return (
|
return (
|
||||||
<section class="main">
|
<section class="main">
|
||||||
<h1>GNU Taler Wallet</h1>
|
<h1>GNU Taler Wallet</h1>
|
||||||
@ -64,7 +67,7 @@ export function View({ prepareTipResult, onAccept, onIgnore }: ViewProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TipPage({ talerTipUri }: Props): JSX.Element {
|
export function TipPage({ talerTipUri }: Props): VNode {
|
||||||
const [updateCounter, setUpdateCounter] = useState<number>(0);
|
const [updateCounter, setUpdateCounter] = useState<number>(0);
|
||||||
const [prepareTipResult, setPrepareTipResult] = useState<
|
const [prepareTipResult, setPrepareTipResult] = useState<
|
||||||
PrepareTipResult | undefined
|
PrepareTipResult | undefined
|
||||||
|
@ -19,10 +19,7 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { amountFractionalBase, Amounts } from "@gnu-taler/taler-util";
|
import { amountFractionalBase } from "@gnu-taler/taler-util";
|
||||||
import { ExchangeRecord } from "@gnu-taler/taler-wallet-core";
|
|
||||||
import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw";
|
|
||||||
import { getMaxListeners } from "process";
|
|
||||||
import { createExample } from "../test-utils";
|
import { createExample } from "../test-utils";
|
||||||
import { View as TestedComponent } from "./Withdraw";
|
import { View as TestedComponent } from "./Withdraw";
|
||||||
|
|
||||||
@ -793,12 +790,6 @@ export const NewTerms = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -810,7 +801,9 @@ export const NewTerms = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "xml",
|
type: "xml",
|
||||||
@ -834,12 +827,6 @@ export const TermsReviewingPLAIN = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -851,7 +838,9 @@ export const TermsReviewingPLAIN = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "plain",
|
type: "plain",
|
||||||
@ -876,12 +865,6 @@ export const TermsReviewingHTML = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -893,7 +876,9 @@ export const TermsReviewingHTML = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "html",
|
type: "html",
|
||||||
@ -935,12 +920,6 @@ export const TermsReviewingPDF = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -952,7 +931,9 @@ export const TermsReviewingPDF = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "pdf",
|
type: "pdf",
|
||||||
@ -979,12 +960,6 @@ export const TermsReviewingXML = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -996,7 +971,9 @@ export const TermsReviewingXML = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "xml",
|
type: "xml",
|
||||||
@ -1021,12 +998,6 @@ export const NewTermsAccepted = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -1037,7 +1008,9 @@ export const NewTermsAccepted = createExample(TestedComponent, {
|
|||||||
value: 2,
|
value: 2,
|
||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "xml",
|
type: "xml",
|
||||||
@ -1062,12 +1035,6 @@ export const TermsShowAgainXML = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -1079,7 +1046,9 @@ export const TermsShowAgainXML = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "xml",
|
type: "xml",
|
||||||
@ -1105,12 +1074,6 @@ export const TermsChanged = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -1122,7 +1085,9 @@ export const TermsChanged = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "xml",
|
type: "xml",
|
||||||
@ -1146,12 +1111,6 @@ export const TermsNotFound = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -1163,7 +1122,9 @@ export const TermsNotFound = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
status: "notfound",
|
status: "notfound",
|
||||||
},
|
},
|
||||||
@ -1183,12 +1144,6 @@ export const TermsAlreadyAccepted = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: amountFractionalBase * 0.5,
|
fraction: amountFractionalBase * 0.5,
|
||||||
@ -1200,7 +1155,9 @@ export const TermsAlreadyAccepted = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
status: "accepted",
|
status: "accepted",
|
||||||
},
|
},
|
||||||
@ -1220,12 +1177,6 @@ export const WithoutFee = createExample(TestedComponent, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
exchangeBaseUrl: "exchange.demo.taler.net",
|
exchangeBaseUrl: "exchange.demo.taler.net",
|
||||||
details: {
|
|
||||||
content: "",
|
|
||||||
contentType: "",
|
|
||||||
currentEtag: "",
|
|
||||||
acceptedEtag: undefined,
|
|
||||||
},
|
|
||||||
withdrawalFee: {
|
withdrawalFee: {
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
@ -1237,7 +1188,9 @@ export const WithoutFee = createExample(TestedComponent, {
|
|||||||
fraction: 10000000,
|
fraction: 10000000,
|
||||||
},
|
},
|
||||||
|
|
||||||
onSwitchExchange: async () => {},
|
onSwitchExchange: async () => {
|
||||||
|
null;
|
||||||
|
},
|
||||||
terms: {
|
terms: {
|
||||||
value: {
|
value: {
|
||||||
type: "xml",
|
type: "xml",
|
||||||
|
@ -29,9 +29,8 @@ import {
|
|||||||
i18n,
|
i18n,
|
||||||
WithdrawUriInfoResponse,
|
WithdrawUriInfoResponse,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { VNode, h } from "preact";
|
import { VNode, h, Fragment } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { Fragment } from "preact/jsx-runtime";
|
|
||||||
import { CheckboxOutlined } from "../components/CheckboxOutlined";
|
import { CheckboxOutlined } from "../components/CheckboxOutlined";
|
||||||
import { ExchangeXmlTos } from "../components/ExchangeToS";
|
import { ExchangeXmlTos } from "../components/ExchangeToS";
|
||||||
import { LogoHeader } from "../components/LogoHeader";
|
import { LogoHeader } from "../components/LogoHeader";
|
||||||
@ -60,7 +59,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewProps {
|
export interface ViewProps {
|
||||||
details: GetExchangeTosResult;
|
|
||||||
withdrawalFee: AmountJson;
|
withdrawalFee: AmountJson;
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
amount: AmountJson;
|
amount: AmountJson;
|
||||||
@ -112,14 +110,13 @@ interface TermsDocumentPdf {
|
|||||||
location: URL;
|
location: URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function amountToString(text: AmountJson) {
|
function amountToString(text: AmountJson): string {
|
||||||
const aj = Amounts.jsonifyAmount(text);
|
const aj = Amounts.jsonifyAmount(text);
|
||||||
const amount = Amounts.stringifyValue(aj);
|
const amount = Amounts.stringifyValue(aj);
|
||||||
return `${amount} ${aj.currency}`;
|
return `${amount} ${aj.currency}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function View({
|
export function View({
|
||||||
details,
|
|
||||||
withdrawalFee,
|
withdrawalFee,
|
||||||
exchangeBaseUrl,
|
exchangeBaseUrl,
|
||||||
knownExchanges,
|
knownExchanges,
|
||||||
@ -132,7 +129,7 @@ export function View({
|
|||||||
onAccept,
|
onAccept,
|
||||||
reviewed,
|
reviewed,
|
||||||
confirmed,
|
confirmed,
|
||||||
}: ViewProps) {
|
}: ViewProps): VNode {
|
||||||
const needsReview = terms.status === "changed" || terms.status === "new";
|
const needsReview = terms.status === "changed" || terms.status === "new";
|
||||||
|
|
||||||
const [switchingExchange, setSwitchingExchange] = useState<
|
const [switchingExchange, setSwitchingExchange] = useState<
|
||||||
@ -309,7 +306,7 @@ export function WithdrawPageWithParsedURI({
|
|||||||
}: {
|
}: {
|
||||||
uri: string;
|
uri: string;
|
||||||
uriInfo: WithdrawUriInfoResponse;
|
uriInfo: WithdrawUriInfoResponse;
|
||||||
}) {
|
}): VNode {
|
||||||
const [customExchange, setCustomExchange] = useState<string | undefined>(
|
const [customExchange, setCustomExchange] = useState<string | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
@ -407,7 +404,7 @@ export function WithdrawPageWithParsedURI({
|
|||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
onWithdraw={onWithdraw}
|
onWithdraw={onWithdraw}
|
||||||
details={details.tos}
|
// details={details.tos}
|
||||||
amount={withdrawAmount}
|
amount={withdrawAmount}
|
||||||
exchangeBaseUrl={exchange}
|
exchangeBaseUrl={exchange}
|
||||||
withdrawalFee={details.info.withdrawFee} //FIXME
|
withdrawalFee={details.info.withdrawFee} //FIXME
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JSX } from "preact/jsx-runtime";
|
import { h, VNode } from "preact";
|
||||||
import { h } from "preact";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View and edit auditors.
|
* View and edit auditors.
|
||||||
@ -27,6 +26,6 @@ import { h } from "preact";
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function makePaybackPage(): JSX.Element {
|
export function makePaybackPage(): VNode {
|
||||||
return <div>not implemented</div>;
|
return <div>not implemented</div>;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, JSX, h } from "preact";
|
import { Component, h, VNode } from "preact";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -45,7 +45,7 @@ class ResetNotification extends Component<any, State> {
|
|||||||
const res = await wxApi.checkUpgrade();
|
const res = await wxApi.checkUpgrade();
|
||||||
this.setState({ resetRequired: res.dbResetRequired });
|
this.setState({ resetRequired: res.dbResetRequired });
|
||||||
}
|
}
|
||||||
render(): JSX.Element {
|
render(): VNode {
|
||||||
if (this.state.resetRequired) {
|
if (this.state.resetRequired) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -92,6 +92,6 @@ class ResetNotification extends Component<any, State> {
|
|||||||
/**
|
/**
|
||||||
* @deprecated to be removed
|
* @deprecated to be removed
|
||||||
*/
|
*/
|
||||||
export function createResetRequiredPage(): JSX.Element {
|
export function createResetRequiredPage(): VNode {
|
||||||
return <ResetNotification />;
|
return <ResetNotification />;
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JSX } from "preact/jsx-runtime";
|
import { h, VNode } from "preact";
|
||||||
import { h } from "preact";
|
|
||||||
/**
|
/**
|
||||||
* Return coins to own bank account.
|
* Return coins to own bank account.
|
||||||
*
|
*
|
||||||
@ -25,6 +24,6 @@ import { h } from "preact";
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
export function createReturnCoinsPage(): JSX.Element {
|
export function createReturnCoinsPage(): VNode {
|
||||||
return <span>Not implemented yet.</span>;
|
return <span>Not implemented yet.</span>;
|
||||||
}
|
}
|
||||||
|
@ -24,18 +24,18 @@ import {
|
|||||||
formatDuration,
|
formatDuration,
|
||||||
intervalToDuration,
|
intervalToDuration,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { Fragment, JSX, VNode, h } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import {
|
import {
|
||||||
BoldLight,
|
BoldLight,
|
||||||
ButtonPrimary,
|
ButtonPrimary,
|
||||||
ButtonSuccess,
|
ButtonSuccess,
|
||||||
Centered,
|
Centered,
|
||||||
CenteredText,
|
|
||||||
CenteredBoldText,
|
CenteredBoldText,
|
||||||
|
CenteredText,
|
||||||
PopupBox,
|
PopupBox,
|
||||||
RowBorderGray,
|
RowBorderGray,
|
||||||
SmallText,
|
|
||||||
SmallLightText,
|
SmallLightText,
|
||||||
|
SmallText,
|
||||||
} from "../components/styled";
|
} from "../components/styled";
|
||||||
import { useBackupStatus } from "../hooks/useBackupStatus";
|
import { useBackupStatus } from "../hooks/useBackupStatus";
|
||||||
import { Pages } from "../NavigationBar";
|
import { Pages } from "../NavigationBar";
|
||||||
@ -72,8 +72,9 @@ export function BackupView({
|
|||||||
return (
|
return (
|
||||||
<PopupBox>
|
<PopupBox>
|
||||||
<section>
|
<section>
|
||||||
{providers.map((provider) => (
|
{providers.map((provider, idx) => (
|
||||||
<BackupLayout
|
<BackupLayout
|
||||||
|
key={idx}
|
||||||
status={provider.paymentStatus}
|
status={provider.paymentStatus}
|
||||||
timestamp={provider.lastSuccessfulBackupTimestamp}
|
timestamp={provider.lastSuccessfulBackupTimestamp}
|
||||||
id={provider.syncProviderBaseUrl}
|
id={provider.syncProviderBaseUrl}
|
||||||
@ -117,7 +118,7 @@ interface TransactionLayoutProps {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BackupLayout(props: TransactionLayoutProps): JSX.Element {
|
function BackupLayout(props: TransactionLayoutProps): VNode {
|
||||||
const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
|
const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
|
||||||
const dateStr = date?.toLocaleString([], {
|
const dateStr = date?.toLocaleString([], {
|
||||||
dateStyle: "medium",
|
dateStyle: "medium",
|
||||||
|
@ -18,17 +18,14 @@ import {
|
|||||||
amountFractionalBase,
|
amountFractionalBase,
|
||||||
Amounts,
|
Amounts,
|
||||||
Balance,
|
Balance,
|
||||||
BalancesResponse,
|
|
||||||
i18n,
|
i18n,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { JSX, h, Fragment } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { ErrorMessage } from "../components/ErrorMessage";
|
|
||||||
import {
|
import {
|
||||||
PopupBox,
|
|
||||||
Centered,
|
|
||||||
ButtonPrimary,
|
ButtonPrimary,
|
||||||
ErrorBox,
|
ErrorBox,
|
||||||
Middle,
|
Middle,
|
||||||
|
PopupBox,
|
||||||
} from "../components/styled/index";
|
} from "../components/styled/index";
|
||||||
import { BalancesHook, useBalances } from "../hooks/useBalances";
|
import { BalancesHook, useBalances } from "../hooks/useBalances";
|
||||||
import { PageLink, renderAmount } from "../renderHtml";
|
import { PageLink, renderAmount } from "../renderHtml";
|
||||||
@ -37,7 +34,7 @@ export function BalancePage({
|
|||||||
goToWalletManualWithdraw,
|
goToWalletManualWithdraw,
|
||||||
}: {
|
}: {
|
||||||
goToWalletManualWithdraw: () => void;
|
goToWalletManualWithdraw: () => void;
|
||||||
}) {
|
}): VNode {
|
||||||
const balance = useBalances();
|
const balance = useBalances();
|
||||||
return (
|
return (
|
||||||
<BalanceView
|
<BalanceView
|
||||||
@ -53,11 +50,11 @@ export interface BalanceViewProps {
|
|||||||
goToWalletManualWithdraw: () => void;
|
goToWalletManualWithdraw: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPending(entry: Balance): JSX.Element {
|
function formatPending(entry: Balance): VNode {
|
||||||
let incoming: JSX.Element | undefined;
|
let incoming: VNode | undefined;
|
||||||
let payment: JSX.Element | undefined;
|
let payment: VNode | undefined;
|
||||||
|
|
||||||
const available = Amounts.parseOrThrow(entry.available);
|
// const available = Amounts.parseOrThrow(entry.available);
|
||||||
const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
|
const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
|
||||||
const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
|
const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
|
||||||
|
|
||||||
@ -105,8 +102,8 @@ export function BalanceView({
|
|||||||
balance,
|
balance,
|
||||||
Linker,
|
Linker,
|
||||||
goToWalletManualWithdraw,
|
goToWalletManualWithdraw,
|
||||||
}: BalanceViewProps) {
|
}: BalanceViewProps): VNode {
|
||||||
function Content() {
|
function Content(): VNode {
|
||||||
if (!balance) {
|
if (!balance) {
|
||||||
return <span />;
|
return <span />;
|
||||||
}
|
}
|
||||||
@ -139,7 +136,7 @@ export function BalanceView({
|
|||||||
return (
|
return (
|
||||||
<section data-expanded data-centered>
|
<section data-expanded data-centered>
|
||||||
<table style={{ width: "100%" }}>
|
<table style={{ width: "100%" }}>
|
||||||
{balance.response.balances.map((entry) => {
|
{balance.response.balances.map((entry, idx) => {
|
||||||
const av = Amounts.parseOrThrow(entry.available);
|
const av = Amounts.parseOrThrow(entry.available);
|
||||||
// Create our number formatter.
|
// Create our number formatter.
|
||||||
let formatter;
|
let formatter;
|
||||||
@ -168,7 +165,7 @@ export function BalanceView({
|
|||||||
const fontSize =
|
const fontSize =
|
||||||
v.length < 8 ? "3em" : v.length < 13 ? "2em" : "1em";
|
v.length < 8 ? "3em" : v.length < 13 ? "2em" : "1em";
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr key={idx}>
|
||||||
<td
|
<td
|
||||||
style={{
|
style={{
|
||||||
height: 50,
|
height: 50,
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JSX, h } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { Diagnostics } from "../components/Diagnostics";
|
import { Diagnostics } from "../components/Diagnostics";
|
||||||
import { useDiagnostics } from "../hooks/useDiagnostics.js";
|
import { useDiagnostics } from "../hooks/useDiagnostics.js";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
export function DeveloperPage(props: any): JSX.Element {
|
export function DeveloperPage(): VNode {
|
||||||
const [status, timedOut] = useDiagnostics();
|
const [status, timedOut] = useDiagnostics();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -36,6 +36,7 @@ export function DeveloperPage(props: any): JSX.Element {
|
|||||||
|
|
||||||
export function reload(): void {
|
export function reload(): void {
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
chrome.runtime.reload();
|
chrome.runtime.reload();
|
||||||
window.close();
|
window.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -57,7 +58,9 @@ export async function confirmReset(): Promise<void> {
|
|||||||
|
|
||||||
export function openExtensionPage(page: string) {
|
export function openExtensionPage(page: string) {
|
||||||
return () => {
|
return () => {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
chrome.tabs.create({
|
chrome.tabs.create({
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
url: chrome.extension.getURL(page),
|
url: chrome.extension.getURL(page),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -21,14 +21,14 @@ import {
|
|||||||
Transaction,
|
Transaction,
|
||||||
TransactionsResponse,
|
TransactionsResponse,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { h, JSX } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { PopupBox } from "../components/styled";
|
import { PopupBox } from "../components/styled";
|
||||||
import { TransactionItem } from "../components/TransactionItem";
|
import { TransactionItem } from "../components/TransactionItem";
|
||||||
import { useBalances } from "../hooks/useBalances";
|
import { useBalances } from "../hooks/useBalances";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
export function HistoryPage(props: any): JSX.Element {
|
export function HistoryPage(): VNode {
|
||||||
const [transactions, setTransactions] = useState<
|
const [transactions, setTransactions] = useState<
|
||||||
TransactionsResponse | undefined
|
TransactionsResponse | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
@ -28,13 +28,13 @@ import {
|
|||||||
Amounts,
|
Amounts,
|
||||||
amountFractionalBase,
|
amountFractionalBase,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { Component, ComponentChildren, JSX, h } from "preact";
|
import { Component, ComponentChildren, h, VNode } from "preact";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render amount as HTML, which non-breaking space between
|
* Render amount as HTML, which non-breaking space between
|
||||||
* decimal value and currency.
|
* decimal value and currency.
|
||||||
*/
|
*/
|
||||||
export function renderAmount(amount: AmountJson | string): JSX.Element {
|
export function renderAmount(amount: AmountJson | string): VNode {
|
||||||
let a;
|
let a;
|
||||||
if (typeof amount === "string") {
|
if (typeof amount === "string") {
|
||||||
a = Amounts.parse(amount);
|
a = Amounts.parse(amount);
|
||||||
@ -56,13 +56,13 @@ export const AmountView = ({
|
|||||||
amount,
|
amount,
|
||||||
}: {
|
}: {
|
||||||
amount: AmountJson | string;
|
amount: AmountJson | string;
|
||||||
}): JSX.Element => renderAmount(amount);
|
}): VNode => renderAmount(amount);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abbreviate a string to a given length, and show the full
|
* Abbreviate a string to a given length, and show the full
|
||||||
* string on hover as a tooltip.
|
* string on hover as a tooltip.
|
||||||
*/
|
*/
|
||||||
export function abbrev(s: string, n = 5): JSX.Element {
|
export function abbrev(s: string, n = 5): VNode {
|
||||||
let sAbbrev = s;
|
let sAbbrev = s;
|
||||||
if (s.length > n) {
|
if (s.length > n) {
|
||||||
sAbbrev = s.slice(0, n) + "..";
|
sAbbrev = s.slice(0, n) + "..";
|
||||||
@ -92,7 +92,7 @@ export class Collapsible extends Component<CollapsibleProps, CollapsibleState> {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = { collapsed: props.initiallyCollapsed };
|
this.state = { collapsed: props.initiallyCollapsed };
|
||||||
}
|
}
|
||||||
render(): JSX.Element {
|
render(): VNode {
|
||||||
const doOpen = (e: any): void => {
|
const doOpen = (e: any): void => {
|
||||||
this.setState({ collapsed: false });
|
this.setState({ collapsed: false });
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -132,19 +132,19 @@ interface ExpanderTextProps {
|
|||||||
/**
|
/**
|
||||||
* Show a heading with a toggle to show/hide the expandable content.
|
* Show a heading with a toggle to show/hide the expandable content.
|
||||||
*/
|
*/
|
||||||
export function ExpanderText({ text }: ExpanderTextProps): JSX.Element {
|
export function ExpanderText({ text }: ExpanderTextProps): VNode {
|
||||||
return <span>{text}</span>;
|
return <span>{text}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadingButtonProps
|
export interface LoadingButtonProps
|
||||||
extends JSX.HTMLAttributes<HTMLButtonElement> {
|
extends h.JSX.HTMLAttributes<HTMLButtonElement> {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProgressButton({
|
export function ProgressButton({
|
||||||
isLoading,
|
isLoading,
|
||||||
...rest
|
...rest
|
||||||
}: LoadingButtonProps): JSX.Element {
|
}: LoadingButtonProps): VNode {
|
||||||
return (
|
return (
|
||||||
<button class="pure-button pure-button-primary" type="button" {...rest}>
|
<button class="pure-button pure-button-primary" type="button" {...rest}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -160,7 +160,8 @@ export function ProgressButton({
|
|||||||
export function PageLink(props: {
|
export function PageLink(props: {
|
||||||
pageName: string;
|
pageName: string;
|
||||||
children?: ComponentChildren;
|
children?: ComponentChildren;
|
||||||
}): JSX.Element {
|
}): VNode {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
const url = chrome.extension.getURL(`/static/wallet.html#/${props.pageName}`);
|
const url = chrome.extension.getURL(`/static/wallet.html#/${props.pageName}`);
|
||||||
return (
|
return (
|
||||||
<a class="actionLink" href={url} target="_blank" rel="noopener noreferrer">
|
<a class="actionLink" href={url} target="_blank" rel="noopener noreferrer">
|
||||||
|
@ -24,18 +24,17 @@ import {
|
|||||||
formatDuration,
|
formatDuration,
|
||||||
intervalToDuration,
|
intervalToDuration,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { Fragment, JSX, VNode, h } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import {
|
import {
|
||||||
BoldLight,
|
BoldLight,
|
||||||
ButtonPrimary,
|
ButtonPrimary,
|
||||||
ButtonSuccess,
|
ButtonSuccess,
|
||||||
Centered,
|
Centered,
|
||||||
CenteredText,
|
|
||||||
CenteredBoldText,
|
CenteredBoldText,
|
||||||
PopupBox,
|
CenteredText,
|
||||||
RowBorderGray,
|
RowBorderGray,
|
||||||
SmallText,
|
|
||||||
SmallLightText,
|
SmallLightText,
|
||||||
|
SmallText,
|
||||||
WalletBox,
|
WalletBox,
|
||||||
} from "../components/styled";
|
} from "../components/styled";
|
||||||
import { useBackupStatus } from "../hooks/useBackupStatus";
|
import { useBackupStatus } from "../hooks/useBackupStatus";
|
||||||
@ -73,8 +72,9 @@ export function BackupView({
|
|||||||
return (
|
return (
|
||||||
<WalletBox>
|
<WalletBox>
|
||||||
<section>
|
<section>
|
||||||
{providers.map((provider) => (
|
{providers.map((provider, idx) => (
|
||||||
<BackupLayout
|
<BackupLayout
|
||||||
|
key={idx}
|
||||||
status={provider.paymentStatus}
|
status={provider.paymentStatus}
|
||||||
timestamp={provider.lastSuccessfulBackupTimestamp}
|
timestamp={provider.lastSuccessfulBackupTimestamp}
|
||||||
id={provider.syncProviderBaseUrl}
|
id={provider.syncProviderBaseUrl}
|
||||||
@ -118,7 +118,7 @@ interface TransactionLayoutProps {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BackupLayout(props: TransactionLayoutProps): JSX.Element {
|
function BackupLayout(props: TransactionLayoutProps): VNode {
|
||||||
const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
|
const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
|
||||||
const dateStr = date?.toLocaleString([], {
|
const dateStr = date?.toLocaleString([], {
|
||||||
dateStyle: "medium",
|
dateStyle: "medium",
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
BalancesResponse,
|
BalancesResponse,
|
||||||
i18n,
|
i18n,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { JSX, h } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { ButtonPrimary, Centered, WalletBox } from "../components/styled/index";
|
import { ButtonPrimary, Centered, WalletBox } from "../components/styled/index";
|
||||||
import { BalancesHook, useBalances } from "../hooks/useBalances";
|
import { BalancesHook, useBalances } from "../hooks/useBalances";
|
||||||
import { PageLink, renderAmount } from "../renderHtml";
|
import { PageLink, renderAmount } from "../renderHtml";
|
||||||
@ -30,7 +30,7 @@ export function BalancePage({
|
|||||||
goToWalletManualWithdraw,
|
goToWalletManualWithdraw,
|
||||||
}: {
|
}: {
|
||||||
goToWalletManualWithdraw: () => void;
|
goToWalletManualWithdraw: () => void;
|
||||||
}) {
|
}): VNode {
|
||||||
const balance = useBalances();
|
const balance = useBalances();
|
||||||
return (
|
return (
|
||||||
<BalanceView
|
<BalanceView
|
||||||
@ -51,7 +51,7 @@ export function BalanceView({
|
|||||||
balance,
|
balance,
|
||||||
Linker,
|
Linker,
|
||||||
goToWalletManualWithdraw,
|
goToWalletManualWithdraw,
|
||||||
}: BalanceViewProps) {
|
}: BalanceViewProps): VNode {
|
||||||
if (!balance) {
|
if (!balance) {
|
||||||
return <span />;
|
return <span />;
|
||||||
}
|
}
|
||||||
@ -85,13 +85,13 @@ export function BalanceView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPending(entry: Balance): JSX.Element {
|
function formatPending(entry: Balance): VNode {
|
||||||
let incoming: JSX.Element | undefined;
|
let incoming: VNode | undefined;
|
||||||
let payment: JSX.Element | undefined;
|
let payment: VNode | undefined;
|
||||||
|
|
||||||
const available = Amounts.parseOrThrow(entry.available);
|
// const available = Amounts.parseOrThrow(entry.available);
|
||||||
const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
|
const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
|
||||||
const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
|
// const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
|
||||||
|
|
||||||
if (!Amounts.isZero(pendingIncoming)) {
|
if (!Amounts.isZero(pendingIncoming)) {
|
||||||
incoming = (
|
incoming = (
|
||||||
@ -128,7 +128,7 @@ function ShowBalances({
|
|||||||
}: {
|
}: {
|
||||||
wallet: BalancesResponse;
|
wallet: BalancesResponse;
|
||||||
onWithdraw: () => void;
|
onWithdraw: () => void;
|
||||||
}) {
|
}): VNode {
|
||||||
return (
|
return (
|
||||||
<WalletBox>
|
<WalletBox>
|
||||||
<section>
|
<section>
|
||||||
|
@ -28,26 +28,27 @@ export default {
|
|||||||
argTypes: {},
|
argTypes: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InitialState = createExample(TestedComponent, {});
|
// ,
|
||||||
|
const exchangeList = {
|
||||||
|
"http://exchange.taler:8081": "COL",
|
||||||
|
"http://exchange.tal": "EUR",
|
||||||
|
};
|
||||||
|
|
||||||
export const WithExchangeFilled = createExample(TestedComponent, {
|
export const InitialState = createExample(TestedComponent, {
|
||||||
currency: "COL",
|
exchangeList,
|
||||||
initialExchange: "http://exchange.taler:8081",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithExchangeAndAmountFilled = createExample(TestedComponent, {
|
export const WithAmountInitialized = createExample(TestedComponent, {
|
||||||
currency: "COL",
|
|
||||||
initialExchange: "http://exchange.taler:8081",
|
|
||||||
initialAmount: "10",
|
initialAmount: "10",
|
||||||
|
exchangeList,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithExchangeError = createExample(TestedComponent, {
|
export const WithExchangeError = createExample(TestedComponent, {
|
||||||
initialExchange: "http://exchange.tal",
|
|
||||||
error: "The exchange url seems invalid",
|
error: "The exchange url seems invalid",
|
||||||
|
exchangeList,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithAmountError = createExample(TestedComponent, {
|
export const WithAmountError = createExample(TestedComponent, {
|
||||||
currency: "COL",
|
|
||||||
initialExchange: "http://exchange.taler:8081",
|
|
||||||
initialAmount: "e",
|
initialAmount: "e",
|
||||||
|
exchangeList,
|
||||||
});
|
});
|
||||||
|
@ -20,9 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
|
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
|
||||||
import { VNode, h } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { ErrorMessage } from "../components/ErrorMessage";
|
import { ErrorMessage } from "../components/ErrorMessage";
|
||||||
|
import { SelectList } from "../components/SelectList";
|
||||||
import {
|
import {
|
||||||
ButtonPrimary,
|
ButtonPrimary,
|
||||||
Input,
|
Input,
|
||||||
@ -33,32 +34,56 @@ import {
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
error: string | undefined;
|
error: string | undefined;
|
||||||
currency: string | undefined;
|
|
||||||
initialExchange?: string;
|
|
||||||
initialAmount?: string;
|
initialAmount?: string;
|
||||||
onExchangeChange: (exchange: string) => void;
|
exchangeList: Record<string, string>;
|
||||||
onCreate: (exchangeBaseUrl: string, amount: AmountJson) => Promise<void>;
|
onCreate: (exchangeBaseUrl: string, amount: AmountJson) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CreateManualWithdraw({
|
export function CreateManualWithdraw({
|
||||||
onExchangeChange,
|
|
||||||
initialExchange,
|
|
||||||
initialAmount,
|
initialAmount,
|
||||||
|
exchangeList,
|
||||||
error,
|
error,
|
||||||
currency,
|
|
||||||
onCreate,
|
onCreate,
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
|
const exchangeSelectList = Object.keys(exchangeList);
|
||||||
|
const currencySelectList = Object.values(exchangeList);
|
||||||
|
const exchangeMap = exchangeSelectList.reduce(
|
||||||
|
(p, c) => ({ ...p, [c]: `${c} (${exchangeList[c]})` }),
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
const currencyMap = currencySelectList.reduce(
|
||||||
|
(p, c) => ({ ...p, [c]: c }),
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialExchange =
|
||||||
|
exchangeSelectList.length > 0 ? exchangeSelectList[0] : "";
|
||||||
|
|
||||||
const [exchange, setExchange] = useState(initialExchange || "");
|
const [exchange, setExchange] = useState(initialExchange || "");
|
||||||
|
const [currency, setCurrency] = useState(exchangeList[initialExchange] ?? "");
|
||||||
|
|
||||||
const [amount, setAmount] = useState(initialAmount || "");
|
const [amount, setAmount] = useState(initialAmount || "");
|
||||||
const parsedAmount = Amounts.parse(`${currency}:${amount}`);
|
const parsedAmount = Amounts.parse(`${currency}:${amount}`);
|
||||||
|
|
||||||
let timeout = useRef<number | undefined>(undefined);
|
function changeExchange(exchange: string): void {
|
||||||
useEffect(() => {
|
setExchange(exchange);
|
||||||
if (timeout) window.clearTimeout(timeout.current);
|
setCurrency(exchangeList[exchange]);
|
||||||
timeout.current = window.setTimeout(async () => {
|
}
|
||||||
onExchangeChange(exchange);
|
|
||||||
}, 1000);
|
function changeCurrency(currency: string): void {
|
||||||
}, [exchange]);
|
setCurrency(currency);
|
||||||
|
const found = Object.entries(exchangeList).find((e) => e[1] === currency);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
setExchange(found[0]);
|
||||||
|
} else {
|
||||||
|
setExchange("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initialExchange) {
|
||||||
|
return <div>There is no known exchange where to withdraw, add one</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WalletBox>
|
<WalletBox>
|
||||||
@ -73,26 +98,38 @@ export function CreateManualWithdraw({
|
|||||||
withdraw the coins
|
withdraw the coins
|
||||||
</LightText>
|
</LightText>
|
||||||
<p>
|
<p>
|
||||||
<Input invalid={!!exchange && !currency}>
|
<Input>
|
||||||
<label>Exchange</label>
|
<SelectList
|
||||||
<input
|
label="Currency"
|
||||||
type="text"
|
list={currencyMap}
|
||||||
placeholder="https://"
|
name="currency"
|
||||||
value={exchange}
|
value={currency}
|
||||||
onChange={(e) => setExchange(e.currentTarget.value)}
|
onChange={changeCurrency}
|
||||||
/>
|
/>
|
||||||
<small>http://exchange.taler:8081</small>
|
|
||||||
</Input>
|
</Input>
|
||||||
|
<Input>
|
||||||
|
<SelectList
|
||||||
|
label="Exchange"
|
||||||
|
list={exchangeMap}
|
||||||
|
name="currency"
|
||||||
|
value={exchange}
|
||||||
|
onChange={changeExchange}
|
||||||
|
/>
|
||||||
|
</Input>
|
||||||
|
{/* <p style={{ display: "flex", justifyContent: "right" }}>
|
||||||
|
<a href="" style={{ marginLeft: "auto" }}>
|
||||||
|
Add new exchange
|
||||||
|
</a>
|
||||||
|
</p> */}
|
||||||
{currency && (
|
{currency && (
|
||||||
<InputWithLabel invalid={!!amount && !parsedAmount}>
|
<InputWithLabel invalid={!!amount && !parsedAmount}>
|
||||||
<label>Amount</label>
|
<label>Amount</label>
|
||||||
<div>
|
<div>
|
||||||
<div>{currency}</div>
|
<span>{currency}</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
style={{ paddingLeft: `${currency.length}em` }}
|
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={(e) => setAmount(e.currentTarget.value)}
|
onInput={(e) => setAmount(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</InputWithLabel>
|
</InputWithLabel>
|
||||||
@ -105,7 +142,7 @@ export function CreateManualWithdraw({
|
|||||||
disabled={!parsedAmount || !exchange}
|
disabled={!parsedAmount || !exchange}
|
||||||
onClick={() => onCreate(exchange, parsedAmount!)}
|
onClick={() => onCreate(exchange, parsedAmount!)}
|
||||||
>
|
>
|
||||||
Create
|
Start withdrawal
|
||||||
</ButtonPrimary>
|
</ButtonPrimary>
|
||||||
</footer>
|
</footer>
|
||||||
</WalletBox>
|
</WalletBox>
|
||||||
|
@ -20,15 +20,15 @@ import {
|
|||||||
Transaction,
|
Transaction,
|
||||||
TransactionsResponse,
|
TransactionsResponse,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { format } from "date-fns";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { Fragment, h, JSX } from "preact";
|
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { DateSeparator, WalletBox } from "../components/styled";
|
import { DateSeparator, WalletBox } from "../components/styled";
|
||||||
|
import { Time } from "../components/Time";
|
||||||
import { TransactionItem } from "../components/TransactionItem";
|
import { TransactionItem } from "../components/TransactionItem";
|
||||||
import { useBalances } from "../hooks/useBalances";
|
import { useBalances } from "../hooks/useBalances";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
export function HistoryPage(props: any): JSX.Element {
|
export function HistoryPage(): VNode {
|
||||||
const [transactions, setTransactions] = useState<
|
const [transactions, setTransactions] = useState<
|
||||||
TransactionsResponse | undefined
|
TransactionsResponse | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
@ -57,24 +57,30 @@ export function HistoryPage(props: any): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function amountToString(c: AmountString) {
|
function amountToString(c: AmountString): string {
|
||||||
const idx = c.indexOf(":");
|
const idx = c.indexOf(":");
|
||||||
return `${c.substring(idx + 1)} ${c.substring(0, idx)}`;
|
return `${c.substring(idx + 1)} ${c.substring(0, idx)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const term = 1000 * 60 * 60 * 24;
|
||||||
|
function normalizeToDay(x: number): number {
|
||||||
|
return Math.round(x / term) * term;
|
||||||
|
}
|
||||||
|
|
||||||
export function HistoryView({
|
export function HistoryView({
|
||||||
list,
|
list,
|
||||||
balances,
|
balances,
|
||||||
}: {
|
}: {
|
||||||
list: Transaction[];
|
list: Transaction[];
|
||||||
balances: Balance[];
|
balances: Balance[];
|
||||||
}) {
|
}): VNode {
|
||||||
const byDate = list.reduce(function (rv, x) {
|
const byDate = list.reduce((rv, x) => {
|
||||||
const theDate =
|
const theDate =
|
||||||
x.timestamp.t_ms === "never"
|
x.timestamp.t_ms === "never" ? 0 : normalizeToDay(x.timestamp.t_ms);
|
||||||
? "never"
|
if (theDate) {
|
||||||
: format(x.timestamp.t_ms, "dd MMMM yyyy");
|
(rv[theDate] = rv[theDate] || []).push(x);
|
||||||
(rv[theDate] = rv[theDate] || []).push(x);
|
}
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}, {} as { [x: string]: Transaction[] });
|
}, {} as { [x: string]: Transaction[] });
|
||||||
|
|
||||||
@ -93,8 +99,8 @@ export function HistoryView({
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
Balance:{" "}
|
Balance:{" "}
|
||||||
<ul style={{ margin: 0 }}>
|
<ul style={{ margin: 0 }}>
|
||||||
{balances.map((b) => (
|
{balances.map((b, i) => (
|
||||||
<li>{b.available}</li>
|
<li key={i}>{b.available}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -105,7 +111,12 @@ export function HistoryView({
|
|||||||
{Object.keys(byDate).map((d, i) => {
|
{Object.keys(byDate).map((d, i) => {
|
||||||
return (
|
return (
|
||||||
<Fragment key={i}>
|
<Fragment key={i}>
|
||||||
<DateSeparator>{d}</DateSeparator>
|
<DateSeparator>
|
||||||
|
<Time
|
||||||
|
timestamp={{ t_ms: Number.parseInt(d, 10) }}
|
||||||
|
format="dd MMMM yyyy"
|
||||||
|
/>
|
||||||
|
</DateSeparator>
|
||||||
{byDate[d].map((tx, i) => (
|
{byDate[d].map((tx, i) => (
|
||||||
<TransactionItem
|
<TransactionItem
|
||||||
key={i}
|
key={i}
|
||||||
|
@ -26,44 +26,31 @@ import {
|
|||||||
import { ReserveCreated } from "./ReserveCreated.js";
|
import { ReserveCreated } from "./ReserveCreated.js";
|
||||||
import { route } from "preact-router";
|
import { route } from "preact-router";
|
||||||
import { Pages } from "../NavigationBar.js";
|
import { Pages } from "../NavigationBar.js";
|
||||||
|
import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
|
||||||
|
|
||||||
interface Props {}
|
export function ManualWithdrawPage(): VNode {
|
||||||
|
|
||||||
export function ManualWithdrawPage({}: Props): VNode {
|
|
||||||
const [success, setSuccess] = useState<
|
const [success, setSuccess] = useState<
|
||||||
AcceptManualWithdrawalResult | undefined
|
| {
|
||||||
|
response: AcceptManualWithdrawalResult;
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
amount: AmountJson;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const [currency, setCurrency] = useState<string | undefined>(undefined);
|
|
||||||
const [error, setError] = useState<string | undefined>(undefined);
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
async function onExchangeChange(exchange: string | undefined): Promise<void> {
|
const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
|
||||||
if (!exchange) return;
|
|
||||||
try {
|
|
||||||
const r = await fetch(`${exchange}/keys`);
|
|
||||||
const j = await r.json();
|
|
||||||
if (j.currency) {
|
|
||||||
await wxApi.addExchange({
|
|
||||||
exchangeBaseUrl: `${exchange}/`,
|
|
||||||
forceUpdate: true,
|
|
||||||
});
|
|
||||||
setCurrency(j.currency);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setError("The exchange url seems invalid");
|
|
||||||
setCurrency(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doCreate(
|
async function doCreate(
|
||||||
exchangeBaseUrl: string,
|
exchangeBaseUrl: string,
|
||||||
amount: AmountJson,
|
amount: AmountJson,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const resp = await wxApi.acceptManualWithdrawal(
|
const response = await wxApi.acceptManualWithdrawal(
|
||||||
exchangeBaseUrl,
|
exchangeBaseUrl,
|
||||||
Amounts.stringify(amount),
|
Amounts.stringify(amount),
|
||||||
);
|
);
|
||||||
setSuccess(resp);
|
setSuccess({ exchangeBaseUrl, response, amount });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
@ -77,8 +64,10 @@ export function ManualWithdrawPage({}: Props): VNode {
|
|||||||
if (success) {
|
if (success) {
|
||||||
return (
|
return (
|
||||||
<ReserveCreated
|
<ReserveCreated
|
||||||
reservePub={success.reservePub}
|
reservePub={success.response.reservePub}
|
||||||
paytos={success.exchangePaytoUris}
|
payto={success.response.exchangePaytoUris[0]}
|
||||||
|
exchangeBaseUrl={success.exchangeBaseUrl}
|
||||||
|
amount={success.amount}
|
||||||
onBack={() => {
|
onBack={() => {
|
||||||
route(Pages.balance);
|
route(Pages.balance);
|
||||||
}}
|
}}
|
||||||
@ -86,12 +75,22 @@ export function ManualWithdrawPage({}: Props): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!knownExchangesHook || knownExchangesHook.hasError) {
|
||||||
|
return <div>No Known exchanges</div>;
|
||||||
|
}
|
||||||
|
const exchangeList = knownExchangesHook.response.exchanges.reduce(
|
||||||
|
(p, c) => ({
|
||||||
|
...p,
|
||||||
|
[c.exchangeBaseUrl]: c.currency,
|
||||||
|
}),
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CreateManualWithdraw
|
<CreateManualWithdraw
|
||||||
error={error}
|
error={error}
|
||||||
currency={currency}
|
exchangeList={exchangeList}
|
||||||
onCreate={doCreate}
|
onCreate={doCreate}
|
||||||
onExchangeChange={onExchangeChange}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,23 +14,23 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n, Timestamp } from "@gnu-taler/taler-util";
|
import { i18n } from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
ProviderInfo,
|
ProviderInfo,
|
||||||
ProviderPaymentStatus,
|
ProviderPaymentStatus,
|
||||||
ProviderPaymentType,
|
ProviderPaymentType,
|
||||||
} from "@gnu-taler/taler-wallet-core";
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
import { format, formatDuration, intervalToDuration } from "date-fns";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { Fragment, VNode, h } from "preact";
|
|
||||||
import { ErrorMessage } from "../components/ErrorMessage";
|
import { ErrorMessage } from "../components/ErrorMessage";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonDestructive,
|
ButtonDestructive,
|
||||||
ButtonPrimary,
|
ButtonPrimary,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
WalletBox,
|
|
||||||
SmallLightText,
|
SmallLightText,
|
||||||
|
WalletBox,
|
||||||
} from "../components/styled";
|
} from "../components/styled";
|
||||||
|
import { Time } from "../components/Time";
|
||||||
import { useProviderStatus } from "../hooks/useProviderStatus";
|
import { useProviderStatus } from "../hooks/useProviderStatus";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -97,10 +97,7 @@ export function ProviderView({
|
|||||||
</header>
|
</header>
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
<b>Last backup:</b>{" "}
|
<b>Last backup:</b> <Time timestamp={lb} format="dd MMMM yyyy" />
|
||||||
{lb == null || lb.t_ms == "never"
|
|
||||||
? "never"
|
|
||||||
: format(lb.t_ms, "dd MMM yyyy")}{" "}
|
|
||||||
</p>
|
</p>
|
||||||
<ButtonPrimary onClick={onSync}>
|
<ButtonPrimary onClick={onSync}>
|
||||||
<i18n.Translate>Back up</i18n.Translate>
|
<i18n.Translate>Back up</i18n.Translate>
|
||||||
@ -128,7 +125,7 @@ export function ProviderView({
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td> </td>
|
||||||
<td>
|
<td>
|
||||||
<i18n.Translate>old</i18n.Translate>
|
<i18n.Translate>old</i18n.Translate>
|
||||||
</td>
|
</td>
|
||||||
@ -174,32 +171,32 @@ export function ProviderView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function daysSince(d?: Timestamp) {
|
// function daysSince(d?: Timestamp): string {
|
||||||
if (!d || d.t_ms === "never") return "never synced";
|
// if (!d || d.t_ms === "never") return "never synced";
|
||||||
const duration = intervalToDuration({
|
// const duration = intervalToDuration({
|
||||||
start: d.t_ms,
|
// start: d.t_ms,
|
||||||
end: new Date(),
|
// end: new Date(),
|
||||||
});
|
// });
|
||||||
const str = formatDuration(duration, {
|
// const str = formatDuration(duration, {
|
||||||
delimiter: ", ",
|
// delimiter: ", ",
|
||||||
format: [
|
// format: [
|
||||||
duration?.years
|
// duration?.years
|
||||||
? i18n.str`years`
|
// ? i18n.str`years`
|
||||||
: duration?.months
|
// : duration?.months
|
||||||
? i18n.str`months`
|
// ? i18n.str`months`
|
||||||
: duration?.days
|
// : duration?.days
|
||||||
? i18n.str`days`
|
// ? i18n.str`days`
|
||||||
: duration?.hours
|
// : duration?.hours
|
||||||
? i18n.str`hours`
|
// ? i18n.str`hours`
|
||||||
: duration?.minutes
|
// : duration?.minutes
|
||||||
? i18n.str`minutes`
|
// ? i18n.str`minutes`
|
||||||
: i18n.str`seconds`,
|
// : i18n.str`seconds`,
|
||||||
],
|
// ],
|
||||||
});
|
// });
|
||||||
return `synced ${str} ago`;
|
// return `synced ${str} ago`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
function Error({ info }: { info: ProviderInfo }) {
|
function Error({ info }: { info: ProviderInfo }): VNode {
|
||||||
if (info.lastError) {
|
if (info.lastError) {
|
||||||
return <ErrorMessage title={info.lastError.hint} />;
|
return <ErrorMessage title={info.lastError.hint} />;
|
||||||
}
|
}
|
||||||
@ -234,45 +231,45 @@ function Error({ info }: { info: ProviderInfo }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return <Fragment />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorByStatus(status: ProviderPaymentType) {
|
// function colorByStatus(status: ProviderPaymentType): string {
|
||||||
switch (status) {
|
// switch (status) {
|
||||||
case ProviderPaymentType.InsufficientBalance:
|
// case ProviderPaymentType.InsufficientBalance:
|
||||||
return "rgb(223, 117, 20)";
|
// return "rgb(223, 117, 20)";
|
||||||
case ProviderPaymentType.Unpaid:
|
// case ProviderPaymentType.Unpaid:
|
||||||
return "rgb(202, 60, 60)";
|
// return "rgb(202, 60, 60)";
|
||||||
case ProviderPaymentType.Paid:
|
// case ProviderPaymentType.Paid:
|
||||||
return "rgb(28, 184, 65)";
|
// return "rgb(28, 184, 65)";
|
||||||
case ProviderPaymentType.Pending:
|
// case ProviderPaymentType.Pending:
|
||||||
return "gray";
|
// return "gray";
|
||||||
case ProviderPaymentType.InsufficientBalance:
|
// // case ProviderPaymentType.InsufficientBalance:
|
||||||
return "rgb(202, 60, 60)";
|
// // return "rgb(202, 60, 60)";
|
||||||
case ProviderPaymentType.TermsChanged:
|
// case ProviderPaymentType.TermsChanged:
|
||||||
return "rgb(202, 60, 60)";
|
// return "rgb(202, 60, 60)";
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
function descriptionByStatus(status: ProviderPaymentStatus) {
|
function descriptionByStatus(status: ProviderPaymentStatus): VNode {
|
||||||
switch (status.type) {
|
switch (status.type) {
|
||||||
// return i18n.str`no enough balance to make the payment`
|
// return i18n.str`no enough balance to make the payment`
|
||||||
// return i18n.str`not paid yet`
|
// return i18n.str`not paid yet`
|
||||||
case ProviderPaymentType.Paid:
|
case ProviderPaymentType.Paid:
|
||||||
case ProviderPaymentType.TermsChanged:
|
case ProviderPaymentType.TermsChanged:
|
||||||
if (status.paidUntil.t_ms === "never") {
|
if (status.paidUntil.t_ms === "never") {
|
||||||
return i18n.str`service paid`;
|
return <span>{i18n.str`service paid`}</span>;
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<b>Backup valid until:</b>{" "}
|
|
||||||
{format(status.paidUntil.t_ms, "dd MMM yyyy")}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<b>Backup valid until:</b>{" "}
|
||||||
|
<Time timestamp={status.paidUntil} format="dd MMM yyyy" />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
case ProviderPaymentType.Unpaid:
|
case ProviderPaymentType.Unpaid:
|
||||||
case ProviderPaymentType.InsufficientBalance:
|
case ProviderPaymentType.InsufficientBalance:
|
||||||
case ProviderPaymentType.Pending:
|
case ProviderPaymentType.Pending:
|
||||||
return "";
|
return <span />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,26 @@ export default {
|
|||||||
argTypes: {},
|
argTypes: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InitialState = createExample(TestedComponent, {
|
export const TalerBank = createExample(TestedComponent, {
|
||||||
reservePub: "ASLKDJQWLKEJASLKDJSADLKASJDLKSADJ",
|
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
|
||||||
paytos: [
|
payto:
|
||||||
"payto://x-taler-bank/bank.taler:5882/exchangeminator?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
|
"payto://x-taler-bank/bank.taler:5882/exchangeminator?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
|
||||||
"payto://x-taler-bank/international-bank.com/myaccount?amount=COL%3A1&message=Taler+Withdrawal+TYQTE7VA4M9GZQ4TR06YBNGA05AJGMFNSK4Q62NXR2FKNDB1J4EX",
|
amount: {
|
||||||
],
|
currency: "USD",
|
||||||
|
value: 10,
|
||||||
|
fraction: 0,
|
||||||
|
},
|
||||||
|
exchangeBaseUrl: "https://exchange.demo.taler.net",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IBAN = createExample(TestedComponent, {
|
||||||
|
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
|
||||||
|
payto:
|
||||||
|
"payto://iban/ASDQWEASDZXCASDQWE?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
|
||||||
|
amount: {
|
||||||
|
currency: "USD",
|
||||||
|
value: 10,
|
||||||
|
fraction: 0,
|
||||||
|
},
|
||||||
|
exchangeBaseUrl: "https://exchange.demo.taler.net",
|
||||||
});
|
});
|
||||||
|
@ -1,66 +1,155 @@
|
|||||||
import { h, Fragment, VNode } from "preact";
|
import {
|
||||||
import { useState } from "preact/hooks";
|
AmountJson,
|
||||||
|
Amounts,
|
||||||
|
parsePaytoUri,
|
||||||
|
PaytoUri,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { Fragment, h, VNode } from "preact";
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { QR } from "../components/QR";
|
import { QR } from "../components/QR";
|
||||||
import { ButtonBox, FontIcon, WalletBox } from "../components/styled";
|
import {
|
||||||
|
ButtonDestructive,
|
||||||
|
ButtonPrimary,
|
||||||
|
WalletBox,
|
||||||
|
WarningBox,
|
||||||
|
} from "../components/styled";
|
||||||
export interface Props {
|
export interface Props {
|
||||||
reservePub: string;
|
reservePub: string;
|
||||||
paytos: string[];
|
payto: string;
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
amount: AmountJson;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReserveCreated({ reservePub, paytos, onBack }: Props): VNode {
|
interface BankDetailsProps {
|
||||||
const [opened, setOpened] = useState(-1);
|
payto: PaytoUri;
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
subject: string;
|
||||||
|
amount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Row({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
literal,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
literal?: boolean;
|
||||||
|
}): VNode {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
function copyText(): void {
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
setCopied(true);
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopied(false);
|
||||||
|
}, 1000);
|
||||||
|
}, [copied]);
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{!copied ? (
|
||||||
|
<ButtonPrimary small onClick={copyText}>
|
||||||
|
Copy
|
||||||
|
</ButtonPrimary>
|
||||||
|
) : (
|
||||||
|
<ButtonPrimary small disabled>
|
||||||
|
Copied
|
||||||
|
</ButtonPrimary>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<b>{name}</b>
|
||||||
|
</td>
|
||||||
|
{literal ? (
|
||||||
|
<td>
|
||||||
|
<pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
|
||||||
|
{value}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
) : (
|
||||||
|
<td>{value}</td>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BankDetailsByPaytoType({
|
||||||
|
payto,
|
||||||
|
subject,
|
||||||
|
exchangeBaseUrl,
|
||||||
|
amount,
|
||||||
|
}: BankDetailsProps): VNode {
|
||||||
|
const firstPart = !payto.isKnown ? (
|
||||||
|
<Fragment>
|
||||||
|
<Row name="Account" value={payto.targetPath} />
|
||||||
|
<Row name="Exchange" value={exchangeBaseUrl} />
|
||||||
|
</Fragment>
|
||||||
|
) : payto.targetType === "x-taler-bank" ? (
|
||||||
|
<Fragment>
|
||||||
|
<Row name="Bank host" value={payto.host} />
|
||||||
|
<Row name="Bank account" value={payto.account} />
|
||||||
|
<Row name="Exchange" value={exchangeBaseUrl} />
|
||||||
|
</Fragment>
|
||||||
|
) : payto.targetType === "iban" ? (
|
||||||
|
<Fragment>
|
||||||
|
<Row name="IBAN" value={payto.iban} />
|
||||||
|
<Row name="Exchange" value={exchangeBaseUrl} />
|
||||||
|
</Fragment>
|
||||||
|
) : undefined;
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
{firstPart}
|
||||||
|
<Row name="Amount" value={amount} />
|
||||||
|
<Row name="Subject" value={subject} literal />
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function ReserveCreated({
|
||||||
|
reservePub,
|
||||||
|
payto,
|
||||||
|
onBack,
|
||||||
|
exchangeBaseUrl,
|
||||||
|
amount,
|
||||||
|
}: Props): VNode {
|
||||||
|
const paytoURI = parsePaytoUri(payto);
|
||||||
|
// const url = new URL(paytoURI?.targetPath);
|
||||||
|
if (!paytoURI) {
|
||||||
|
return <div>could not parse payto uri from exchange {payto}</div>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<WalletBox>
|
<WalletBox>
|
||||||
<section>
|
<section>
|
||||||
<h2>Reserve created!</h2>
|
<h1>Bank transfer details</h1>
|
||||||
<p>
|
<p>
|
||||||
Now you need to send money to the exchange to one of the following
|
Please wire <b>{Amounts.stringify(amount)}</b> to:
|
||||||
accounts
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To complete the setup of the reserve, you must now initiate a wire
|
|
||||||
transfer using the given wire transfer subject and crediting the
|
|
||||||
specified amount to the indicated account of the exchange.
|
|
||||||
</p>
|
</p>
|
||||||
|
<BankDetailsByPaytoType
|
||||||
|
amount={Amounts.stringify(amount)}
|
||||||
|
exchangeBaseUrl={exchangeBaseUrl}
|
||||||
|
payto={paytoURI}
|
||||||
|
subject={reservePub}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<ul>
|
<p>
|
||||||
{paytos.map((href, idx) => {
|
<WarningBox>
|
||||||
const url = new URL(href);
|
Make sure to use the correct subject, otherwise the money will not
|
||||||
return (
|
arrive in this wallet.
|
||||||
<li key={idx}>
|
</WarningBox>
|
||||||
<p>
|
</p>
|
||||||
<a
|
<p>
|
||||||
href=""
|
Alternative, you can also scan this QR code or open{" "}
|
||||||
onClick={(e) => {
|
<a href={payto}>this link</a> if you have a banking app installed that
|
||||||
setOpened((o) => (o === idx ? -1 : idx));
|
supports RFC 8905
|
||||||
e.preventDefault();
|
</p>
|
||||||
}}
|
<QR text={payto} />
|
||||||
>
|
|
||||||
{url.pathname}
|
|
||||||
</a>
|
|
||||||
{opened === idx && (
|
|
||||||
<Fragment>
|
|
||||||
<p>
|
|
||||||
If your system supports RFC 8905, you can do this by
|
|
||||||
opening <a href={href}>this URI</a> or scan the QR with
|
|
||||||
your wallet
|
|
||||||
</p>
|
|
||||||
<QR text={href} />
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
<ButtonBox onClick={onBack}>
|
|
||||||
<FontIcon>←</FontIcon>
|
|
||||||
</ButtonBox>
|
|
||||||
<div />
|
<div />
|
||||||
|
<ButtonDestructive onClick={onBack}>Cancel withdraw</ButtonDestructive>
|
||||||
</footer>
|
</footer>
|
||||||
</WalletBox>
|
</WalletBox>
|
||||||
);
|
);
|
||||||
|
@ -21,28 +21,27 @@ import {
|
|||||||
Transaction,
|
Transaction,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { format } from "date-fns";
|
import { h, VNode } from "preact";
|
||||||
import { JSX, VNode, h } from "preact";
|
|
||||||
import { route } from "preact-router";
|
import { route } from "preact-router";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import emptyImg from "../../static/img/empty.png";
|
import emptyImg from "../../static/img/empty.png";
|
||||||
import { ErrorMessage } from "../components/ErrorMessage";
|
import { ErrorMessage } from "../components/ErrorMessage";
|
||||||
import { Part } from "../components/Part";
|
import { Part } from "../components/Part";
|
||||||
import {
|
import {
|
||||||
ButtonBox,
|
Button,
|
||||||
ButtonBoxDestructive,
|
ButtonDestructive,
|
||||||
ButtonPrimary,
|
ButtonPrimary,
|
||||||
FontIcon,
|
|
||||||
ListOfProducts,
|
ListOfProducts,
|
||||||
RowBorderGray,
|
RowBorderGray,
|
||||||
SmallLightText,
|
SmallLightText,
|
||||||
WalletBox,
|
WalletBox,
|
||||||
WarningBox,
|
WarningBox,
|
||||||
} from "../components/styled";
|
} from "../components/styled";
|
||||||
|
import { Time } from "../components/Time";
|
||||||
import { Pages } from "../NavigationBar";
|
import { Pages } from "../NavigationBar";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
export function TransactionPage({ tid }: { tid: string }): JSX.Element {
|
export function TransactionPage({ tid }: { tid: string }): VNode {
|
||||||
const [transaction, setTransaction] = useState<Transaction | undefined>(
|
const [transaction, setTransaction] = useState<Transaction | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
@ -70,8 +69,8 @@ export function TransactionPage({ tid }: { tid: string }): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<TransactionView
|
<TransactionView
|
||||||
transaction={transaction}
|
transaction={transaction}
|
||||||
onDelete={() => wxApi.deleteTransaction(tid).then((_) => history.go(-1))}
|
onDelete={() => wxApi.deleteTransaction(tid).then(() => history.go(-1))}
|
||||||
onRetry={() => wxApi.retryTransaction(tid).then((_) => history.go(-1))}
|
onRetry={() => wxApi.retryTransaction(tid).then(() => history.go(-1))}
|
||||||
onBack={() => {
|
onBack={() => {
|
||||||
route(Pages.history);
|
route(Pages.history);
|
||||||
}}
|
}}
|
||||||
@ -91,42 +90,42 @@ export function TransactionView({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onRetry,
|
onRetry,
|
||||||
onBack,
|
onBack,
|
||||||
}: WalletTransactionProps) {
|
}: WalletTransactionProps): VNode {
|
||||||
function TransactionTemplate({ children }: { children: VNode[] }) {
|
function TransactionTemplate({ children }: { children: VNode[] }): VNode {
|
||||||
return (
|
return (
|
||||||
<WalletBox>
|
<WalletBox>
|
||||||
<section style={{ padding: 8, textAlign: "center" }}>
|
<section style={{ padding: 8, textAlign: "center" }}>
|
||||||
<ErrorMessage title={transaction?.error?.hint} />
|
<ErrorMessage title={transaction?.error?.hint} />
|
||||||
{transaction.pending && (
|
{transaction.pending && (
|
||||||
<WarningBox>This transaction is not completed</WarningBox>
|
<WarningBox>
|
||||||
|
This transaction is not completed
|
||||||
|
<a href="">more info...</a>
|
||||||
|
</WarningBox>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<div style={{ textAlign: "center" }}>{children}</div>
|
<div style={{ textAlign: "center" }}>{children}</div>
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
<ButtonBox onClick={onBack}>
|
<Button onClick={onBack}>
|
||||||
<i18n.Translate>
|
<i18n.Translate> < Back </i18n.Translate>
|
||||||
{" "}
|
</Button>
|
||||||
<FontIcon>←</FontIcon>{" "}
|
|
||||||
</i18n.Translate>
|
|
||||||
</ButtonBox>
|
|
||||||
<div>
|
<div>
|
||||||
{transaction?.error ? (
|
{transaction?.error ? (
|
||||||
<ButtonPrimary onClick={onRetry}>
|
<ButtonPrimary onClick={onRetry}>
|
||||||
<i18n.Translate>retry</i18n.Translate>
|
<i18n.Translate>retry</i18n.Translate>
|
||||||
</ButtonPrimary>
|
</ButtonPrimary>
|
||||||
) : null}
|
) : null}
|
||||||
<ButtonBoxDestructive onClick={onDelete}>
|
<ButtonDestructive onClick={onDelete}>
|
||||||
<i18n.Translate>🗑</i18n.Translate>
|
<i18n.Translate> Forget </i18n.Translate>
|
||||||
</ButtonBoxDestructive>
|
</ButtonDestructive>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</WalletBox>
|
</WalletBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function amountToString(text: AmountLike) {
|
function amountToString(text: AmountLike): string {
|
||||||
const aj = Amounts.jsonifyAmount(text);
|
const aj = Amounts.jsonifyAmount(text);
|
||||||
const amount = Amounts.stringifyValue(aj);
|
const amount = Amounts.stringifyValue(aj);
|
||||||
return `${amount} ${aj.currency}`;
|
return `${amount} ${aj.currency}`;
|
||||||
@ -140,23 +139,26 @@ export function TransactionView({
|
|||||||
return (
|
return (
|
||||||
<TransactionTemplate>
|
<TransactionTemplate>
|
||||||
<h2>Withdrawal</h2>
|
<h2>Withdrawal</h2>
|
||||||
<div>
|
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
|
||||||
{transaction.timestamp.t_ms === "never"
|
|
||||||
? "never"
|
|
||||||
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
|
|
||||||
</div>
|
|
||||||
<br />
|
<br />
|
||||||
<Part
|
<Part
|
||||||
|
big
|
||||||
title="Total withdrawn"
|
title="Total withdrawn"
|
||||||
text={amountToString(transaction.amountEffective)}
|
text={amountToString(transaction.amountEffective)}
|
||||||
kind="positive"
|
kind="positive"
|
||||||
/>
|
/>
|
||||||
<Part
|
<Part
|
||||||
|
big
|
||||||
title="Chosen amount"
|
title="Chosen amount"
|
||||||
text={amountToString(transaction.amountRaw)}
|
text={amountToString(transaction.amountRaw)}
|
||||||
kind="neutral"
|
kind="neutral"
|
||||||
/>
|
/>
|
||||||
<Part title="Exchange fee" text={amountToString(fee)} kind="negative" />
|
<Part
|
||||||
|
big
|
||||||
|
title="Exchange fee"
|
||||||
|
text={amountToString(fee)}
|
||||||
|
kind="negative"
|
||||||
|
/>
|
||||||
<Part
|
<Part
|
||||||
title="Exchange"
|
title="Exchange"
|
||||||
text={new URL(transaction.exchangeBaseUrl).hostname}
|
text={new URL(transaction.exchangeBaseUrl).hostname}
|
||||||
@ -166,7 +168,9 @@ export function TransactionView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const showLargePic = () => {};
|
const showLargePic = (): void => {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if (transaction.type === TransactionType.Payment) {
|
if (transaction.type === TransactionType.Payment) {
|
||||||
const fee = Amounts.sub(
|
const fee = Amounts.sub(
|
||||||
@ -177,11 +181,7 @@ export function TransactionView({
|
|||||||
return (
|
return (
|
||||||
<TransactionTemplate>
|
<TransactionTemplate>
|
||||||
<h2>Payment </h2>
|
<h2>Payment </h2>
|
||||||
<div>
|
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
|
||||||
{transaction.timestamp.t_ms === "never"
|
|
||||||
? "never"
|
|
||||||
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
|
|
||||||
</div>
|
|
||||||
<br />
|
<br />
|
||||||
<Part
|
<Part
|
||||||
big
|
big
|
||||||
@ -241,11 +241,7 @@ export function TransactionView({
|
|||||||
return (
|
return (
|
||||||
<TransactionTemplate>
|
<TransactionTemplate>
|
||||||
<h2>Deposit </h2>
|
<h2>Deposit </h2>
|
||||||
<div>
|
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
|
||||||
{transaction.timestamp.t_ms === "never"
|
|
||||||
? "never"
|
|
||||||
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
|
|
||||||
</div>
|
|
||||||
<br />
|
<br />
|
||||||
<Part
|
<Part
|
||||||
big
|
big
|
||||||
@ -272,11 +268,7 @@ export function TransactionView({
|
|||||||
return (
|
return (
|
||||||
<TransactionTemplate>
|
<TransactionTemplate>
|
||||||
<h2>Refresh</h2>
|
<h2>Refresh</h2>
|
||||||
<div>
|
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
|
||||||
{transaction.timestamp.t_ms === "never"
|
|
||||||
? "never"
|
|
||||||
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
|
|
||||||
</div>
|
|
||||||
<br />
|
<br />
|
||||||
<Part
|
<Part
|
||||||
big
|
big
|
||||||
@ -303,11 +295,7 @@ export function TransactionView({
|
|||||||
return (
|
return (
|
||||||
<TransactionTemplate>
|
<TransactionTemplate>
|
||||||
<h2>Tip</h2>
|
<h2>Tip</h2>
|
||||||
<div>
|
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
|
||||||
{transaction.timestamp.t_ms === "never"
|
|
||||||
? "never"
|
|
||||||
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
|
|
||||||
</div>
|
|
||||||
<br />
|
<br />
|
||||||
<Part
|
<Part
|
||||||
big
|
big
|
||||||
@ -334,11 +322,7 @@ export function TransactionView({
|
|||||||
return (
|
return (
|
||||||
<TransactionTemplate>
|
<TransactionTemplate>
|
||||||
<h2>Refund</h2>
|
<h2>Refund</h2>
|
||||||
<div>
|
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
|
||||||
{transaction.timestamp.t_ms === "never"
|
|
||||||
? "never"
|
|
||||||
: format(transaction.timestamp.t_ms, "dd MMMM yyyy, HH:mm")}
|
|
||||||
</div>
|
|
||||||
<br />
|
<br />
|
||||||
<Part
|
<Part
|
||||||
big
|
big
|
||||||
@ -391,5 +375,5 @@ export function TransactionView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div></div>;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
@ -20,16 +20,15 @@
|
|||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JSX } from "preact/jsx-runtime";
|
|
||||||
import { Checkbox } from "../components/Checkbox";
|
import { Checkbox } from "../components/Checkbox";
|
||||||
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
|
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
|
||||||
import { Diagnostics } from "../components/Diagnostics";
|
import { Diagnostics } from "../components/Diagnostics";
|
||||||
import { WalletBox } from "../components/styled";
|
import { WalletBox } from "../components/styled";
|
||||||
import { useDiagnostics } from "../hooks/useDiagnostics";
|
import { useDiagnostics } from "../hooks/useDiagnostics";
|
||||||
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
||||||
import { h } from "preact";
|
import { h, VNode } from "preact";
|
||||||
|
|
||||||
export function WelcomePage() {
|
export function WelcomePage(): VNode {
|
||||||
const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
|
const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
|
||||||
const [diagnostics, timedOut] = useDiagnostics();
|
const [diagnostics, timedOut] = useDiagnostics();
|
||||||
return (
|
return (
|
||||||
@ -53,7 +52,7 @@ export function View({
|
|||||||
togglePermissions,
|
togglePermissions,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
timedOut,
|
timedOut,
|
||||||
}: ViewProps): JSX.Element {
|
}: ViewProps): VNode {
|
||||||
return (
|
return (
|
||||||
<WalletBox>
|
<WalletBox>
|
||||||
<h1>Browser Extension Installed!</h1>
|
<h1>Browser Extension Installed!</h1>
|
||||||
|
@ -2,11 +2,27 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="/dist/popupEntryPoint.css" />
|
<link rel="stylesheet" type="text/css" href="/dist/popupEntryPoint.css" />
|
||||||
<link rel="icon" href="/static/img/icon.png" />
|
<link rel="icon" href="/static/img/icon.png" />
|
||||||
<script src="/dist/walletEntryPoint.js"></script>
|
<script src="/dist/walletEntryPoint.js"></script>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
font-family: sans-serif; /* 1 */
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f8faf7;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
Loading…
Reference in New Issue
Block a user