new wallet history and view refactoring
This commit is contained in:
parent
b015f76e72
commit
97a05ff659
@ -15,7 +15,8 @@
|
||||
*/
|
||||
|
||||
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'
|
||||
|
||||
export const parameters = {
|
||||
@ -43,7 +44,7 @@ export const globalTypes = {
|
||||
export const decorators = [
|
||||
(Story, { kind }) => {
|
||||
if (kind.startsWith('popup')) {
|
||||
|
||||
|
||||
function Body() {
|
||||
const isTestingHeader = (/.*\/header\/?.*/.test(kind));
|
||||
if (isTestingHeader) {
|
||||
@ -51,16 +52,16 @@ export const decorators = [
|
||||
return <div style={{ width: 400, height: 320 }}>
|
||||
<Story />
|
||||
</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">
|
||||
@ -95,11 +96,71 @@ export const decorators = [
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
if (kind.startsWith('wallet')) {
|
||||
return <div class="wallet-container">
|
||||
if (kind.startsWith('cta')) {
|
||||
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="2" rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<Story />
|
||||
</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>
|
||||
|
@ -12,7 +12,7 @@
|
||||
"test": "jest ./tests",
|
||||
"compile": "tsc && rollup -c",
|
||||
"build-storybook": "build-storybook",
|
||||
"storybook": "start-storybook -s static -p 6006",
|
||||
"storybook": "start-storybook -s . -p 6006",
|
||||
"watch": "tsc --watch & rollup -w -c"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -1,14 +1,13 @@
|
||||
// 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 css from 'rollup-plugin-css-only';
|
||||
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 = () => [
|
||||
alias({
|
||||
|
@ -27,10 +27,11 @@
|
||||
import { i18n } from "@gnu-taler/taler-util";
|
||||
import { ComponentChildren, JSX } from "preact";
|
||||
import Match from "preact-router/match";
|
||||
import { useDevContext } from "../context/devContext";
|
||||
import { PopupNavigation } from '../components/styled'
|
||||
import { useDevContext } from "./context/devContext";
|
||||
import { PopupNavigation } from './components/styled'
|
||||
|
||||
export enum Pages {
|
||||
welcome = '/welcome',
|
||||
balance = '/balance',
|
||||
settings = '/settings',
|
||||
dev = '/dev',
|
||||
@ -39,6 +40,15 @@ export enum Pages {
|
||||
transaction = '/transaction/:tid',
|
||||
provider_detail = '/provider/:pid',
|
||||
provider_add = '/provider/add',
|
||||
|
||||
reset_required = '/reset-required',
|
||||
payback = '/payback',
|
||||
return_coins = '/return-coins',
|
||||
|
||||
pay = '/pay',
|
||||
refund = '/refund',
|
||||
tips = '/tips',
|
||||
withdraw = '/withdraw',
|
||||
}
|
||||
|
||||
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}>
|
||||
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
|
||||
<Tab target="/history" current={path}>{i18n.str`History`}</Tab>
|
||||
<Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
|
||||
<Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab>
|
||||
{devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>}
|
||||
<div>
|
||||
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
|
||||
<Tab target="/history" current={path}>{i18n.str`History`}</Tab>
|
||||
<Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
|
||||
<Tab target="/settings" current={path}>{i18n.str`Settings`}</Tab>
|
||||
{devMode && <Tab target="/dev" current={path}>{i18n.str`Dev`}</Tab>}
|
||||
</div>
|
||||
</PopupNavigation>
|
||||
}
|
||||
|
||||
export function WalletNavBar() {
|
||||
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>
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
}
|
@ -11,7 +11,7 @@ export const PaymentStatus = styled.div<{ color: string }>`
|
||||
background-color: ${p => p.color};
|
||||
`
|
||||
|
||||
export const WalletPage = styled.section`
|
||||
export const WalletAction = styled.section`
|
||||
border: solid 5px black;
|
||||
border-radius: 10px;
|
||||
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 }>`
|
||||
height: 290px;
|
||||
width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
@ -194,10 +259,32 @@ export const RowBorderGray = styled(Row)`
|
||||
|
||||
export const RowLightBorderGray = styled(Row2)`
|
||||
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 {
|
||||
margin-left: auto;
|
||||
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;
|
||||
height: 35px;
|
||||
|
||||
& > a {
|
||||
justify-content: space-around;
|
||||
display: flex;
|
||||
|
||||
& > div {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
& > div > a {
|
||||
color: #f8faf7;
|
||||
display: inline-block;
|
||||
width: calc(400px / ${({ devMode }) => !devMode ? 4 : 5});
|
||||
@ -298,7 +391,7 @@ export const PopupNavigation = styled.div<{devMode?:boolean}>`
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
& > a.active {
|
||||
& > div > a.active {
|
||||
background-color: #f8faf7;
|
||||
color: #0042b2;
|
||||
font-weight: bold;
|
||||
|
@ -20,23 +20,16 @@
|
||||
*/
|
||||
|
||||
import { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util';
|
||||
import { FunctionalComponent, h } from 'preact';
|
||||
import { createExample } from '../test-utils';
|
||||
import { PaymentRequestView as TestedComponent } from './Pay';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'wallet/pay',
|
||||
title: 'cta/pay',
|
||||
component: TestedComponent,
|
||||
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, {
|
||||
payStatus: {
|
||||
status: PreparePayResultType.InsufficientBalance,
|
@ -19,24 +19,18 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { ContractTerms, OrderShortInfo, PreparePayResultType } from '@gnu-taler/taler-util';
|
||||
import { FunctionalComponent, h } from 'preact';
|
||||
import { OrderShortInfo } from '@gnu-taler/taler-util';
|
||||
import { createExample } from '../test-utils';
|
||||
import { View as TestedComponent } from './Refund';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'wallet/refund',
|
||||
title: 'cta/refund',
|
||||
component: TestedComponent,
|
||||
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, {
|
||||
applyResult: {
|
||||
amountEffectivePaid: 'USD:10',
|
@ -19,24 +19,17 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util';
|
||||
import { FunctionalComponent, h } from 'preact';
|
||||
import { createExample } from '../test-utils';
|
||||
import { View as TestedComponent } from './Tip';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'wallet/tip',
|
||||
title: 'cta/tip',
|
||||
component: TestedComponent,
|
||||
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, {
|
||||
prepareTipResult: {
|
||||
accepted: true,
|
@ -19,32 +19,27 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { h } from 'preact';
|
||||
import { View, ViewProps } from './Withdraw';
|
||||
import { createExample } from '../test-utils';
|
||||
import { View as TestedComponent } from './Withdraw';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'wallet/withdraw',
|
||||
component: View,
|
||||
title: 'cta/withdraw',
|
||||
component: TestedComponent,
|
||||
argTypes: {
|
||||
},
|
||||
};
|
||||
|
||||
export const WithoutDetails = (a: any) => <View {...a} />;
|
||||
WithoutDetails.args = {
|
||||
} as ViewProps
|
||||
|
||||
export const CompleteWithExchange = (a: any) => <View {...a} />;
|
||||
CompleteWithExchange.args = {
|
||||
export const CompleteWithExchange = createExample(TestedComponent, {
|
||||
details: {
|
||||
amount: 'USD:2',
|
||||
possibleExchanges: [],
|
||||
},
|
||||
selectedExchange: 'Some exchange'
|
||||
} as ViewProps
|
||||
|
||||
export const CompleteWithoutExchange = (a: any) => <View {...a} />;
|
||||
CompleteWithoutExchange.args = {
|
||||
})
|
||||
export const CompleteWithoutExchange = createExample(TestedComponent, {
|
||||
details: {
|
||||
amount: 'USD:2',
|
||||
possibleExchanges: [],
|
||||
},
|
||||
} as ViewProps
|
||||
})
|
@ -32,14 +32,13 @@ import {
|
||||
} from "../wxApi";
|
||||
import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
|
||||
import { JSX } from "preact/jsx-runtime";
|
||||
import { WalletPage } from '../components/styled';
|
||||
import { WalletAction } from '../components/styled';
|
||||
|
||||
interface Props {
|
||||
talerWithdrawUri?: string;
|
||||
}
|
||||
|
||||
export interface ViewProps {
|
||||
talerWithdrawUri?: string;
|
||||
details: WithdrawUriInfoResponse;
|
||||
selectedExchange?: string;
|
||||
accept: () => Promise<void>;
|
||||
@ -50,7 +49,7 @@ export interface ViewProps {
|
||||
export function View({ details, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) {
|
||||
|
||||
return (
|
||||
<WalletPage>
|
||||
<WalletAction>
|
||||
<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>
|
||||
@ -101,26 +100,19 @@ export function View({ details, selectedExchange, accept, setCancelled, setSelec
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</WalletPage>
|
||||
</WalletAction>
|
||||
)
|
||||
}
|
||||
|
||||
export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element {
|
||||
const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined);
|
||||
const [selectedExchange, setSelectedExchange] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const [selectedExchange, setSelectedExchange] = useState<string | undefined>(undefined);
|
||||
const [cancelled, setCancelled] = 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 [state, setState] = useState(1)
|
||||
|
||||
// setTimeout(() => {
|
||||
// console.log('tick...')
|
||||
// setState(s => s + 1)
|
||||
// }, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
return onUpdateNotification(() => {
|
||||
console.log('updating...')
|
||||
@ -132,20 +124,19 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
||||
console.log('on effect yes', talerWithdrawUri)
|
||||
if (!talerWithdrawUri) return
|
||||
const fetchData = async (): Promise<void> => {
|
||||
console.log('que pasa')
|
||||
try {
|
||||
const res = await getWithdrawalDetailsForUri({ talerWithdrawUri });
|
||||
console.log('res', res)
|
||||
setDetails(res);
|
||||
if (res.defaultExchangeBaseUrl) {
|
||||
setSelectedExchange(res.defaultExchangeBaseUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error('error',JSON.stringify(e,undefined,2))
|
||||
setError(true)
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter, state]);
|
||||
}, [selectedExchange, selecting, talerWithdrawUri, updateCounter, state]);
|
||||
|
||||
if (!talerWithdrawUri) {
|
||||
return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
|
||||
@ -169,6 +160,9 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
|
||||
if (cancelled) {
|
||||
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}
|
||||
setCancelled={setCancelled} setSelecting={setSelecting}
|
@ -25,7 +25,7 @@ import {
|
||||
SmallText, SmallTextLight
|
||||
} from "../components/styled";
|
||||
import { useBackupStatus } from "../hooks/useBackupStatus";
|
||||
import { Pages } from "./popup";
|
||||
import { Pages } from "../NavigationBar";
|
||||
|
||||
interface Props {
|
||||
onAddProvider: () => void;
|
||||
|
@ -14,11 +14,19 @@
|
||||
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 { 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 { Pages } from "./popup";
|
||||
import { Pages } from "../NavigationBar";
|
||||
|
||||
|
||||
export function HistoryPage(props: any): JSX.Element {
|
||||
@ -45,7 +53,7 @@ export function HistoryPage(props: any): JSX.Element {
|
||||
|
||||
function amountToString(c: AmountString) {
|
||||
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>
|
||||
<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>
|
||||
</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 {
|
||||
const tx = props.tx;
|
||||
switch (tx.type) {
|
||||
@ -171,18 +173,16 @@ function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
|
||||
const now = new Date();
|
||||
const dateStr = formatDistance(date, now, { addSuffix: true })
|
||||
return (
|
||||
<HistoryRow>
|
||||
<HistoryRow href={Pages.transaction.replace(':tid', props.id)}>
|
||||
<img src={props.iconPath} />
|
||||
<Column>
|
||||
<ExtraLargeText>
|
||||
<a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a>
|
||||
<span>{props.title}</span>
|
||||
{props.pending ? (
|
||||
<span style={{ color: "darkblue" }}> (Pending)</span>
|
||||
) : null}
|
||||
</ExtraLargeText>
|
||||
<SmallTextLight>{dateStr}</SmallTextLight>
|
||||
|
||||
{/* <div>{props.subtitle}</div> */}
|
||||
</Column>
|
||||
<TransactionAmount
|
||||
pending={props.pending}
|
||||
|
@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
import { Fragment, FunctionalComponent } from 'preact';
|
||||
import { NavBar as TestedComponent } from './popup';
|
||||
import { NavBar as TestedComponent } from '../NavigationBar';
|
||||
|
||||
export default {
|
||||
title: 'popup/header',
|
||||
|
@ -20,7 +20,7 @@ import { Fragment, JSX, VNode } from "preact";
|
||||
import { route } from 'preact-router';
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import * as wxApi from "../wxApi";
|
||||
import { Pages } from "./popup";
|
||||
import { Pages } from "../NavigationBar";
|
||||
import emptyImg from "../../static/img/empty.png"
|
||||
import { Button, ButtonDestructive, ButtonPrimary, ListOfProducts, PopupBox, Row, RowBorderGray, SmallTextLight } from "../components/styled";
|
||||
import { ErrorMessage } from "../components/ErrorMessage";
|
||||
|
@ -34,7 +34,7 @@ import { DeveloperPage as DeveloperPage } from "./popup/Debug";
|
||||
import { HistoryPage } from "./popup/History";
|
||||
import {
|
||||
Pages, WalletNavBar
|
||||
} from "./popup/popup";
|
||||
} from "./NavigationBar";
|
||||
import { ProviderAddPage } from "./popup/ProviderAddPage";
|
||||
import { ProviderDetailPage } from "./popup/ProviderDetailPage";
|
||||
import { SettingsPage } from "./popup/Settings";
|
||||
@ -129,4 +129,4 @@ function Redirect({ to }: { to: string }): null {
|
||||
route(to, true)
|
||||
})
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
8
packages/taler-wallet-webextension/src/test-utils.ts
Normal file
8
packages/taler-wallet-webextension/src/test-utils.ts
Normal 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
|
||||
}
|
||||
|
@ -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,
|
||||
// });
|
248
packages/taler-wallet-webextension/src/wallet/History.tsx
Normal file
248
packages/taler-wallet-webextension/src/wallet/History.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import { FunctionalComponent, h } from 'preact';
|
||||
import { createExample } from '../test-utils';
|
||||
import { View as TestedComponent } from './Welcome';
|
||||
|
||||
|
||||
@ -28,12 +28,6 @@ export default {
|
||||
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, {
|
||||
permissionsEnabled: true,
|
||||
diagnostics: {
|
||||
|
@ -24,7 +24,7 @@ import { JSX } from "preact/jsx-runtime";
|
||||
import { Checkbox } from "../components/Checkbox";
|
||||
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
|
||||
import { Diagnostics } from "../components/Diagnostics";
|
||||
import { WalletPage } from "../components/styled";
|
||||
import { WalletBox } from "../components/styled";
|
||||
import { useDiagnostics } from "../hooks/useDiagnostics";
|
||||
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
||||
|
||||
@ -44,12 +44,7 @@ export interface ViewProps {
|
||||
timedOut: boolean,
|
||||
}
|
||||
export function View({ permissionsEnabled, togglePermissions, diagnostics, timedOut }: ViewProps): JSX.Element {
|
||||
return (<WalletPage>
|
||||
<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>
|
||||
return (<WalletBox>
|
||||
<h1>Browser Extension Installed!</h1>
|
||||
<div>
|
||||
<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 »
|
||||
</a>
|
||||
</div>
|
||||
</WalletPage>
|
||||
</WalletBox>
|
||||
);
|
||||
}
|
||||
|
@ -20,17 +20,24 @@
|
||||
* @author Florian Dold <dold@taler.net>
|
||||
*/
|
||||
|
||||
import { render } from "preact";
|
||||
import { Fragment, render } from "preact";
|
||||
import { setupI18n } from "@gnu-taler/taler-util";
|
||||
import { strings } from "./i18n/strings";
|
||||
import { createHashHistory } from 'history';
|
||||
|
||||
import { WithdrawPage } from "./wallet/Withdraw";
|
||||
import { WelcomePage } from "./wallet/Welcome";
|
||||
import { PayPage } from "./wallet/Pay";
|
||||
import { RefundPage } from "./wallet/Refund";
|
||||
import { TipPage } from './wallet/Tip';
|
||||
import { HistoryPage } from "./wallet/History";
|
||||
import { WithdrawPage } from "./cta/Withdraw";
|
||||
import { PayPage } from "./cta/Pay";
|
||||
import { RefundPage } from "./cta/Refund";
|
||||
import { TipPage } from './cta/Tip';
|
||||
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 {
|
||||
try {
|
||||
@ -53,32 +60,43 @@ if (document.readyState === "loading") {
|
||||
main();
|
||||
}
|
||||
|
||||
|
||||
enum Pages {
|
||||
welcome = '/welcome',
|
||||
pay = '/pay',
|
||||
payback = '/payback',
|
||||
refund = '/refund',
|
||||
reset_required = '/reset-required',
|
||||
return_coins = '/return-coins',
|
||||
tips = '/tips',
|
||||
withdraw = '/withdraw',
|
||||
function withLogoAndNavBar(Component: any) {
|
||||
return () => <Fragment>
|
||||
<LogoHeader />
|
||||
<WalletNavBar />
|
||||
<Component />
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
function Application() {
|
||||
const h = createHashHistory();
|
||||
return <Router history={h} >
|
||||
return <div>
|
||||
<DevContextProvider>
|
||||
<Router history={createHashHistory()} >
|
||||
|
||||
<Route path={Pages.welcome} component={WelcomePage} />
|
||||
<Route path={Pages.pay} component={PayPage} />
|
||||
<Route path={Pages.refund} component={RefundPage} />
|
||||
<Route path={Pages.welcome} component={withLogoAndNavBar(WelcomePage)} />
|
||||
|
||||
<Route path={Pages.tips} component={TipPage} />
|
||||
<Route path={Pages.withdraw} component={WithdrawPage} />
|
||||
<Route path={Pages.history} component={withLogoAndNavBar(HistoryPage)} />
|
||||
<Route path={Pages.transaction} component={withLogoAndNavBar(HistoryPage)} />
|
||||
|
||||
<Route path={Pages.reset_required} 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.reset_required} 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>} />
|
||||
|
||||
</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
|
||||
}
|
||||
|
@ -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 |
@ -2,6 +2,7 @@ body {
|
||||
font-size: 100%;
|
||||
overflow-y: scroll;
|
||||
margin-top: 2em;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.wallet-container {
|
||||
|
Loading…
Reference in New Issue
Block a user