add new history view and some global style changes

This commit is contained in:
Marcos Gutierrez 2020-01-27 10:11:39 -03:00
parent b961ca2c39
commit d6d5647946
No known key found for this signature in database
GPG Key ID: 3C721C882729F8AB
2 changed files with 459 additions and 104 deletions

View File

@ -12,21 +12,24 @@ body {
padding: 0;
max-height: 800px;
overflow: hidden;
background-color: #f8faf7;
font-family: Arial, Helvetica, sans-serif;
}
.nav {
background-color: #ddd;
background-color: #033;
padding: 0.5em 0;
}
.nav a {
color: black;
padding: 0.5em;
color: #f8faf7;
padding: 0.7em 1.4em;
text-decoration: none;
}
.nav a.active {
background-color: white;
background-color: #f8faf7;
color: #000;
font-weight: bold;
}
@ -71,14 +74,114 @@ body {
}
.historyItem {
border: 1px solid black;
border-radius: 10px;
padding-left: 0.5em;
margin: 0.5em;
}
.historyDate {
font-size: 90%;
min-width: 380px;
display: flex;
flex-direction: row;
border-bottom: 1px solid #d9dbd8;
padding: 0.5em;
align-items: center;
}
.historyItem .amount {
font-size: 110%;
font-weight: bold;
text-align: right;
}
.historyDate,
.historyTitle,
.historyText,
.historySmall {
margin: 0.3em;
}
.historyDate {
font-size: 90%;
color: slategray;
}
text-align: right;
}
.historyLeft {
display: flex;
flex-direction: column;
text-align: right;
}
.historyContent {
flex: 1;
}
.historyTitle {
font-weight: 400;
}
.historyText {
font-size: 90%;
}
.historySmall {
font-size: 70%;
text-transform: uppercase;
}
.historyAmount {
flex-grow: 1;
}
.historyAmount .primary {
font-size: 100%;
}
.historyAmount .secondary {
font-size: 80%;
}
.historyAmount .positive {
color: #088;
}
.historyAmount .positive:before {
content: "+";
}
.historyAmount .negative {
color: #800
}
.historyAmount .negative:before {
color: #800;
content: "-";
}
.icon {
margin: 0 10px;
text-align: center;
width: 35px;
font-size: 20px;
border-radius: 50%;
background: #ccc;
color: #fff;
padding-top: 4px;
height: 30px;
}
.option {
text-transform: uppercase;
text-align: right;
padding: 0.4em;
font-size: 0.9em;
}
input[type=checkbox], input[type=radio] {
vertical-align: middle;
position: relative;
bottom: 1px;
}
input[type=radio] {
bottom: 2px;
}
.balance {
text-align: center;
padding-top: 2em;
}

View File

@ -42,9 +42,12 @@ import {
} from "../renderHtml";
import * as wxApi from "../wxApi";
import * as React from "react";
import React, { Fragment } from "react";
import { HistoryEvent } from "../../types/history";
import moment from "moment";
import { Timestamp } from "../../util/time";
function onUpdateNotification(f: () => void): () => void {
const port = chrome.runtime.connect({ name: "notifications" });
const listener = () => {
@ -185,7 +188,7 @@ function bigAmount(amount: AmountJson): JSX.Element {
const v = amount.value + amount.fraction / Amounts.fractionalBase;
return (
<span>
<span style={{ fontSize: "300%" }}>{v}</span>{" "}
<span style={{ fontSize: "5em", display: 'block'}}>{v}</span>{" "}
<span>{amount.currency}</span>
</span>
);
@ -193,12 +196,10 @@ function bigAmount(amount: AmountJson): JSX.Element {
function EmptyBalanceView() {
return (
<div>
<i18n.Translate wrap="p">
You have no balance to show. Need some{" "}
<PageLink pageName="welcome.html">help</PageLink> getting started?
</i18n.Translate>
</div>
<i18n.Translate wrap="p">
You have no balance to show. Need some{" "}
<PageLink pageName="welcome.html">help</PageLink> getting started?
</i18n.Translate>
);
}
@ -299,7 +300,7 @@ class WalletBalanceView extends React.Component<any, any> {
const wallet = this.balance;
if (this.gotError) {
return (
<div>
<div className='balance'>
<p>{i18n.str`Error: could not retrieve balance information.`}</p>
<p>
Click <PageLink pageName="welcome.html">here</PageLink> for help and
@ -320,114 +321,354 @@ class WalletBalanceView extends React.Component<any, any> {
</p>
);
});
return <div>{listing.length > 0 ? listing : <EmptyBalanceView />}</div>;
return listing.length > 0 ? <div className='balance'>{listing}</div> : <EmptyBalanceView />;
}
}
function Icon({ l }: { l: string }) {
return <div className={"icon"}>{l}</div>;
}
function formatAndCapitalize(text: string) {
text = text.replace("-", " ");
text = text.replace(/^./, text[0].toUpperCase());
return text;
}
type HistoryItemProps = {
title?: string | JSX.Element;
text?: string | JSX.Element;
small?: string | JSX.Element;
amount?: string | AmountJson;
fees?: string | AmountJson;
invalid?: string | AmountJson;
icon?: string;
timestamp: Timestamp;
negative?: boolean;
};
function HistoryItem({
title,
text,
small,
amount,
fees,
invalid,
timestamp,
icon,
negative = false
}: HistoryItemProps) {
function formatDate(timestamp: number | "never") {
if (timestamp !== "never") {
const itemDate = moment(timestamp);
if (itemDate.isBetween(moment().subtract(2, "days"), moment())) {
return itemDate.fromNow();
}
return itemDate.format("lll");
}
return null;
}
let invalidElement, amountElement, feesElement;
if (amount) {
amountElement = renderAmount(amount);
}
if (fees) {
fees = typeof fees === "string" ? Amounts.parse(fees) : fees;
if (fees && Amounts.isNonZero(fees)) {
feesElement = renderAmount(fees);
}
}
if (invalid) {
invalid = typeof invalid === "string" ? Amounts.parse(invalid) : invalid;
if (invalid && Amounts.isNonZero(invalid)) {
invalidElement = renderAmount(invalid);
}
}
return (
<div className="historyItem">
{icon ? <Icon l={icon} /> : null}
<div className="historyContent">
{title ? <div className={"historyTitle"}>{title}</div> : null}
{text ? <div className={"historyText"}>{text}</div> : null}
{small ? <div className={"historySmall"}>{small}</div> : null}
</div>
<div className={"historyLeft"}>
<div className={"historyAmount"}>
{amountElement ? (
<div className={`${negative ? "negative" : "positive"}`}>
{amountElement}
</div>
) : null}
{invalidElement ? (
<div className={"secondary"}>
{i18n.str`Invalid `}{" "}
<span className={"negative"}>{invalidElement}</span>
</div>
) : null}
{feesElement ? (
<div className={"secondary"}>
{i18n.str`Fees `}{" "}
<span className={"negative"}>{feesElement}</span>
</div>
) : null}
</div>
<div className="historyDate">{formatDate(timestamp.t_ms)}</div>
</div>
</div>
);
}
function amountDiff(
total: string | Amounts.AmountJson,
partial: string | Amounts.AmountJson
): Amounts.AmountJson | string {
let a = typeof total === "string" ? Amounts.parse(total) : total;
let b = typeof partial === "string" ? Amounts.parse(partial) : partial;
if (a && b) {
return Amounts.sub(a, b).amount;
} else {
return total;
}
}
function parseSummary(summary: string) {
let parsed = summary.split(/: (.+)/);
return {
merchant: parsed[0],
item: parsed[1]
};
}
function formatHistoryItem(historyItem: HistoryEvent) {
const d = historyItem;
console.log("hist item", historyItem);
switch (historyItem.type) {
/*
case "create-reserve":
case "refreshed": {
return (
<i18n.Translate wrap="p">
Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for{" "}
<span>{renderAmount(d.requestedAmount)}</span>.
</i18n.Translate>
);
case "confirm-reserve": {
const exchange = new URL(d.exchangeBaseUrl).host;
const pub = abbrev(d.reservePub);
return (
<i18n.Translate wrap="p">
Started to withdraw
<span>{renderAmount(d.requestedAmount)}</span>
from <span>{exchange}</span> (<span>{pub}</span>).
</i18n.Translate>
<HistoryItem
timestamp={historyItem.timestamp}
small={i18n.str`Refresh sessions has completed`}
fees={amountDiff(
historyItem.amountRefreshedRaw,
historyItem.amountRefreshedEffective
)}
/>
);
}
case "offer-contract": {
case "order-refused": {
const { merchant, item } = parseSummary(
historyItem.orderShortInfo.summary
);
return (
<i18n.Translate wrap="p">
Merchant <em>{abbrev(d.merchantName, 15)}</em> offered contract{" "}
<span>{abbrev(d.contractTermsHash)}</span>.
</i18n.Translate>
<HistoryItem
icon={"X"}
timestamp={historyItem.timestamp}
small={i18n.str`Order Refused`}
title={merchant}
text={abbrev(item, 30)}
/>
);
}
case "depleted-reserve": {
const exchange = d.exchangeBaseUrl
? new URL(d.exchangeBaseUrl).host
: "??";
const amount = renderAmount(d.requestedAmount);
const pub = abbrev(d.reservePub);
case "order-redirected": {
const { merchant, item } = parseSummary(
historyItem.newOrderShortInfo.summary
);
return (
<i18n.Translate wrap="p">
Withdrew <span>{amount}</span> from <span>{exchange}</span> (
<span>{pub}</span>).
</i18n.Translate>
<HistoryItem
icon={"⟲"}
small={i18n.str`Order redirected`}
text={abbrev(item, 40)}
timestamp={historyItem.timestamp}
title={merchant}
/>
);
}
case "pay": {
const url = d.fulfillmentUrl;
const merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
case "payment-aborted": {
const { merchant, item } = parseSummary(
historyItem.orderShortInfo.summary
);
return (
<HistoryItem
amount={historyItem.orderShortInfo.amount}
fees={historyItem.amountLost}
icon={"P"}
small={i18n.str`Payment aborted`}
text={abbrev(item, 40)}
timestamp={historyItem.timestamp}
title={merchant}
/>
);
}
case "payment-sent": {
const url = historyItem.orderShortInfo.merchantBaseUrl;
const { merchant, item } = parseSummary(
historyItem.orderShortInfo.summary
);
const fees = amountDiff(
historyItem.amountPaidWithFees,
historyItem.orderShortInfo.amount
);
const fulfillmentLinkElem = (
<a href={url} onClick={openTab(url)}>
view product
</a>
<Fragment>
<a
href={historyItem.orderShortInfo.merchantBaseUrl}
onClick={openTab(url)}
>
{item ? abbrev(item, 30) : null}
</a>
</Fragment>
);
return (
<i18n.Translate wrap="p">
Paid <span>{renderAmount(d.amount)}</span> to merchant{" "}
<span>{merchantElem}</span>.<span> </span>(
<span>{fulfillmentLinkElem}</span>)
</i18n.Translate>
<HistoryItem
amount={historyItem.orderShortInfo.amount}
fees={fees}
icon={"P"}
negative={true}
small={i18n.str`Payment Sent`}
text={fulfillmentLinkElem}
timestamp={historyItem.timestamp}
title={merchant}
/>
);
}
case "order-accepted": {
const url = historyItem.orderShortInfo.merchantBaseUrl;
const { merchant, item } = parseSummary(
historyItem.orderShortInfo.summary
);
const fulfillmentLinkElem = (
<Fragment>
<a
href={historyItem.orderShortInfo.merchantBaseUrl}
onClick={openTab(url)}
>
{item ? abbrev(item, 40) : null}
</a>
</Fragment>
);
return (
<HistoryItem
negative={true}
amount={historyItem.orderShortInfo.amount}
icon={"P"}
small={i18n.str`Order accepted`}
text={fulfillmentLinkElem}
timestamp={historyItem.timestamp}
title={merchant}
/>
);
}
case "reserve-balance-updated": {
return (
<HistoryItem
timestamp={historyItem.timestamp}
small={i18n.str`Reserve balance updated`}
fees={amountDiff(
historyItem.amountExpected,
historyItem.amountReserveBalance
)}
/>
);
}
case "refund": {
const merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
const merchantElem = (
<em>{abbrev(historyItem.orderShortInfo.summary, 25)}</em>
);
return (
<i18n.Translate wrap="p">
Merchant <span>{merchantElem}</span> gave a refund over{" "}
<span>{renderAmount(d.refundAmount)}</span>.
</i18n.Translate>
<HistoryItem
icon={"R"}
timestamp={historyItem.timestamp}
small={i18n.str`Payment refund`}
text={merchantElem}
amount={historyItem.amountRefundedRaw}
invalid={historyItem.amountRefundedInvalid}
fees={amountDiff(
amountDiff(
historyItem.amountRefundedRaw,
historyItem.amountRefundedInvalid
),
historyItem.amountRefundedEffective
)}
/>
);
}
case "tip": {
const tipPageUrl = new URL(chrome.extension.getURL("/src/webex/pages/tip.html"));
tipPageUrl.searchParams.set("tip_id", d.tipId);
tipPageUrl.searchParams.set("merchant_domain", d.merchantDomain);
const url = tipPageUrl.href;
const tipLink = <a href={url} onClick={openTab(url)}>{i18n.str`tip`}</a>;
// i18n: Tip
case "withdrawn": {
const exchange = new URL(historyItem.exchangeBaseUrl).host;
const fees = amountDiff(
historyItem.amountWithdrawnRaw,
historyItem.amountWithdrawnEffective
);
return (
<>
<i18n.Translate wrap="p">
Merchant <span>{d.merchantDomain}</span> gave a{" "}
<span>{tipLink}</span> of <span>{renderAmount(d.amount)}</span>.
</i18n.Translate>
<span>
{" "}
{d.accepted ? null : (
<i18n.Translate>You did not accept the tip yet.</i18n.Translate>
)}
</span>
</>
<HistoryItem
amount={historyItem.amountWithdrawnRaw}
fees={fees}
icon={"w"}
small={i18n.str`Withdrawn`}
title={exchange}
timestamp={historyItem.timestamp}
/>
);
}
case "tip-accepted": {
return (
<HistoryItem
icon={"T"}
negative={true}
timestamp={historyItem.timestamp}
title={<i18n.Translate wrap={Fragment}>Tip Accepted</i18n.Translate>}
amount={historyItem.tipAmountRaw}
/>
);
}
case "tip-declined": {
return (
<HistoryItem
icon={"T"}
timestamp={historyItem.timestamp}
title={<i18n.Translate wrap={Fragment}>Tip Declined</i18n.Translate>}
amount={historyItem.tipAmountRaw}
/>
);
}
*/
default:
return <p>{i18n.str`Unknown event (${historyItem.type})`}</p>;
return (
<HistoryItem
timestamp={historyItem.timestamp}
small={i18n.str`${formatAndCapitalize(historyItem.type)}`}
/>
);
}
}
const HistoryComponent = (props: any) => {
const record = props.record;
return formatHistoryItem(record);
};
class WalletHistory extends React.Component<any, any> {
private myHistory: any[];
private gotError = false;
private unmounted = false;
private hidenTypes: string[] = [
"order-accepted",
"order-redirected",
"refreshed",
"reserve-balance-updated",
"exchange-updated",
"exchange-added"
];
componentWillMount() {
this.update();
this.setState({ filter: true });
onUpdateNotification(() => this.update());
}
@ -468,21 +709,32 @@ class WalletHistory extends React.Component<any, any> {
}
const listing: any[] = [];
for (const record of history.reverse()) {
const item = (
<div className="historyItem">
<div className="historyDate">
{new Date(record.timestamp.t_ms).toString()}
</div>
{formatHistoryItem(record)}
</div>
);
const messages = history
.reverse()
.filter(hEvent => {
if (!this.state.filter) return true;
return this.hidenTypes.indexOf(hEvent.type) === -1;
});
for (const record of messages) {
const item = (<HistoryComponent key={record.eventId} record={record} />);
listing.push(item);
}
if (listing.length > 0) {
return <div className="container">{listing}</div>;
return (
<div>
<div className="container">{listing}</div>
<div className="option">
Filtered list{" "}
<input
type="checkbox"
checked={this.state.filter}
onChange={() => this.setState({ filter: !this.state.filter })}
/>
</div>
</div>
);
}
return <p>{i18n.str`Your wallet has no events recorded.`}</p>;
}