new wallet history and view refactoring

This commit is contained in:
Sebastian 2021-08-19 00:34:47 -03:00
parent b015f76e72
commit 97a05ff659
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
30 changed files with 869 additions and 160 deletions

View File

@ -15,7 +15,8 @@
*/ */
import { Fragment } from "preact" import { Fragment } from "preact"
import { NavBar } from '../src/popup/popup' import { NavBar } from '../src/NavigationBar'
import { LogoHeader } from '../src/components/LogoHeader'
import { TranslationProvider } from '../src/context/translation' import { TranslationProvider } from '../src/context/translation'
export const parameters = { export const parameters = {
@ -51,16 +52,16 @@ export const decorators = [
return <div style={{ width: 400, height: 320 }}> return <div style={{ width: 400, height: 320 }}>
<Story /> <Story />
</div> </div>
} else {
const path = !isTestingHeader ? /popup(\/.*).*/.exec(kind)[1] : ''
// add a fake header so it looks similar
return <Fragment>
<NavBar path={path} devMode={path === '/dev'} />
<div style={{ width: 400, height: 290 }}>
<Story />
</div>
</Fragment>
} }
const path = /popup(\/.*).*/.exec(kind)[1];
// add a fake header so it looks similar
return <Fragment>
<NavBar path={path} devMode={path === '/dev'} />
<div style={{ width: 400, height: 290 }}>
<Story />
</div>
</Fragment>
} }
return <div class="popup-container"> return <div class="popup-container">
@ -95,13 +96,73 @@ export const decorators = [
</div> </div>
</div> </div>
} }
if (kind.startsWith('wallet')) { if (kind.startsWith('cta')) {
return <div class="wallet-container"> return <div>
<style>{`
html {
font-family: sans-serif; /* 1 */
}
body {
margin: 0;
}`}
</style>
<style>{`
html {
}
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>
<link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" /> <link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" />
<link key="2" rel="stylesheet" type="text/css" href="/style/wallet.css" /> <link key="2" rel="stylesheet" type="text/css" href="/style/wallet.css" />
<Story /> <Story />
</div> </div>
} }
if (kind.startsWith('wallet')) {
const path = /wallet(\/.*).*/.exec(kind)[1];
return <div class="wallet-container">
<style>{`
html {
font-family: sans-serif; /* 1 */
}
body {
margin: 0;
}`}
</style>
<style>{`
html {
}
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>
<LogoHeader />
<NavBar path={path} devMode={path === '/dev'} />
{/* <link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" />
<link key="2" rel="stylesheet" type="text/css" href="/style/wallet.css" /> */}
<Story />
</div>
}
return <div> return <div>
<h1>this story is not under wallet or popup, check title property</h1> <h1>this story is not under wallet or popup, check title property</h1>
<Story /> <Story />

View File

@ -12,7 +12,7 @@
"test": "jest ./tests", "test": "jest ./tests",
"compile": "tsc && rollup -c", "compile": "tsc && rollup -c",
"build-storybook": "build-storybook", "build-storybook": "build-storybook",
"storybook": "start-storybook -s static -p 6006", "storybook": "start-storybook -s . -p 6006",
"watch": "tsc --watch & rollup -w -c" "watch": "tsc --watch & rollup -w -c"
}, },
"dependencies": { "dependencies": {

View File

@ -1,14 +1,13 @@
// rollup.config.js // rollup.config.js
import commonjs from "@rollup/plugin-commonjs";
import nodeResolve from "@rollup/plugin-node-resolve";
import json from "@rollup/plugin-json";
import builtins from "builtin-modules";
import replace from "@rollup/plugin-replace";
import ignore from "rollup-plugin-ignore"
import image from '@rollup/plugin-image';
import linaria from '@linaria/rollup'; import linaria from '@linaria/rollup';
import css from 'rollup-plugin-css-only';
import alias from '@rollup/plugin-alias'; import alias from '@rollup/plugin-alias';
import commonjs from "@rollup/plugin-commonjs";
import image from '@rollup/plugin-image';
import json from "@rollup/plugin-json";
import nodeResolve from "@rollup/plugin-node-resolve";
import replace from "@rollup/plugin-replace";
import css from 'rollup-plugin-css-only';
import ignore from "rollup-plugin-ignore";
const makePlugins = () => [ const makePlugins = () => [
alias({ alias({

View File

@ -27,10 +27,11 @@
import { i18n } from "@gnu-taler/taler-util"; import { i18n } from "@gnu-taler/taler-util";
import { ComponentChildren, JSX } from "preact"; import { ComponentChildren, JSX } from "preact";
import Match from "preact-router/match"; import Match from "preact-router/match";
import { useDevContext } from "../context/devContext"; import { useDevContext } from "./context/devContext";
import { PopupNavigation } from '../components/styled' import { PopupNavigation } from './components/styled'
export enum Pages { export enum Pages {
welcome = '/welcome',
balance = '/balance', balance = '/balance',
settings = '/settings', settings = '/settings',
dev = '/dev', dev = '/dev',
@ -39,6 +40,15 @@ export enum Pages {
transaction = '/transaction/:tid', transaction = '/transaction/:tid',
provider_detail = '/provider/:pid', provider_detail = '/provider/:pid',
provider_add = '/provider/add', provider_add = '/provider/add',
reset_required = '/reset-required',
payback = '/payback',
return_coins = '/return-coins',
pay = '/pay',
refund = '/refund',
tips = '/tips',
withdraw = '/withdraw',
} }
interface TabProps { interface TabProps {
@ -59,18 +69,23 @@ function Tab(props: TabProps): JSX.Element {
); );
} }
export function NavBar({devMode, path}:{path:string, devMode:boolean}) { export function NavBar({ devMode, path }: { path: string, devMode: boolean }) {
return <PopupNavigation devMode={devMode}> return <PopupNavigation devMode={devMode}>
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab> <div>
<Tab target="/history" current={path}>{i18n.str`History`}</Tab> <Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
<Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab> <Tab target="/history" current={path}>{i18n.str`History`}</Tab>
<Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab> <Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
{devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>} <Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab>
{devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>}
</div>
</PopupNavigation> </PopupNavigation>
} }
export function WalletNavBar() { export function WalletNavBar() {
const { devMode } = useDevContext() const { devMode } = useDevContext()
return <Match>{({ path }: any) => <NavBar devMode={devMode} path={path} />}</Match> return <Match>{({ path }: any) => {
console.log("path", path)
return <NavBar devMode={devMode} path={path} />
}}</Match>
} }

View File

@ -0,0 +1,13 @@
export function LogoHeader() {
return <div style={{
display: 'flex',
justifyContent: 'space-around',
margin: '2em',
}}>
<img style={{
width: 150,
height: 70,
}} src="/static/img/logo-2021.svg" width="150" />
</div>
}

View File

@ -11,7 +11,7 @@ export const PaymentStatus = styled.div<{ color: string }>`
background-color: ${p => p.color}; background-color: ${p => p.color};
` `
export const WalletPage = styled.section` export const WalletAction = styled.section`
border: solid 5px black; border: solid 5px black;
border-radius: 10px; border-radius: 10px;
margin-left: auto; margin-left: auto;
@ -28,8 +28,73 @@ export const WalletPage = styled.section`
} }
` `
export const DateSeparator = styled.div`
color: gray;
margin: .2em;
margin-top: 1em;
`
export const WalletBox = styled.div<{ noPadding?: boolean }>`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
& > * {
width: 400px;
}
& > section {
padding-left: ${({ noPadding }) => noPadding ? '0px' : '8px'};
padding-right: ${({ noPadding }) => noPadding ? '0px' : '8px'};
// this margin will send the section up when used with a header
margin-bottom: auto;
overflow: auto;
table td {
padding: 5px 10px;
}
table tr {
border-bottom: 1px solid black;
border-top: 1px solid black;
}
}
& > header {
flex-direction: row;
justify-content: space-between;
display: flex;
padding: 8px;
margin-bottom: 5px;
& > div {
align-self: center;
}
& > h3 {
margin: 0px;
}
& > .title {
/* margin: 1em; */
font-size: large;
color: #3c4e92;
}
}
& > footer {
padding-top: 8px;
padding-bottom: 8px;
flex-direction: row;
justify-content: space-between;
display: flex;
& button {
margin-right: 8px;
margin-left: 8px;
}
}
`
export const PopupBox = styled.div<{ noPadding?: boolean }>` export const PopupBox = styled.div<{ noPadding?: boolean }>`
height: 290px; height: 290px;
width: 400px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@ -194,10 +259,32 @@ export const RowBorderGray = styled(Row)`
export const RowLightBorderGray = styled(Row2)` export const RowLightBorderGray = styled(Row2)`
border: 1px solid lightgray; border: 1px solid lightgray;
/* border-radius: 0.5em; */ border-top: 0px;
${DateSeparator} + & {
border: 1px solid lightgray;
background-color: red;
}
` `
export const HistoryRow = styled(RowLightBorderGray)` export const HistoryRow = styled.a`
text-decoration: none;
display: flex;
justify-content: space-between;
padding: 0.5em;
border: 1px solid lightgray;
border-top: 0px;
${DateSeparator} + & {
border: 1px solid lightgray;
}
:hover {
background-color: lightgray;
}
& > ${Column}:last-of-type { & > ${Column}:last-of-type {
margin-left: auto; margin-left: auto;
align-self: center; align-self: center;
@ -284,11 +371,17 @@ export const ErrorBox = styled.div`
} }
} }
` `
export const PopupNavigation = styled.div<{devMode?:boolean}>` export const PopupNavigation = styled.div<{ devMode?: boolean }>`
background-color:#0042b2; background-color:#0042b2;
height: 35px; height: 35px;
justify-content: space-around;
display: flex;
& > a { & > div {
width: 400px;
}
& > div > a {
color: #f8faf7; color: #f8faf7;
display: inline-block; display: inline-block;
width: calc(400px / ${({ devMode }) => !devMode ? 4 : 5}); width: calc(400px / ${({ devMode }) => !devMode ? 4 : 5});
@ -298,7 +391,7 @@ export const PopupNavigation = styled.div<{devMode?:boolean}>`
line-height: 35px; line-height: 35px;
} }
& > a.active { & > div > a.active {
background-color: #f8faf7; background-color: #f8faf7;
color: #0042b2; color: #0042b2;
font-weight: bold; font-weight: bold;

View File

@ -20,23 +20,16 @@
*/ */
import { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util'; import { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util';
import { FunctionalComponent, h } from 'preact'; import { createExample } from '../test-utils';
import { PaymentRequestView as TestedComponent } from './Pay'; import { PaymentRequestView as TestedComponent } from './Pay';
export default { export default {
title: 'wallet/pay', title: 'cta/pay',
component: TestedComponent, component: TestedComponent,
argTypes: { argTypes: {
}, },
}; };
function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
const r = (args: any) => <Component {...args} />
r.args = props
return r
}
export const InsufficientBalance = createExample(TestedComponent, { export const InsufficientBalance = createExample(TestedComponent, {
payStatus: { payStatus: {
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,

View File

@ -19,24 +19,18 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { ContractTerms, OrderShortInfo, PreparePayResultType } from '@gnu-taler/taler-util'; import { OrderShortInfo } from '@gnu-taler/taler-util';
import { FunctionalComponent, h } from 'preact'; import { createExample } from '../test-utils';
import { View as TestedComponent } from './Refund'; import { View as TestedComponent } from './Refund';
export default { export default {
title: 'wallet/refund', title: 'cta/refund',
component: TestedComponent, component: TestedComponent,
argTypes: { argTypes: {
}, },
}; };
function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
const r = (args: any) => <Component {...args} />
r.args = props
return r
}
export const Complete = createExample(TestedComponent, { export const Complete = createExample(TestedComponent, {
applyResult: { applyResult: {
amountEffectivePaid: 'USD:10', amountEffectivePaid: 'USD:10',

View File

@ -19,24 +19,17 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util'; import { createExample } from '../test-utils';
import { FunctionalComponent, h } from 'preact';
import { View as TestedComponent } from './Tip'; import { View as TestedComponent } from './Tip';
export default { export default {
title: 'wallet/tip', title: 'cta/tip',
component: TestedComponent, component: TestedComponent,
argTypes: { argTypes: {
}, },
}; };
function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
const r = (args: any) => <Component {...args} />
r.args = props
return r
}
export const Accepted = createExample(TestedComponent, { export const Accepted = createExample(TestedComponent, {
prepareTipResult: { prepareTipResult: {
accepted: true, accepted: true,

View File

@ -19,32 +19,27 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { h } from 'preact'; import { createExample } from '../test-utils';
import { View, ViewProps } from './Withdraw'; import { View as TestedComponent } from './Withdraw';
export default { export default {
title: 'wallet/withdraw', title: 'cta/withdraw',
component: View, component: TestedComponent,
argTypes: { argTypes: {
}, },
}; };
export const WithoutDetails = (a: any) => <View {...a} />; export const CompleteWithExchange = createExample(TestedComponent, {
WithoutDetails.args = {
} as ViewProps
export const CompleteWithExchange = (a: any) => <View {...a} />;
CompleteWithExchange.args = {
details: { details: {
amount: 'USD:2', amount: 'USD:2',
possibleExchanges: [],
}, },
selectedExchange: 'Some exchange' selectedExchange: 'Some exchange'
} as ViewProps })
export const CompleteWithoutExchange = createExample(TestedComponent, {
export const CompleteWithoutExchange = (a: any) => <View {...a} />;
CompleteWithoutExchange.args = {
details: { details: {
amount: 'USD:2', amount: 'USD:2',
possibleExchanges: [],
}, },
} as ViewProps })

View File

@ -32,14 +32,13 @@ import {
} from "../wxApi"; } from "../wxApi";
import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util"; import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
import { JSX } from "preact/jsx-runtime"; import { JSX } from "preact/jsx-runtime";
import { WalletPage } from '../components/styled'; import { WalletAction } from '../components/styled';
interface Props { interface Props {
talerWithdrawUri?: string; talerWithdrawUri?: string;
} }
export interface ViewProps { export interface ViewProps {
talerWithdrawUri?: string;
details: WithdrawUriInfoResponse; details: WithdrawUriInfoResponse;
selectedExchange?: string; selectedExchange?: string;
accept: () => Promise<void>; accept: () => Promise<void>;
@ -50,7 +49,7 @@ export interface ViewProps {
export function View({ details, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) { export function View({ details, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) {
return ( return (
<WalletPage> <WalletAction>
<div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;"> <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
<h1 style="font-family: monospace; font-size: 250%;"> <h1 style="font-family: monospace; font-size: 250%;">
<span style="color: #aa3939;"></span>Taler Wallet<span style="color: #aa3939;"></span> <span style="color: #aa3939;"></span>Taler Wallet<span style="color: #aa3939;"></span>
@ -101,26 +100,19 @@ export function View({ details, selectedExchange, accept, setCancelled, setSelec
</div> </div>
</div> </div>
</div> </div>
</WalletPage> </WalletAction>
) )
} }
export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element { export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element {
const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined); const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined);
const [selectedExchange, setSelectedExchange] = useState< const [selectedExchange, setSelectedExchange] = useState<string | undefined>(undefined);
string | undefined
>(undefined);
const [cancelled, setCancelled] = useState(false); const [cancelled, setCancelled] = useState(false);
const [selecting, setSelecting] = useState(false); const [selecting, setSelecting] = useState(false);
const [errMsg, setErrMsg] = useState<string | undefined>(""); const [error, setError] = useState<boolean>(false);
const [updateCounter, setUpdateCounter] = useState(1); const [updateCounter, setUpdateCounter] = useState(1);
const [state, setState] = useState(1) const [state, setState] = useState(1)
// setTimeout(() => {
// console.log('tick...')
// setState(s => s + 1)
// }, 1000);
useEffect(() => { useEffect(() => {
return onUpdateNotification(() => { return onUpdateNotification(() => {
console.log('updating...') console.log('updating...')
@ -132,20 +124,19 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
console.log('on effect yes', talerWithdrawUri) console.log('on effect yes', talerWithdrawUri)
if (!talerWithdrawUri) return if (!talerWithdrawUri) return
const fetchData = async (): Promise<void> => { const fetchData = async (): Promise<void> => {
console.log('que pasa')
try { try {
const res = await getWithdrawalDetailsForUri({ talerWithdrawUri }); const res = await getWithdrawalDetailsForUri({ talerWithdrawUri });
console.log('res', res)
setDetails(res); setDetails(res);
if (res.defaultExchangeBaseUrl) { if (res.defaultExchangeBaseUrl) {
setSelectedExchange(res.defaultExchangeBaseUrl); setSelectedExchange(res.defaultExchangeBaseUrl);
} }
} catch (e) { } catch (e) {
console.error(e) console.error('error',JSON.stringify(e,undefined,2))
setError(true)
} }
}; };
fetchData(); fetchData();
}, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter, state]); }, [selectedExchange, selecting, talerWithdrawUri, updateCounter, state]);
if (!talerWithdrawUri) { if (!talerWithdrawUri) {
return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>; return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
@ -169,6 +160,9 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
if (cancelled) { if (cancelled) {
return <span><i18n.Translate>Withdraw operation has been cancelled.</i18n.Translate></span>; return <span><i18n.Translate>Withdraw operation has been cancelled.</i18n.Translate></span>;
} }
if (error) {
return <span><i18n.Translate>This URI is not valid anymore.</i18n.Translate></span>;
}
return <View accept={accept} return <View accept={accept}
setCancelled={setCancelled} setSelecting={setSelecting} setCancelled={setCancelled} setSelecting={setSelecting}

View File

@ -25,7 +25,7 @@ import {
SmallText, SmallTextLight SmallText, SmallTextLight
} from "../components/styled"; } from "../components/styled";
import { useBackupStatus } from "../hooks/useBackupStatus"; import { useBackupStatus } from "../hooks/useBackupStatus";
import { Pages } from "./popup"; import { Pages } from "../NavigationBar";
interface Props { interface Props {
onAddProvider: () => void; onAddProvider: () => void;

View File

@ -14,11 +14,19 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { AmountJson, Amounts, AmountString, Balance, Timestamp, Transaction, TransactionsResponse, TransactionType } from "@gnu-taler/taler-util"; import { AmountString, Balance, Timestamp, Transaction, TransactionsResponse, TransactionType } from "@gnu-taler/taler-util";
import { formatDistance } from "date-fns";
import { JSX } from "preact"; import { JSX } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
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';
import imageRefund from '../../static/img/ri-refund-2-line.svg';
import imageShoppingCart from '../../static/img/ri-shopping-cart-line.svg';
import { Column, ExtraLargeText, HistoryRow, PopupBox, SmallTextLight } from "../components/styled";
import { useBalances } from "../hooks/useBalances";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import { Pages } from "./popup"; import { Pages } from "../NavigationBar";
export function HistoryPage(props: any): JSX.Element { export function HistoryPage(props: any): JSX.Element {
@ -45,7 +53,7 @@ export function HistoryPage(props: any): JSX.Element {
function amountToString(c: AmountString) { function amountToString(c: AmountString) {
const idx = c.indexOf(':') const idx = c.indexOf(':')
return `${c.substring(idx+1)} ${c.substring(0,idx)}` return `${c.substring(idx + 1)} ${c.substring(0, idx)}`
} }
@ -68,20 +76,14 @@ export function HistoryView({ list, balances }: { list: Transaction[], balances:
))} ))}
</section> </section>
<footer style={{ justifyContent: 'space-around' }}> <footer style={{ justifyContent: 'space-around' }}>
<a style={{ color: 'darkgreen', textDecoration:'none' }} href={Pages.transaction.replace(':tid', 'asd')}>VIEW MORE TRANSACTIONS</a> <a target="_blank"
rel="noopener noreferrer"
style={{ color: 'darkgreen', textDecoration: 'none' }}
href={chrome.extension ? chrome.extension.getURL(`/static/wallet.html#/history`) : '#'}>VIEW MORE TRANSACTIONS</a>
</footer> </footer>
</PopupBox> </PopupBox>
} }
import imageBank from '../../static/img/ri-bank-line.svg';
import imageShoppingCart from '../../static/img/ri-shopping-cart-line.svg';
import imageRefund from '../../static/img/ri-refund-2-line.svg';
import imageHandHeart from '../../static/img/ri-hand-heart-line.svg';
import imageRefresh from '../../static/img/ri-refresh-line.svg';
import { Column, ExtraLargeText, HistoryRow, PopupBox, Row, RowBorderGray, SmallTextLight } from "../components/styled";
import { useBalances } from "../hooks/useBalances";
import { formatDistance } from "date-fns";
function TransactionItem(props: { tx: Transaction }): JSX.Element { function TransactionItem(props: { tx: Transaction }): JSX.Element {
const tx = props.tx; const tx = props.tx;
switch (tx.type) { switch (tx.type) {
@ -171,18 +173,16 @@ function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
const now = new Date(); const now = new Date();
const dateStr = formatDistance(date, now, { addSuffix: true }) const dateStr = formatDistance(date, now, { addSuffix: true })
return ( return (
<HistoryRow> <HistoryRow href={Pages.transaction.replace(':tid', props.id)}>
<img src={props.iconPath} /> <img src={props.iconPath} />
<Column> <Column>
<ExtraLargeText> <ExtraLargeText>
<a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a> <span>{props.title}</span>
{props.pending ? ( {props.pending ? (
<span style={{ color: "darkblue" }}> (Pending)</span> <span style={{ color: "darkblue" }}> (Pending)</span>
) : null} ) : null}
</ExtraLargeText> </ExtraLargeText>
<SmallTextLight>{dateStr}</SmallTextLight> <SmallTextLight>{dateStr}</SmallTextLight>
{/* <div>{props.subtitle}</div> */}
</Column> </Column>
<TransactionAmount <TransactionAmount
pending={props.pending} pending={props.pending}

View File

@ -20,7 +20,7 @@
*/ */
import { Fragment, FunctionalComponent } from 'preact'; import { Fragment, FunctionalComponent } from 'preact';
import { NavBar as TestedComponent } from './popup'; import { NavBar as TestedComponent } from '../NavigationBar';
export default { export default {
title: 'popup/header', title: 'popup/header',

View File

@ -20,7 +20,7 @@ import { Fragment, JSX, VNode } from "preact";
import { route } from 'preact-router'; import { route } from 'preact-router';
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import { Pages } from "./popup"; import { Pages } from "../NavigationBar";
import emptyImg from "../../static/img/empty.png" import emptyImg from "../../static/img/empty.png"
import { Button, ButtonDestructive, ButtonPrimary, ListOfProducts, PopupBox, Row, RowBorderGray, SmallTextLight } from "../components/styled"; import { Button, ButtonDestructive, ButtonPrimary, ListOfProducts, PopupBox, Row, RowBorderGray, SmallTextLight } from "../components/styled";
import { ErrorMessage } from "../components/ErrorMessage"; import { ErrorMessage } from "../components/ErrorMessage";

View File

@ -34,7 +34,7 @@ import { DeveloperPage as DeveloperPage } from "./popup/Debug";
import { HistoryPage } from "./popup/History"; import { HistoryPage } from "./popup/History";
import { import {
Pages, WalletNavBar Pages, WalletNavBar
} from "./popup/popup"; } from "./NavigationBar";
import { ProviderAddPage } from "./popup/ProviderAddPage"; import { ProviderAddPage } from "./popup/ProviderAddPage";
import { ProviderDetailPage } from "./popup/ProviderDetailPage"; import { ProviderDetailPage } from "./popup/ProviderDetailPage";
import { SettingsPage } from "./popup/Settings"; import { SettingsPage } from "./popup/Settings";

View File

@ -0,0 +1,8 @@
import { FunctionalComponent, h as render } from 'preact';
export function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
const r = (args: any) => render(Component, args)
r.args = props
return r
}

View File

@ -0,0 +1,294 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import {
PaymentStatus,
TransactionCommon, TransactionDeposit, TransactionPayment,
TransactionRefresh, TransactionRefund, TransactionTip, TransactionType,
TransactionWithdrawal,
WithdrawalType
} from '@gnu-taler/taler-util';
import { FunctionalComponent } from 'preact';
import { HistoryView as TestedComponent } from './History';
export default {
title: 'wallet/history/list',
component: TestedComponent,
};
let count = 0
const commonTransaction = () => ({
amountRaw: 'USD:10',
amountEffective: 'USD:9',
pending: false,
timestamp: {
t_ms: new Date().getTime() - (count++ * 1000*60*60*7)
},
transactionId: '12',
} as TransactionCommon)
const exampleData = {
withdraw: {
...commonTransaction(),
type: TransactionType.Withdrawal,
exchangeBaseUrl: 'http://exchange.taler',
withdrawalDetails: {
confirmed: false,
exchangePaytoUris: ['payto://x-taler-bank/bank/account'],
type: WithdrawalType.ManualTransfer,
}
} as TransactionWithdrawal,
payment: {
...commonTransaction(),
amountEffective: 'USD:11',
type: TransactionType.Payment,
info: {
contractTermsHash: 'ASDZXCASD',
merchant: {
name: 'the merchant',
},
orderId: '2021.167-03NPY6MCYMVGT',
products: [],
summary: 'the summary',
fulfillmentMessage: '',
},
proposalId: '1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0',
status: PaymentStatus.Accepted,
} as TransactionPayment,
deposit: {
...commonTransaction(),
type: TransactionType.Deposit,
depositGroupId: '#groupId',
targetPaytoUri: 'payto://x-taler-bank/bank/account',
} as TransactionDeposit,
refresh: {
...commonTransaction(),
type: TransactionType.Refresh,
exchangeBaseUrl: 'http://exchange.taler',
} as TransactionRefresh,
tip: {
...commonTransaction(),
type: TransactionType.Tip,
merchantBaseUrl: 'http://merchant.taler',
} as TransactionTip,
refund: {
...commonTransaction(),
type: TransactionType.Refund,
refundedTransactionId: 'payment:1EMJJH8EP1NX3XF7733NCYS2DBEJW4Q2KA5KEB37MCQJQ8Q5HMC0',
info: {
contractTermsHash: 'ASDZXCASD',
merchant: {
name: 'the merchant',
},
orderId: '2021.167-03NPY6MCYMVGT',
products: [],
summary: 'the summary',
fulfillmentMessage: '',
},
} as TransactionRefund,
}
function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
const r = (args: any) => <Component {...args} />
r.args = props
return r
}
export const Empty = createExample(TestedComponent, {
list: [],
balances: [{
available: 'TESTKUDOS:10',
pendingIncoming: 'TESTKUDOS:0',
pendingOutgoing: 'TESTKUDOS:0',
hasPendingTransactions: false,
requiresUserInput: false,
}]
});
export const One = createExample(TestedComponent, {
list: [exampleData.withdraw],
balances: [{
available: 'USD:10',
pendingIncoming: 'USD:0',
pendingOutgoing: 'USD:0',
hasPendingTransactions: false,
requiresUserInput: false,
}]
});
export const Several = createExample(TestedComponent, {
list: [
exampleData.withdraw,
exampleData.payment,
exampleData.withdraw,
exampleData.payment,
exampleData.refresh,
exampleData.refund,
exampleData.tip,
exampleData.deposit,
],
balances: [{
available: 'TESTKUDOS:10',
pendingIncoming: 'TESTKUDOS:0',
pendingOutgoing: 'TESTKUDOS:0',
hasPendingTransactions: false,
requiresUserInput: false,
}]
});
export const SeveralWithTwoCurrencies = createExample(TestedComponent, {
list: [
exampleData.withdraw,
exampleData.payment,
exampleData.withdraw,
exampleData.payment,
exampleData.refresh,
exampleData.refund,
exampleData.tip,
exampleData.deposit,
],
balances: [{
available: 'TESTKUDOS:10',
pendingIncoming: 'TESTKUDOS:0',
pendingOutgoing: 'TESTKUDOS:0',
hasPendingTransactions: false,
requiresUserInput: false,
},{
available: 'USD:10',
pendingIncoming: 'USD:0',
pendingOutgoing: 'USD:0',
hasPendingTransactions: false,
requiresUserInput: false,
}]
});
// export const WithdrawPending = createExample(TestedComponent, {
// transaction: { ...exampleData.withdraw, pending: true },
// });
// export const Payment = createExample(TestedComponent, {
// transaction: exampleData.payment
// });
// export const PaymentWithoutFee = createExample(TestedComponent, {
// transaction: {
// ...exampleData.payment,
// amountRaw: 'USD:11',
// }
// });
// export const PaymentPending = createExample(TestedComponent, {
// transaction: { ...exampleData.payment, pending: true },
// });
// export const PaymentWithProducts = createExample(TestedComponent, {
// transaction: {
// ...exampleData.payment,
// info: {
// ...exampleData.payment.info,
// summary: 'this order has 5 products',
// products: [{
// description: 't-shirt',
// unit: 'shirts',
// quantity: 1,
// }, {
// description: 't-shirt',
// unit: 'shirts',
// quantity: 1,
// }, {
// description: 'e-book',
// }, {
// description: 'beer',
// unit: 'pint',
// quantity: 15,
// }, {
// description: 'beer',
// unit: 'pint',
// quantity: 15,
// }]
// }
// } as TransactionPayment,
// });
// export const PaymentWithLongSummary = createExample(TestedComponent, {
// transaction: {
// ...exampleData.payment,
// info: {
// ...exampleData.payment.info,
// summary: 'this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, this is a very long summary that will occupy severals lines, ',
// products: [{
// description: 'an xl sized t-shirt with some drawings on it, color pink',
// unit: 'shirts',
// quantity: 1,
// }, {
// description: 'beer',
// unit: 'pint',
// quantity: 15,
// }]
// }
// } as TransactionPayment,
// });
// export const Deposit = createExample(TestedComponent, {
// transaction: exampleData.deposit
// });
// export const DepositPending = createExample(TestedComponent, {
// transaction: { ...exampleData.deposit, pending: true }
// });
// export const Refresh = createExample(TestedComponent, {
// transaction: exampleData.refresh
// });
// export const Tip = createExample(TestedComponent, {
// transaction: exampleData.tip
// });
// export const TipPending = createExample(TestedComponent, {
// transaction: { ...exampleData.tip, pending: true }
// });
// export const Refund = createExample(TestedComponent, {
// transaction: exampleData.refund
// });
// export const RefundPending = createExample(TestedComponent, {
// transaction: { ...exampleData.refund, pending: true }
// });
// export const RefundWithProducts = createExample(TestedComponent, {
// transaction: {
// ...exampleData.refund,
// info: {
// ...exampleData.refund.info,
// products: [{
// description: 't-shirt',
// }, {
// description: 'beer',
// }]
// }
// } as TransactionRefund,
// });

View File

@ -0,0 +1,248 @@
/*
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 { AmountString, Balance, Timestamp, Transaction, TransactionsResponse, TransactionType } from "@gnu-taler/taler-util";
import { format } from "date-fns";
import { Fragment, JSX } from "preact";
import { useEffect, useState } from "preact/hooks";
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';
import imageRefund from '../../static/img/ri-refund-2-line.svg';
import imageShoppingCart from '../../static/img/ri-shopping-cart-line.svg';
import { Column, ExtraLargeText, HistoryRow, WalletBox, DateSeparator, SmallTextLight } from "../components/styled";
import { useBalances } from "../hooks/useBalances";
import * as wxApi from "../wxApi";
import { Pages } from "../NavigationBar";
export function HistoryPage(props: any): JSX.Element {
const [transactions, setTransactions] = useState<
TransactionsResponse | undefined
>(undefined);
const balance = useBalances()
const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || [])
useEffect(() => {
const fetchData = async (): Promise<void> => {
const res = await wxApi.getTransactions();
setTransactions(res);
};
fetchData();
}, []);
if (!transactions) {
return <div>Loading ...</div>;
}
return <HistoryView balances={balanceWithoutError} list={[...transactions.transactions].reverse()} />;
}
function amountToString(c: AmountString) {
const idx = c.indexOf(':')
return `${c.substring(idx + 1)} ${c.substring(0, idx)}`
}
export function HistoryView({ list, balances }: { list: Transaction[], balances: Balance[] }) {
const byDate = list.reduce(function (rv, x) {
const theDate = x.timestamp.t_ms === "never" ? "never" : format(x.timestamp.t_ms, 'dd MMMM yyyy');
(rv[theDate] = rv[theDate] || []).push(x);
return rv;
}, {} as { [x: string]: Transaction[] });
return <WalletBox noPadding>
{balances.length > 0 && <header>
{balances.length === 1 && <div class="title">
Balance: <span>{amountToString(balances[0].available)}</span>
</div>}
{balances.length > 1 && <div class="title">
Balance: <ul style={{ margin: 0 }}>
{balances.map(b => <li>{b.available}</li>)}
</ul>
</div>}
</header>}
<section>
{Object.keys(byDate).map(d => {
return <Fragment>
<DateSeparator>{d}</DateSeparator>
{byDate[d].map((tx, i) => (
<TransactionItem key={i} tx={tx} />
))}
</Fragment>
})}
</section>
</WalletBox>
}
function TransactionItem(props: { tx: Transaction }): JSX.Element {
const tx = props.tx;
switch (tx.type) {
case TransactionType.Withdrawal:
return (
<TransactionLayout
id={tx.transactionId}
amount={tx.amountEffective}
debitCreditIndicator={"credit"}
title="Withdrawal"
subtitle={`via ${tx.exchangeBaseUrl}`}
timestamp={tx.timestamp}
iconPath={imageBank}
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Payment:
return (
<TransactionLayout
id={tx.transactionId}
amount={tx.amountEffective}
debitCreditIndicator={"debit"}
title="Payment"
subtitle={tx.info.summary}
timestamp={tx.timestamp}
iconPath={imageShoppingCart}
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Refund:
return (
<TransactionLayout
id={tx.transactionId}
amount={tx.amountEffective}
debitCreditIndicator={"credit"}
title="Refund"
subtitle={tx.info.summary}
timestamp={tx.timestamp}
iconPath={imageRefund}
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Tip:
return (
<TransactionLayout
id={tx.transactionId}
amount={tx.amountEffective}
debitCreditIndicator={"credit"}
title="Tip"
subtitle={`from ${new URL(tx.merchantBaseUrl).hostname}`}
timestamp={tx.timestamp}
iconPath={imageHandHeart}
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Refresh:
return (
<TransactionLayout
id={tx.transactionId}
amount={tx.amountEffective}
debitCreditIndicator={"credit"}
title="Refresh"
subtitle={`via exchange ${tx.exchangeBaseUrl}`}
timestamp={tx.timestamp}
iconPath={imageRefresh}
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Deposit:
return (
<TransactionLayout
id={tx.transactionId}
amount={tx.amountEffective}
debitCreditIndicator={"debit"}
title="Refresh"
subtitle={`to ${tx.targetPaytoUri}`}
timestamp={tx.timestamp}
iconPath={imageRefresh}
pending={tx.pending}
></TransactionLayout>
);
}
}
function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
const date = new Date(props.timestamp.t_ms);
const dateStr = format(date, 'HH:mm:ss')
return (
// <a href={Pages.transaction.replace(':tid', props.id)}>
<HistoryRow href={Pages.transaction.replace(':tid', props.id)}>
<img src={props.iconPath} />
<Column>
<ExtraLargeText>
<span>{props.title}</span>
{props.pending ? (
<span style={{ color: "darkblue" }}> (Pending)</span>
) : null}
</ExtraLargeText>
<SmallTextLight>{dateStr}</SmallTextLight>
</Column>
<TransactionAmount
pending={props.pending}
amount={props.amount}
debitCreditIndicator={props.debitCreditIndicator}
/>
</HistoryRow>
// </a>
);
}
interface TransactionLayoutProps {
debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountString | "unknown";
timestamp: Timestamp;
title: string;
id: string;
subtitle: string;
iconPath: string;
pending: boolean;
}
interface TransactionAmountProps {
debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountString | "unknown";
pending: boolean;
}
function TransactionAmount(props: TransactionAmountProps): JSX.Element {
const [currency, amount] = props.amount.split(":");
let sign: string;
switch (props.debitCreditIndicator) {
case "credit":
sign = "+";
break;
case "debit":
sign = "-";
break;
case "unknown":
sign = "";
}
return (
<Column style={{
color:
props.pending ? "gray" :
(sign === '+' ? 'darkgreen' :
(sign === '-' ? 'darkred' :
undefined))
}}>
<ExtraLargeText>
{sign}
{amount}
</ExtraLargeText>
<div>{currency}</div>
</Column>
);
}

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { FunctionalComponent, h } from 'preact'; import { createExample } from '../test-utils';
import { View as TestedComponent } from './Welcome'; import { View as TestedComponent } from './Welcome';
@ -28,12 +28,6 @@ export default {
component: TestedComponent, component: TestedComponent,
}; };
function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) {
const r = (args: any) => <Component {...args} />
r.args = props
return r
}
export const Normal = createExample(TestedComponent, { export const Normal = createExample(TestedComponent, {
permissionsEnabled: true, permissionsEnabled: true,
diagnostics: { diagnostics: {

View File

@ -24,7 +24,7 @@ import { JSX } from "preact/jsx-runtime";
import { Checkbox } from "../components/Checkbox"; import { Checkbox } from "../components/Checkbox";
import { useExtendedPermissions } from "../hooks/useExtendedPermissions"; import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
import { Diagnostics } from "../components/Diagnostics"; import { Diagnostics } from "../components/Diagnostics";
import { WalletPage } from "../components/styled"; import { WalletBox } from "../components/styled";
import { useDiagnostics } from "../hooks/useDiagnostics"; import { useDiagnostics } from "../hooks/useDiagnostics";
import { WalletDiagnostics } from "@gnu-taler/taler-util"; import { WalletDiagnostics } from "@gnu-taler/taler-util";
@ -44,12 +44,7 @@ export interface ViewProps {
timedOut: boolean, timedOut: boolean,
} }
export function View({ permissionsEnabled, togglePermissions, diagnostics, timedOut }: ViewProps): JSX.Element { export function View({ permissionsEnabled, togglePermissions, diagnostics, timedOut }: ViewProps): JSX.Element {
return (<WalletPage> return (<WalletBox>
<div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
<h1 style="font-family: monospace; font-size: 250%;">
<span style="color: #aa3939;"></span>Taler Wallet<span style="color: #aa3939;"></span>
</h1>
</div>
<h1>Browser Extension Installed!</h1> <h1>Browser Extension Installed!</h1>
<div> <div>
<p>Thank you for installing the wallet.</p> <p>Thank you for installing the wallet.</p>
@ -68,6 +63,6 @@ export function View({ permissionsEnabled, togglePermissions, diagnostics, timed
Learn how to top up your wallet balance » Learn how to top up your wallet balance »
</a> </a>
</div> </div>
</WalletPage> </WalletBox>
); );
} }

View File

@ -20,17 +20,24 @@
* @author Florian Dold <dold@taler.net> * @author Florian Dold <dold@taler.net>
*/ */
import { render } from "preact"; import { Fragment, render } from "preact";
import { setupI18n } from "@gnu-taler/taler-util"; import { setupI18n } from "@gnu-taler/taler-util";
import { strings } from "./i18n/strings"; import { strings } from "./i18n/strings";
import { createHashHistory } from 'history'; import { createHashHistory } from 'history';
import { WithdrawPage } from "./wallet/Withdraw";
import { WelcomePage } from "./wallet/Welcome"; import { WelcomePage } from "./wallet/Welcome";
import { PayPage } from "./wallet/Pay"; import { HistoryPage } from "./wallet/History";
import { RefundPage } from "./wallet/Refund"; import { WithdrawPage } from "./cta/Withdraw";
import { TipPage } from './wallet/Tip'; import { PayPage } from "./cta/Pay";
import { RefundPage } from "./cta/Refund";
import { TipPage } from './cta/Tip';
import Router, { route, Route } from "preact-router"; import Router, { route, Route } from "preact-router";
import { DevContextProvider } from "./context/devContext";
import { LogoHeader } from "./components/LogoHeader";
import { useEffect } from "preact/hooks";
import {
Pages, WalletNavBar
} from "./NavigationBar";
function main(): void { function main(): void {
try { try {
@ -53,32 +60,43 @@ if (document.readyState === "loading") {
main(); main();
} }
function withLogoAndNavBar(Component: any) {
enum Pages { return () => <Fragment>
welcome = '/welcome', <LogoHeader />
pay = '/pay', <WalletNavBar />
payback = '/payback', <Component />
refund = '/refund', </Fragment>
reset_required = '/reset-required',
return_coins = '/return-coins',
tips = '/tips',
withdraw = '/withdraw',
} }
function Application() { function Application() {
const h = createHashHistory(); return <div>
return <Router history={h} > <DevContextProvider>
<Router history={createHashHistory()} >
<Route path={Pages.welcome} component={WelcomePage} /> <Route path={Pages.welcome} component={withLogoAndNavBar(WelcomePage)} />
<Route path={Pages.pay} component={PayPage} />
<Route path={Pages.refund} component={RefundPage} />
<Route path={Pages.tips} component={TipPage} /> <Route path={Pages.history} component={withLogoAndNavBar(HistoryPage)} />
<Route path={Pages.withdraw} component={WithdrawPage} /> <Route path={Pages.transaction} component={withLogoAndNavBar(HistoryPage)} />
<Route path={Pages.reset_required} component={() => <div>no yet implemented</div>} /> <Route path={Pages.reset_required} component={() => <div>no yet implemented</div>} />
<Route path={Pages.payback} component={() => <div>no yet implemented</div>} /> <Route path={Pages.payback} component={() => <div>no yet implemented</div>} />
<Route path={Pages.return_coins} component={() => <div>no yet implemented</div>} /> <Route path={Pages.return_coins} component={() => <div>no yet implemented</div>} />
</Router> {/** call to action */}
<Route path={Pages.pay} component={PayPage} />
<Route path={Pages.refund} component={RefundPage} />
<Route path={Pages.tips} component={TipPage} />
<Route path={Pages.withdraw} component={WithdrawPage} />
<Route default component={Redirect} to={Pages.history} />
</Router>
</DevContextProvider>
</div>
}
function Redirect({ to }: { to: string }): null {
useEffect(() => {
route(to, true)
})
return null
} }

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" width="670" height="300" viewBox="0 0 201 90"><g fill="#0042b3" fill-rule="evenodd" stroke-width=".3"><path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z"/><path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z"/><path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z"/></g><path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -2,6 +2,7 @@ body {
font-size: 100%; font-size: 100%;
overflow-y: scroll; overflow-y: scroll;
margin-top: 2em; margin-top: 2em;
font-family: Arial, Helvetica, sans-serif;
} }
.wallet-container { .wallet-container {