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