new wallet UI and more tests
This commit is contained in:
parent
eb553be841
commit
e9bb85a212
@ -56,7 +56,7 @@ export const decorators = [
|
|||||||
// add a fake header so it looks similar
|
// add a fake header so it looks similar
|
||||||
return <Fragment>
|
return <Fragment>
|
||||||
<NavBar path={path} devMode={path === '/dev'} />
|
<NavBar path={path} devMode={path === '/dev'} />
|
||||||
<div style={{ padding: 8, width: 'calc(400px - 16px)', height: 'calc(320px - 34px - 16px)' }}>
|
<div style={{ width: 400, height: 290 }}>
|
||||||
<Story />
|
<Story />
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -68,6 +68,6 @@ worker.onmessage = (msg: MessageEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleRequest(operation, id, args).catch((e) => {
|
handleRequest(operation, id, args).catch((e) => {
|
||||||
console.error("error in browsere worker", e);
|
console.error("error in browser worker", e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,30 +4,12 @@ import { PageLink } from "../renderHtml";
|
|||||||
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
||||||
import { JSX } from "preact/jsx-runtime";
|
import { JSX } from "preact/jsx-runtime";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
timedOut: boolean;
|
||||||
|
diagnostics: WalletDiagnostics | undefined
|
||||||
|
}
|
||||||
|
|
||||||
export function Diagnostics(): JSX.Element | null {
|
export function Diagnostics({timedOut, diagnostics}: Props): JSX.Element | null {
|
||||||
const [timedOut, setTimedOut] = useState(false);
|
|
||||||
const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | undefined>(
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let gotDiagnostics = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!gotDiagnostics) {
|
|
||||||
console.error("timed out");
|
|
||||||
setTimedOut(true);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
const doFetch = async (): Promise<void> => {
|
|
||||||
const d = await getDiagnostics();
|
|
||||||
console.log("got diagnostics", d);
|
|
||||||
gotDiagnostics = true;
|
|
||||||
setDiagnostics(d);
|
|
||||||
};
|
|
||||||
console.log("fetching diagnostics");
|
|
||||||
doFetch();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (timedOut) {
|
if (timedOut) {
|
||||||
return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>;
|
return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>;
|
||||||
|
@ -11,13 +11,32 @@ export const PaymentStatus = styled.div<{ color: string }>`
|
|||||||
background-color: ${p => p.color};
|
background-color: ${p => p.color};
|
||||||
`
|
`
|
||||||
|
|
||||||
export const PopupBox = styled.div`
|
export const WalletPage = styled.section`
|
||||||
height: calc(320px - 34px - 16px);
|
border: solid 5px black;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding-top: 2em;
|
||||||
|
max-width: 50%;
|
||||||
|
padding: 2em;
|
||||||
|
|
||||||
|
margin: auto;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
& h1:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const PopupBox = styled.div<{ noPadding?: boolean }>`
|
||||||
|
height: 290px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
& > section {
|
& > 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
|
// this margin will send the section up when used with a header
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@ -35,6 +54,7 @@ export const PopupBox = styled.div`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding: 8px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
@ -44,15 +64,23 @@ export const PopupBox = styled.div`
|
|||||||
& > h3 {
|
& > h3 {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > .title {
|
||||||
|
/* margin: 1em; */
|
||||||
|
font-size: large;
|
||||||
|
color: #3c4e92;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > footer {
|
& > footer {
|
||||||
padding-top: 5px;
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
& button {
|
& button {
|
||||||
margin-left: 5px;
|
margin-right: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +173,13 @@ export const Row = styled.div`
|
|||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const Row2 = styled.div`
|
||||||
|
display: flex;
|
||||||
|
/* margin: 0.5em 0; */
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.5em;
|
||||||
|
`
|
||||||
|
|
||||||
export const Column = styled.div`
|
export const Column = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -154,10 +189,15 @@ export const Column = styled.div`
|
|||||||
|
|
||||||
export const RowBorderGray = styled(Row)`
|
export const RowBorderGray = styled(Row)`
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
border-radius: 0.5em;
|
/* border-radius: 0.5em; */
|
||||||
`
|
`
|
||||||
|
|
||||||
export const HistoryRow = styled(RowBorderGray)`
|
export const RowLightBorderGray = styled(Row2)`
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
/* border-radius: 0.5em; */
|
||||||
|
`
|
||||||
|
|
||||||
|
export const HistoryRow = styled(RowLightBorderGray)`
|
||||||
& > ${Column}:last-of-type {
|
& > ${Column}:last-of-type {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
@ -244,24 +284,24 @@ export const ErrorBox = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
export const PopupNavigation = styled.div`
|
export const PopupNavigation = styled.div<{devMode?:boolean}>`
|
||||||
background-color: #033;
|
background-color:#0042b2;
|
||||||
|
height: 35px;
|
||||||
|
|
||||||
& > a {
|
& > a {
|
||||||
color: #f8faf7;
|
color: #f8faf7;
|
||||||
padding-top: 0.7em;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: calc(400px / 5);
|
width: calc(400px / ${({ devMode }) => !devMode ? 4 : 5});
|
||||||
padding-bottom: 0.7em;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > a.active {
|
& > a.active {
|
||||||
background-color: #f8faf7;
|
background-color: #f8faf7;
|
||||||
color: #000;
|
color: #0042b2;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
|
export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] {
|
||||||
|
const [timedOut, setTimedOut] = useState(false);
|
||||||
|
const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let gotDiagnostics = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!gotDiagnostics) {
|
||||||
|
console.error("timed out");
|
||||||
|
setTimedOut(true);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
const doFetch = async (): Promise<void> => {
|
||||||
|
const d = await wxApi.getDiagnostics();
|
||||||
|
console.log("got diagnostics", d);
|
||||||
|
gotDiagnostics = true;
|
||||||
|
setDiagnostics(d);
|
||||||
|
};
|
||||||
|
console.log("fetching diagnostics");
|
||||||
|
doFetch();
|
||||||
|
}, []);
|
||||||
|
return [diagnostics, timedOut]
|
||||||
|
}
|
@ -57,12 +57,8 @@ function makeExtensionUrlWithParams(
|
|||||||
): string {
|
): string {
|
||||||
const innerUrl = new URL(chrome.extension.getURL("/" + url));
|
const innerUrl = new URL(chrome.extension.getURL("/" + url));
|
||||||
if (params) {
|
if (params) {
|
||||||
for (const key in params) {
|
const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&')
|
||||||
const p = params[key];
|
innerUrl.hash = innerUrl.hash + '?' + hParams
|
||||||
if (p) {
|
|
||||||
innerUrl.searchParams.set(key, p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return innerUrl.href;
|
return innerUrl.href;
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ export function BalanceView({ balance, Linker }: BalanceViewProps) {
|
|||||||
<div>
|
<div>
|
||||||
<p>{i18n.str`Error: could not retrieve balance information.`}</p>
|
<p>{i18n.str`Error: could not retrieve balance information.`}</p>
|
||||||
<p>
|
<p>
|
||||||
Click <Linker pageName="welcome.html">here</Linker> for help and
|
Click <Linker pageName="welcome">here</Linker> for help and
|
||||||
diagnostics.
|
diagnostics.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
import { JSX } from "preact";
|
import { JSX } from "preact";
|
||||||
import { Diagnostics } from "../components/Diagnostics";
|
import { Diagnostics } from "../components/Diagnostics";
|
||||||
|
import { useDiagnostics } from "../hooks/useDiagnostics.js";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
|
|
||||||
|
|
||||||
export function DeveloperPage(props: any): JSX.Element {
|
export function DeveloperPage(props: any): JSX.Element {
|
||||||
|
const [status, timedOut] = useDiagnostics();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>Debug tools:</p>
|
<p>Debug tools:</p>
|
||||||
@ -27,7 +29,7 @@ export function DeveloperPage(props: any): JSX.Element {
|
|||||||
<br />
|
<br />
|
||||||
<button onClick={confirmReset}>reset</button>
|
<button onClick={confirmReset}>reset</button>
|
||||||
<button onClick={reload}>reload chrome extension</button>
|
<button onClick={reload}>reload chrome extension</button>
|
||||||
<Diagnostics />
|
<Diagnostics diagnostics={status} timedOut={timedOut} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import { FunctionalComponent } from 'preact';
|
|||||||
import { HistoryView as TestedComponent } from './History';
|
import { HistoryView as TestedComponent } from './History';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'popup/transaction/list',
|
title: 'popup/history/list',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -112,12 +112,26 @@ function createExample<Props>(Component: FunctionalComponent<Props>, props: Part
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent, {
|
export const Empty = createExample(TestedComponent, {
|
||||||
list: []
|
list: [],
|
||||||
|
balances: [{
|
||||||
|
available: 'TESTKUDOS:10',
|
||||||
|
pendingIncoming: 'TESTKUDOS:0',
|
||||||
|
pendingOutgoing: 'TESTKUDOS:0',
|
||||||
|
hasPendingTransactions: false,
|
||||||
|
requiresUserInput: false,
|
||||||
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export const One = createExample(TestedComponent, {
|
export const One = createExample(TestedComponent, {
|
||||||
list: [exampleData.withdraw]
|
list: [exampleData.withdraw],
|
||||||
|
balances: [{
|
||||||
|
available: 'USD:10',
|
||||||
|
pendingIncoming: 'USD:0',
|
||||||
|
pendingOutgoing: 'USD:0',
|
||||||
|
hasPendingTransactions: false,
|
||||||
|
requiresUserInput: false,
|
||||||
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Several = createExample(TestedComponent, {
|
export const Several = createExample(TestedComponent, {
|
||||||
@ -130,7 +144,40 @@ export const Several = createExample(TestedComponent, {
|
|||||||
exampleData.refund,
|
exampleData.refund,
|
||||||
exampleData.tip,
|
exampleData.tip,
|
||||||
exampleData.deposit,
|
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, {
|
// export const WithdrawPending = createExample(TestedComponent, {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountString, Timestamp, Transaction, TransactionsResponse, TransactionType } from "@gnu-taler/taler-util";
|
import { AmountJson, Amounts, AmountString, Balance, Timestamp, Transaction, TransactionsResponse, TransactionType } from "@gnu-taler/taler-util";
|
||||||
import { JSX } from "preact";
|
import { JSX } from "preact";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import * as wxApi from "../wxApi";
|
import * as wxApi from "../wxApi";
|
||||||
@ -25,6 +25,8 @@ export function HistoryPage(props: any): JSX.Element {
|
|||||||
const [transactions, setTransactions] = useState<
|
const [transactions, setTransactions] = useState<
|
||||||
TransactionsResponse | undefined
|
TransactionsResponse | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
const balance = useBalances()
|
||||||
|
const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async (): Promise<void> => {
|
const fetchData = async (): Promise<void> => {
|
||||||
@ -38,16 +40,36 @@ export function HistoryPage(props: any): JSX.Element {
|
|||||||
return <div>Loading ...</div>;
|
return <div>Loading ...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <HistoryView list={[...transactions.transactions].reverse()} />;
|
return <HistoryView balances={balanceWithoutError} list={[...transactions.transactions].reverse()} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HistoryView({ list }: { list: Transaction[] }) {
|
function amountToString(c: AmountString) {
|
||||||
return <PopupBox>
|
const idx = c.indexOf(':')
|
||||||
|
return `${c.substring(idx+1)} ${c.substring(0,idx)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function HistoryView({ list, balances }: { list: Transaction[], balances: Balance[] }) {
|
||||||
|
return <PopupBox 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>
|
<section>
|
||||||
{list.map((tx, i) => (
|
{list.slice(0, 3).map((tx, i) => (
|
||||||
<TransactionItem key={i} tx={tx} />
|
<TransactionItem key={i} tx={tx} />
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
|
<footer style={{ justifyContent: 'space-around' }}>
|
||||||
|
<a style={{ color: 'darkgreen', textDecoration:'none' }} href={Pages.transaction.replace(':tid', 'asd')}>VIEW MORE TRANSACTIONS</a>
|
||||||
|
</footer>
|
||||||
</PopupBox>
|
</PopupBox>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +79,8 @@ import imageRefund from '../../static/img/ri-refund-2-line.svg';
|
|||||||
import imageHandHeart from '../../static/img/ri-hand-heart-line.svg';
|
import imageHandHeart from '../../static/img/ri-hand-heart-line.svg';
|
||||||
import imageRefresh from '../../static/img/ri-refresh-line.svg';
|
import imageRefresh from '../../static/img/ri-refresh-line.svg';
|
||||||
import { Column, ExtraLargeText, HistoryRow, PopupBox, Row, RowBorderGray, SmallTextLight } from "../components/styled";
|
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;
|
||||||
@ -144,23 +168,21 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element {
|
|||||||
|
|
||||||
function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
|
function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
|
||||||
const date = new Date(props.timestamp.t_ms);
|
const date = new Date(props.timestamp.t_ms);
|
||||||
const dateStr = date.toLocaleString([], {
|
const now = new Date();
|
||||||
dateStyle: "medium",
|
const dateStr = formatDistance(date, now, { addSuffix: true })
|
||||||
timeStyle: "short",
|
|
||||||
} as any);
|
|
||||||
return (
|
return (
|
||||||
<HistoryRow>
|
<HistoryRow>
|
||||||
<img src={props.iconPath} />
|
<img src={props.iconPath} />
|
||||||
<Column>
|
<Column>
|
||||||
<SmallTextLight>{dateStr}</SmallTextLight>
|
|
||||||
<ExtraLargeText>
|
<ExtraLargeText>
|
||||||
<a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a>
|
<a href={Pages.transaction.replace(':tid', props.id)}><span>{props.title}</span></a>
|
||||||
{props.pending ? (
|
{props.pending ? (
|
||||||
<span style={{ color: "darkblue" }}> (Pending)</span>
|
<span style={{ color: "darkblue" }}> (Pending)</span>
|
||||||
) : null}
|
) : null}
|
||||||
</ExtraLargeText>
|
</ExtraLargeText>
|
||||||
|
<SmallTextLight>{dateStr}</SmallTextLight>
|
||||||
|
|
||||||
<div>{props.subtitle}</div>
|
{/* <div>{props.subtitle}</div> */}
|
||||||
</Column>
|
</Column>
|
||||||
<TransactionAmount
|
<TransactionAmount
|
||||||
pending={props.pending}
|
pending={props.pending}
|
||||||
@ -202,7 +224,13 @@ function TransactionAmount(props: TransactionAmountProps): JSX.Element {
|
|||||||
sign = "";
|
sign = "";
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Column style={{ color: props.pending ? "gray" : undefined }}>
|
<Column style={{
|
||||||
|
color:
|
||||||
|
props.pending ? "gray" :
|
||||||
|
(sign === '+' ? 'darkgreen' :
|
||||||
|
(sign === '-' ? 'darkred' :
|
||||||
|
undefined))
|
||||||
|
}}>
|
||||||
<ExtraLargeText>
|
<ExtraLargeText>
|
||||||
{sign}
|
{sign}
|
||||||
{amount}
|
{amount}
|
||||||
|
@ -58,13 +58,12 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP
|
|||||||
const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged
|
const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged
|
||||||
return (
|
return (
|
||||||
<PopupBox>
|
<PopupBox>
|
||||||
<header>
|
{info.backupProblem || info.lastError ? <header>
|
||||||
<Error info={info} />
|
<Error info={info} />
|
||||||
|
</header> : undefined }
|
||||||
</header>
|
|
||||||
<header>
|
<header>
|
||||||
<h3>{info.name} <SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3>
|
<h3>{info.name} <SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3>
|
||||||
<PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid': 'Unpaid' }</PaymentStatus>
|
<PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 60)'}>{isPaid ? 'Paid' : 'Unpaid'}</PaymentStatus>
|
||||||
</header>
|
</header>
|
||||||
<section>
|
<section>
|
||||||
<p><b>Last backup:</b> {lb == null || lb.t_ms == "never" ? "never" : format(lb.t_ms, 'dd MMM yyyy')} </p>
|
<p><b>Last backup:</b> {lb == null || lb.t_ms == "never" ? "never" : format(lb.t_ms, 'dd MMM yyyy')} </p>
|
||||||
|
@ -68,7 +68,7 @@ const names: LangsNames = {
|
|||||||
export function SettingsView({ lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode {
|
export function SettingsView({ lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<section style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}>
|
<section style={{ height: 300, overflow: 'auto' }}>
|
||||||
<h2><i18n.Translate>Wallet</i18n.Translate></h2>
|
<h2><i18n.Translate>Wallet</i18n.Translate></h2>
|
||||||
<SelectList
|
<SelectList
|
||||||
value={lang}
|
value={lang}
|
||||||
|
@ -30,7 +30,7 @@ import { FunctionalComponent } from 'preact';
|
|||||||
import { TransactionView as TestedComponent } from './Transaction';
|
import { TransactionView as TestedComponent } from './Transaction';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'popup/transaction/details',
|
title: 'popup/history/details',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onRetry: { action: 'onRetry' },
|
onRetry: { action: 'onRetry' },
|
||||||
|
@ -60,7 +60,7 @@ 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>
|
return <PopupNavigation devMode={devMode}>
|
||||||
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
|
<Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
|
||||||
<Tab target="/history" current={path}>{i18n.str`History`}</Tab>
|
<Tab target="/history" current={path}>{i18n.str`History`}</Tab>
|
||||||
<Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
|
<Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
|
||||||
|
@ -93,7 +93,7 @@ function Application() {
|
|||||||
<div>
|
<div>
|
||||||
<DevContextProvider>
|
<DevContextProvider>
|
||||||
<WalletNavBar />
|
<WalletNavBar />
|
||||||
<div style={{ padding: 8, width: 'calc(400px - 16px)', height: 'calc(320px - 34px - 16px)' }}>
|
<div style={{ width: 400, height: 290 }}>
|
||||||
<Router history={createHashHistory()}>
|
<Router history={createHashHistory()}>
|
||||||
<Route path={Pages.balance} component={BalancePage} />
|
<Route path={Pages.balance} component={BalancePage} />
|
||||||
<Route path={Pages.settings} component={SettingsPage} />
|
<Route path={Pages.settings} component={SettingsPage} />
|
||||||
|
103
packages/taler-wallet-webextension/src/wallet/Pay.stories.tsx
Normal file
103
packages/taler-wallet-webextension/src/wallet/Pay.stories.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
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 { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util';
|
||||||
|
import { FunctionalComponent, h } from 'preact';
|
||||||
|
import { PaymentRequestView as TestedComponent } from './Pay';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'wallet/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,
|
||||||
|
proposalId: "proposal1234",
|
||||||
|
contractTerms: {
|
||||||
|
merchant: {
|
||||||
|
name: 'someone'
|
||||||
|
},
|
||||||
|
amount: 'USD:10',
|
||||||
|
} as Partial<ContractTerms> as any,
|
||||||
|
amountRaw: 'USD:10',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PaymentPossible = createExample(TestedComponent, {
|
||||||
|
payStatus: {
|
||||||
|
status: PreparePayResultType.PaymentPossible,
|
||||||
|
amountEffective: 'USD:10',
|
||||||
|
amountRaw: 'USD:10',
|
||||||
|
contractTerms: {
|
||||||
|
merchant: {
|
||||||
|
name: 'someone'
|
||||||
|
},
|
||||||
|
amount: 'USD:10',
|
||||||
|
} as Partial<ContractTerms> as any,
|
||||||
|
contractTermsHash: '123456',
|
||||||
|
proposalId: 'proposal1234'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
|
||||||
|
payStatus: {
|
||||||
|
status: PreparePayResultType.AlreadyConfirmed,
|
||||||
|
amountEffective: 'USD:10',
|
||||||
|
amountRaw: 'USD:10',
|
||||||
|
contractTerms: {
|
||||||
|
merchant: {
|
||||||
|
name: 'someone'
|
||||||
|
},
|
||||||
|
fulfillment_message: 'congratulations! you are looking at the fulfillment message! ',
|
||||||
|
amount: 'USD:10',
|
||||||
|
} as Partial<ContractTerms> as any,
|
||||||
|
contractTermsHash: '123456',
|
||||||
|
proposalId: 'proposal1234',
|
||||||
|
paid: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AlreadyConfirmedWithoutFullfilment = createExample(TestedComponent, {
|
||||||
|
payStatus: {
|
||||||
|
status: PreparePayResultType.AlreadyConfirmed,
|
||||||
|
amountEffective: 'USD:10',
|
||||||
|
amountRaw: 'USD:10',
|
||||||
|
contractTerms: {
|
||||||
|
merchant: {
|
||||||
|
name: 'someone'
|
||||||
|
},
|
||||||
|
amount: 'USD:10',
|
||||||
|
} as Partial<ContractTerms> as any,
|
||||||
|
contractTermsHash: '123456',
|
||||||
|
proposalId: 'proposal1234',
|
||||||
|
paid: false,
|
||||||
|
}
|
||||||
|
});
|
@ -29,7 +29,7 @@ import * as wxApi from "../wxApi";
|
|||||||
|
|
||||||
import { useState, useEffect } from "preact/hooks";
|
import { useState, useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { getJsonI18n, i18n } from "@gnu-taler/taler-util";
|
import { ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
PreparePayResult,
|
PreparePayResult,
|
||||||
ConfirmPayResult,
|
ConfirmPayResult,
|
||||||
@ -45,95 +45,37 @@ interface Props {
|
|||||||
talerPayUri?: string
|
talerPayUri?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PayPage({ talerPayUri }: Props): JSX.Element {
|
export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) {
|
||||||
const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(undefined);
|
|
||||||
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined);
|
|
||||||
const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
|
|
||||||
const [numTries, setNumTries] = useState(0);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
let totalFees: AmountJson | undefined = undefined;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!talerPayUri) return;
|
|
||||||
const doFetch = async (): Promise<void> => {
|
|
||||||
const p = await wxApi.preparePay(talerPayUri);
|
|
||||||
setPayStatus(p);
|
|
||||||
};
|
|
||||||
doFetch();
|
|
||||||
}, [numTries, talerPayUri]);
|
|
||||||
|
|
||||||
if (!talerPayUri) {
|
|
||||||
return <span>missing pay uri</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!payStatus) {
|
|
||||||
return <span>Loading payment information ...</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let insufficientBalance = false;
|
|
||||||
if (payStatus.status == PreparePayResultType.InsufficientBalance) {
|
|
||||||
insufficientBalance = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payStatus.status === PreparePayResultType.PaymentPossible) {
|
|
||||||
const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw);
|
|
||||||
const amountEffective: AmountJson = Amounts.parseOrThrow(
|
|
||||||
payStatus.amountEffective,
|
|
||||||
);
|
|
||||||
totalFees = Amounts.sub(amountEffective, amountRaw).amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
payStatus.status === PreparePayResultType.AlreadyConfirmed &&
|
|
||||||
numTries === 0
|
|
||||||
) {
|
|
||||||
const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
|
const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
|
||||||
|
let message;
|
||||||
if (fulfillmentUrl) {
|
if (fulfillmentUrl) {
|
||||||
return (
|
message = (
|
||||||
<span>
|
<span>
|
||||||
You have already paid for this article. Click{" "}
|
You have already paid for this article. Click{" "}
|
||||||
<a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
|
<a href={fulfillmentUrl} target="_bank" rel="external">here</a> to view it again.
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
<span>
|
message = <span>
|
||||||
You have already paid for this article:{" "}
|
You have already paid for this article:{" "}
|
||||||
<em>
|
<em>
|
||||||
{payStatus.contractTerms.fulfillment_message ?? "no message given"}
|
{payStatus.contractTerms.fulfillment_message ?? "no message given"}
|
||||||
</em>
|
</em>
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
}
|
return <section class="main">
|
||||||
|
<h1>GNU Taler Wallet</h1>
|
||||||
|
<article class="fade">
|
||||||
|
{message}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
const contractTerms: ContractTerms = payStatus.contractTerms;
|
const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultDone> => {
|
||||||
|
|
||||||
if (!contractTerms) {
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
Error: did not get contract terms from merchant or wallet backend.
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let merchantName: VNode;
|
|
||||||
if (contractTerms.merchant && contractTerms.merchant.name) {
|
|
||||||
merchantName = <strong>{contractTerms.merchant.name}</strong>;
|
|
||||||
} else {
|
|
||||||
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const amount = (
|
|
||||||
<strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong>
|
|
||||||
);
|
|
||||||
|
|
||||||
const doPayment = async (): Promise<void> => {
|
|
||||||
if (payStatus.status !== "payment-possible") {
|
if (payStatus.status !== "payment-possible") {
|
||||||
throw Error(`invalid state: ${payStatus.status}`);
|
throw Error(`invalid state: ${payStatus.status}`);
|
||||||
}
|
}
|
||||||
const proposalId = payStatus.proposalId;
|
const proposalId = payStatus.proposalId;
|
||||||
setNumTries(numTries + 1);
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const res = await wxApi.confirmPay(proposalId, undefined);
|
const res = await wxApi.confirmPay(proposalId, undefined);
|
||||||
if (res.type !== ConfirmPayResultType.Done) {
|
if (res.type !== ConfirmPayResultType.Done) {
|
||||||
throw Error("payment pending");
|
throw Error("payment pending");
|
||||||
@ -142,12 +84,32 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
|||||||
if (fu) {
|
if (fu) {
|
||||||
document.location.href = fu;
|
document.location.href = fu;
|
||||||
}
|
}
|
||||||
setPayResult(res);
|
return res;
|
||||||
} catch (e) {
|
};
|
||||||
console.error(e);
|
|
||||||
setPayErrMsg(e.message);
|
|
||||||
}
|
|
||||||
|
export function PayPage({ talerPayUri }: Props): JSX.Element {
|
||||||
|
const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(undefined);
|
||||||
|
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined);
|
||||||
|
const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!talerPayUri) return;
|
||||||
|
const doFetch = async (): Promise<void> => {
|
||||||
|
const p = await wxApi.preparePay(talerPayUri);
|
||||||
|
setPayStatus(p);
|
||||||
};
|
};
|
||||||
|
doFetch();
|
||||||
|
}, [talerPayUri]);
|
||||||
|
|
||||||
|
if (!talerPayUri) {
|
||||||
|
return <span>missing pay uri</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payStatus) {
|
||||||
|
return <span>Loading payment information ...</span>;
|
||||||
|
}
|
||||||
|
|
||||||
if (payResult && payResult.type === ConfirmPayResultType.Done) {
|
if (payResult && payResult.type === ConfirmPayResultType.Done) {
|
||||||
if (payResult.contractTerms.fulfillment_message) {
|
if (payResult.contractTerms.fulfillment_message) {
|
||||||
@ -168,7 +130,72 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onClick = async () => {
|
||||||
|
try {
|
||||||
|
const res = await doPayment(payStatus)
|
||||||
|
setPayResult(res);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setPayErrMsg(e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PaymentRequestView payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentRequestViewProps {
|
||||||
|
payStatus: PreparePayResult;
|
||||||
|
onClick: () => void;
|
||||||
|
payErrMsg?: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
export function PaymentRequestView({ payStatus, onClick, payErrMsg }: PaymentRequestViewProps) {
|
||||||
|
let totalFees: AmountJson | undefined = undefined;
|
||||||
|
let insufficientBalance = false;
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const contractTerms: ContractTerms = payStatus.contractTerms;
|
||||||
|
|
||||||
|
if (
|
||||||
|
payStatus.status === PreparePayResultType.AlreadyConfirmed
|
||||||
|
) {
|
||||||
|
return <AlreadyPaid payStatus={payStatus} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contractTerms) {
|
||||||
return (
|
return (
|
||||||
|
<span>
|
||||||
|
Error: did not get contract terms from merchant or wallet backend.
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payStatus.status == PreparePayResultType.InsufficientBalance) {
|
||||||
|
insufficientBalance = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payStatus.status === PreparePayResultType.PaymentPossible) {
|
||||||
|
const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw);
|
||||||
|
const amountEffective: AmountJson = Amounts.parseOrThrow(
|
||||||
|
payStatus.amountEffective,
|
||||||
|
);
|
||||||
|
totalFees = Amounts.sub(amountEffective, amountRaw).amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
let merchantName: VNode;
|
||||||
|
if (contractTerms.merchant && contractTerms.merchant.name) {
|
||||||
|
merchantName = <strong>{contractTerms.merchant.name}</strong>;
|
||||||
|
} else {
|
||||||
|
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = (
|
||||||
|
<strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <section class="main">
|
||||||
|
<h1>GNU Taler Wallet</h1>
|
||||||
|
<article class="fade">
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<i18n.Translate>
|
<i18n.Translate>
|
||||||
@ -202,7 +229,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
|||||||
<p>Payment failed: {payErrMsg}</p>
|
<p>Payment failed: {payErrMsg}</p>
|
||||||
<button
|
<button
|
||||||
class="pure-button button-success"
|
class="pure-button button-success"
|
||||||
onClick={() => doPayment()}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{i18n.str`Retry`}
|
{i18n.str`Retry`}
|
||||||
</button>
|
</button>
|
||||||
@ -212,13 +239,15 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
|
|||||||
<ProgressButton
|
<ProgressButton
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
disabled={insufficientBalance}
|
disabled={insufficientBalance}
|
||||||
onClick={() => doPayment()}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{i18n.str`Confirm payment`}
|
{i18n.str`Confirm payment`}
|
||||||
</ProgressButton>
|
</ProgressButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
</article>
|
||||||
}
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
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 { ContractTerms, OrderShortInfo, PreparePayResultType } from '@gnu-taler/taler-util';
|
||||||
|
import { FunctionalComponent, h } from 'preact';
|
||||||
|
import { View as TestedComponent } from './Refund';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'wallet/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',
|
||||||
|
amountRefundGone: 'USD:0',
|
||||||
|
amountRefundGranted: 'USD:2',
|
||||||
|
contractTermsHash: 'QWEASDZXC',
|
||||||
|
info: {
|
||||||
|
summary: 'tasty cold beer',
|
||||||
|
contractTermsHash: 'QWEASDZXC',
|
||||||
|
} as Partial<OrderShortInfo> as any,
|
||||||
|
pendingAtExchange: false,
|
||||||
|
proposalId: "proposal123",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Partial = createExample(TestedComponent, {
|
||||||
|
applyResult: {
|
||||||
|
amountEffectivePaid: 'USD:10',
|
||||||
|
amountRefundGone: 'USD:1',
|
||||||
|
amountRefundGranted: 'USD:2',
|
||||||
|
contractTermsHash: 'QWEASDZXC',
|
||||||
|
info: {
|
||||||
|
summary: 'tasty cold beer',
|
||||||
|
contractTermsHash: 'QWEASDZXC',
|
||||||
|
} as Partial<OrderShortInfo> as any,
|
||||||
|
pendingAtExchange: false,
|
||||||
|
proposalId: "proposal123",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const InProgress = createExample(TestedComponent, {
|
||||||
|
applyResult: {
|
||||||
|
amountEffectivePaid: 'USD:10',
|
||||||
|
amountRefundGone: 'USD:1',
|
||||||
|
amountRefundGranted: 'USD:2',
|
||||||
|
contractTermsHash: 'QWEASDZXC',
|
||||||
|
info: {
|
||||||
|
summary: 'tasty cold beer',
|
||||||
|
contractTermsHash: 'QWEASDZXC',
|
||||||
|
} as Partial<OrderShortInfo> as any,
|
||||||
|
pendingAtExchange: true,
|
||||||
|
proposalId: "proposal123",
|
||||||
|
}
|
||||||
|
});
|
@ -32,7 +32,32 @@ import { JSX } from "preact/jsx-runtime";
|
|||||||
interface Props {
|
interface Props {
|
||||||
talerRefundUri?: string
|
talerRefundUri?: string
|
||||||
}
|
}
|
||||||
|
export interface ViewProps {
|
||||||
|
applyResult: ApplyRefundResponse;
|
||||||
|
}
|
||||||
|
export function View({ applyResult }: ViewProps) {
|
||||||
|
return <section class="main">
|
||||||
|
<h1>GNU Taler Wallet</h1>
|
||||||
|
<article class="fade">
|
||||||
|
<h2>Refund Status</h2>
|
||||||
|
<p>
|
||||||
|
The product <em>{applyResult.info.summary}</em> has received a total
|
||||||
|
effective refund of{" "}
|
||||||
|
<AmountView amount={applyResult.amountRefundGranted} />.
|
||||||
|
</p>
|
||||||
|
{applyResult.pendingAtExchange ? (
|
||||||
|
<p>Refund processing is still in progress.</p>
|
||||||
|
) : null}
|
||||||
|
{!Amounts.isZero(applyResult.amountRefundGone) ? (
|
||||||
|
<p>
|
||||||
|
The refund amount of{" "}
|
||||||
|
<AmountView amount={applyResult.amountRefundGone} />{" "}
|
||||||
|
could not be applied.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
export function RefundPage({ talerRefundUri }: Props): JSX.Element {
|
export function RefundPage({ talerRefundUri }: Props): JSX.Element {
|
||||||
const [applyResult, setApplyResult] = useState<ApplyRefundResponse | undefined>(undefined);
|
const [applyResult, setApplyResult] = useState<ApplyRefundResponse | undefined>(undefined);
|
||||||
const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
|
const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
|
||||||
@ -66,24 +91,5 @@ export function RefundPage({ talerRefundUri }: Props): JSX.Element {
|
|||||||
return <span>Updating refund status</span>;
|
return <span>Updating refund status</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <View applyResult={applyResult} />;
|
||||||
<>
|
|
||||||
<h2>Refund Status</h2>
|
|
||||||
<p>
|
|
||||||
The product <em>{applyResult.info.summary}</em> has received a total
|
|
||||||
effective refund of{" "}
|
|
||||||
<AmountView amount={applyResult.amountRefundGranted} />.
|
|
||||||
</p>
|
|
||||||
{applyResult.pendingAtExchange ? (
|
|
||||||
<p>Refund processing is still in progress.</p>
|
|
||||||
) : null}
|
|
||||||
{!Amounts.isZero(applyResult.amountRefundGone) ? (
|
|
||||||
<p>
|
|
||||||
The refund amount of{" "}
|
|
||||||
<AmountView amount={applyResult.amountRefundGone} />
|
|
||||||
could not be applied.
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
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 { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util';
|
||||||
|
import { FunctionalComponent, h } from 'preact';
|
||||||
|
import { View as TestedComponent } from './Tip';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'wallet/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,
|
||||||
|
merchantBaseUrl: '',
|
||||||
|
exchangeBaseUrl: '',
|
||||||
|
expirationTimestamp : {
|
||||||
|
t_ms: 0
|
||||||
|
},
|
||||||
|
tipAmountEffective: 'USD:10',
|
||||||
|
tipAmountRaw: 'USD:5',
|
||||||
|
walletTipId: 'id'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NotYetAccepted = createExample(TestedComponent, {
|
||||||
|
prepareTipResult: {
|
||||||
|
accepted: false,
|
||||||
|
merchantBaseUrl: 'http://merchant.url/',
|
||||||
|
exchangeBaseUrl: 'http://exchange.url/',
|
||||||
|
expirationTimestamp : {
|
||||||
|
t_ms: 0
|
||||||
|
},
|
||||||
|
tipAmountEffective: 'USD:10',
|
||||||
|
tipAmountRaw: 'USD:5',
|
||||||
|
walletTipId: 'id'
|
||||||
|
}
|
||||||
|
});
|
@ -29,6 +29,39 @@ import { JSX } from "preact/jsx-runtime";
|
|||||||
interface Props {
|
interface Props {
|
||||||
talerTipUri?: string
|
talerTipUri?: string
|
||||||
}
|
}
|
||||||
|
export interface ViewProps {
|
||||||
|
prepareTipResult: PrepareTipResult;
|
||||||
|
onAccept: () => void;
|
||||||
|
onIgnore: () => void;
|
||||||
|
|
||||||
|
}
|
||||||
|
export function View({ prepareTipResult, onAccept, onIgnore }: ViewProps) {
|
||||||
|
return <section class="main">
|
||||||
|
<h1>GNU Taler Wallet</h1>
|
||||||
|
<article class="fade">
|
||||||
|
{prepareTipResult.accepted ? (
|
||||||
|
<span>
|
||||||
|
Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. Check
|
||||||
|
your transactions list for more details.
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is
|
||||||
|
offering you a tip of{" "}
|
||||||
|
<strong>
|
||||||
|
<AmountView amount={prepareTipResult.tipAmountEffective} />
|
||||||
|
</strong>{" "}
|
||||||
|
via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code>
|
||||||
|
</p>
|
||||||
|
<button onClick={onAccept}>Accept tip</button>
|
||||||
|
<button onClick={onIgnore}>Ignore</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export function TipPage({ talerTipUri }: Props): JSX.Element {
|
export function TipPage({ talerTipUri }: Props): JSX.Element {
|
||||||
const [updateCounter, setUpdateCounter] = useState<number>(0);
|
const [updateCounter, setUpdateCounter] = useState<number>(0);
|
||||||
@ -71,27 +104,7 @@ export function TipPage({ talerTipUri }: Props): JSX.Element {
|
|||||||
return <span>Loading ...</span>;
|
return <span>Loading ...</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prepareTipResult.accepted) {
|
return <View prepareTipResult={prepareTipResult}
|
||||||
return (
|
onAccept={doAccept} onIgnore={doIgnore}
|
||||||
<span>
|
/>
|
||||||
Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. Check
|
|
||||||
your transactions list for more details.
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is
|
|
||||||
offering you a tip of{" "}
|
|
||||||
<strong>
|
|
||||||
<AmountView amount={prepareTipResult.tipAmountEffective} />
|
|
||||||
</strong>{" "}
|
|
||||||
via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code>
|
|
||||||
</p>
|
|
||||||
<button onClick={doAccept}>Accept tip</button>
|
|
||||||
<button onClick={doIgnore}>Ignore</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
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 { FunctionalComponent, h } from 'preact';
|
||||||
|
import { View as TestedComponent } from './Welcome';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'wallet/welcome',
|
||||||
|
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: {
|
||||||
|
errors: [],
|
||||||
|
walletManifestVersion: '1.0',
|
||||||
|
walletManifestDisplayVersion: '1.0',
|
||||||
|
firefoxIdbProblem: false,
|
||||||
|
dbOutdated: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const TimedoutDiagnostics = createExample(TestedComponent, {
|
||||||
|
timedOut: true,
|
||||||
|
permissionsEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RunningDiagnostics = createExample(TestedComponent, {
|
||||||
|
permissionsEnabled: false,
|
||||||
|
});
|
||||||
|
|
@ -24,13 +24,36 @@ 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 { useDiagnostics } from "../hooks/useDiagnostics";
|
||||||
|
import { WalletDiagnostics } from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
export function WelcomePage(): JSX.Element {
|
export function WelcomePage() {
|
||||||
const [permissionsEnabled, togglePermissions] = useExtendedPermissions()
|
const [permissionsEnabled, togglePermissions] = useExtendedPermissions()
|
||||||
return (
|
const [diagnostics, timedOut] = useDiagnostics()
|
||||||
<>
|
return <View
|
||||||
|
permissionsEnabled={permissionsEnabled} togglePermissions={togglePermissions}
|
||||||
|
diagnostics={diagnostics} timedOut={timedOut}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewProps {
|
||||||
|
permissionsEnabled: boolean,
|
||||||
|
togglePermissions: () => void,
|
||||||
|
diagnostics: WalletDiagnostics | undefined,
|
||||||
|
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>
|
||||||
|
<h1>Browser Extension Installed!</h1>
|
||||||
|
<div>
|
||||||
<p>Thank you for installing the wallet.</p>
|
<p>Thank you for installing the wallet.</p>
|
||||||
<Diagnostics />
|
<Diagnostics diagnostics={diagnostics} timedOut={timedOut} />
|
||||||
<h2>Permissions</h2>
|
<h2>Permissions</h2>
|
||||||
<Checkbox label="Automatically open wallet based on page content"
|
<Checkbox label="Automatically open wallet based on page content"
|
||||||
name="perm"
|
name="perm"
|
||||||
@ -44,6 +67,7 @@ export function WelcomePage(): JSX.Element {
|
|||||||
<a href="https://demo.taler.net/" style={{ display: "block" }}>
|
<a href="https://demo.taler.net/" style={{ display: "block" }}>
|
||||||
Learn how to top up your wallet balance »
|
Learn how to top up your wallet balance »
|
||||||
</a>
|
</a>
|
||||||
</>
|
</div>
|
||||||
|
</WalletPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,27 +30,12 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithoutURI = (a: any) => <View {...a} />;
|
|
||||||
WithoutURI.args = {
|
|
||||||
} as ViewProps
|
|
||||||
|
|
||||||
export const WithoutDetails = (a: any) => <View {...a} />;
|
export const WithoutDetails = (a: any) => <View {...a} />;
|
||||||
WithoutDetails.args = {
|
WithoutDetails.args = {
|
||||||
talerWithdrawUri: 'http://something'
|
|
||||||
} as ViewProps
|
|
||||||
|
|
||||||
export const Cancelled = (a: any) => <View {...a} />;
|
|
||||||
Cancelled.args = {
|
|
||||||
talerWithdrawUri: 'http://something',
|
|
||||||
details: {
|
|
||||||
amount: 'USD:2',
|
|
||||||
},
|
|
||||||
cancelled: true
|
|
||||||
} as ViewProps
|
} as ViewProps
|
||||||
|
|
||||||
export const CompleteWithExchange = (a: any) => <View {...a} />;
|
export const CompleteWithExchange = (a: any) => <View {...a} />;
|
||||||
CompleteWithExchange.args = {
|
CompleteWithExchange.args = {
|
||||||
talerWithdrawUri: 'http://something',
|
|
||||||
details: {
|
details: {
|
||||||
amount: 'USD:2',
|
amount: 'USD:2',
|
||||||
},
|
},
|
||||||
@ -59,7 +44,6 @@ CompleteWithExchange.args = {
|
|||||||
|
|
||||||
export const CompleteWithoutExchange = (a: any) => <View {...a} />;
|
export const CompleteWithoutExchange = (a: any) => <View {...a} />;
|
||||||
CompleteWithoutExchange.args = {
|
CompleteWithoutExchange.args = {
|
||||||
talerWithdrawUri: 'http://something',
|
|
||||||
details: {
|
details: {
|
||||||
amount: 'USD:2',
|
amount: 'USD:2',
|
||||||
},
|
},
|
||||||
|
@ -32,6 +32,7 @@ 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';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
talerWithdrawUri?: string;
|
talerWithdrawUri?: string;
|
||||||
@ -39,32 +40,23 @@ interface Props {
|
|||||||
|
|
||||||
export interface ViewProps {
|
export interface ViewProps {
|
||||||
talerWithdrawUri?: string;
|
talerWithdrawUri?: string;
|
||||||
details?: WithdrawUriInfoResponse;
|
details: WithdrawUriInfoResponse;
|
||||||
cancelled?: boolean;
|
|
||||||
selectedExchange?: string;
|
selectedExchange?: string;
|
||||||
accept: () => Promise<void>;
|
accept: () => Promise<void>;
|
||||||
setCancelled: (b: boolean) => void;
|
setCancelled: (b: boolean) => void;
|
||||||
setSelecting: (b: boolean) => void;
|
setSelecting: (b: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function View({ talerWithdrawUri, details, cancelled, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) {
|
export function View({ details, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) {
|
||||||
const [state, setState] = useState(1)
|
|
||||||
setTimeout(() => {
|
|
||||||
setState(s => s + 1)
|
|
||||||
}, 1000);
|
|
||||||
if (!talerWithdrawUri) {
|
|
||||||
return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!details) {
|
|
||||||
return <span><i18n.Translate>Loading...</i18n.Translate></span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancelled) {
|
|
||||||
return <span><i18n.Translate>Withdraw operation has been cancelled.{state}</i18n.Translate></span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
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>
|
||||||
|
<div class="fade">
|
||||||
<div>
|
<div>
|
||||||
<h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1>
|
<h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1>
|
||||||
<p><i18n.Translate>
|
<p><i18n.Translate>
|
||||||
@ -108,10 +100,12 @@ export function View({ talerWithdrawUri, details, cancelled, selectedExchange, a
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</WalletPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WithdrawPage({ talerWithdrawUri }: 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
|
string | undefined
|
||||||
@ -120,27 +114,44 @@ export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element {
|
|||||||
const [selecting, setSelecting] = useState(false);
|
const [selecting, setSelecting] = useState(false);
|
||||||
const [errMsg, setErrMsg] = useState<string | undefined>("");
|
const [errMsg, setErrMsg] = useState<string | undefined>("");
|
||||||
const [updateCounter, setUpdateCounter] = useState(1);
|
const [updateCounter, setUpdateCounter] = 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...')
|
||||||
setUpdateCounter(updateCounter + 1);
|
setUpdateCounter(updateCounter + 1);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
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 {
|
||||||
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) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);
|
}, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter, state]);
|
||||||
|
|
||||||
|
if (!talerWithdrawUri) {
|
||||||
|
return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
|
||||||
|
}
|
||||||
|
|
||||||
const accept = async (): Promise<void> => {
|
const accept = async (): Promise<void> => {
|
||||||
if (!talerWithdrawUri) return
|
|
||||||
if (!selectedExchange) {
|
if (!selectedExchange) {
|
||||||
throw Error("can't accept, no exchange selected");
|
throw Error("can't accept, no exchange selected");
|
||||||
}
|
}
|
||||||
@ -152,10 +163,16 @@ export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!details) {
|
||||||
|
return <span><i18n.Translate>Loading...</i18n.Translate></span>;
|
||||||
|
}
|
||||||
|
if (cancelled) {
|
||||||
|
return <span><i18n.Translate>Withdraw operation has been cancelled.</i18n.Translate></span>;
|
||||||
|
}
|
||||||
|
|
||||||
return <View accept={accept}
|
return <View accept={accept}
|
||||||
setCancelled={setCancelled} setSelecting={setSelecting}
|
setCancelled={setCancelled} setSelecting={setSelecting}
|
||||||
cancelled={cancelled} details={details} selectedExchange={selectedExchange}
|
details={details} selectedExchange={selectedExchange}
|
||||||
talerWithdrawUri={talerWithdrawUri}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,6 @@ import { RefundPage } from "./wallet/Refund";
|
|||||||
import { TipPage } from './wallet/Tip';
|
import { TipPage } from './wallet/Tip';
|
||||||
import Router, { route, Route } from "preact-router";
|
import Router, { route, Route } from "preact-router";
|
||||||
|
|
||||||
|
|
||||||
function main(): void {
|
function main(): void {
|
||||||
try {
|
try {
|
||||||
const container = document.getElementById("container");
|
const container = document.getElementById("container");
|
||||||
@ -67,64 +66,15 @@ enum Pages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Application() {
|
function Application() {
|
||||||
const sp = new URL(document.location.href).searchParams
|
const h = createHashHistory();
|
||||||
const queryParams: any = {}
|
return <Router history={h} >
|
||||||
sp.forEach((v, k) => { queryParams[k] = v; });
|
|
||||||
|
|
||||||
return <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={() => {
|
<Route path={Pages.tips} component={TipPage} />
|
||||||
return <section class="main">
|
<Route path={Pages.withdraw} component={WithdrawPage} />
|
||||||
<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>
|
|
||||||
<div>
|
|
||||||
<WelcomePage />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}} />
|
|
||||||
|
|
||||||
<Route path={Pages.pay} component={() => {
|
|
||||||
return <section class="main">
|
|
||||||
<h1>GNU Taler Wallet</h1>
|
|
||||||
<article class="fade">
|
|
||||||
<PayPage talerPayUri={queryParams.talerPayUri} />
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
}} />
|
|
||||||
|
|
||||||
<Route path={Pages.refund} component={() => {
|
|
||||||
return <section class="main">
|
|
||||||
<h1>GNU Taler Wallet</h1>
|
|
||||||
<article class="fade">
|
|
||||||
<RefundPage talerRefundUri={queryParams.talerRefundUri} />
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
}} />
|
|
||||||
|
|
||||||
<Route path={Pages.tips} component={() => {
|
|
||||||
return <section class="main">
|
|
||||||
<h1>GNU Taler Wallet</h1>
|
|
||||||
<div>
|
|
||||||
<TipPage talerTipUri={queryParams.talerTipUri} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}} />
|
|
||||||
<Route path={Pages.withdraw} component={() => {
|
|
||||||
return <section class="main">
|
|
||||||
<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>
|
|
||||||
<div class="fade">
|
|
||||||
<WithdrawPage talerWithdrawUri={queryParams.talerWithdrawUri} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}} />
|
|
||||||
|
|
||||||
<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>} />
|
||||||
|
@ -216,12 +216,8 @@ function makeSyncWalletRedirect(
|
|||||||
): Record<string, unknown> {
|
): Record<string, unknown> {
|
||||||
const innerUrl = new URL(chrome.extension.getURL(url));
|
const innerUrl = new URL(chrome.extension.getURL(url));
|
||||||
if (params) {
|
if (params) {
|
||||||
for (const key in params) {
|
const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&')
|
||||||
const p = params[key];
|
innerUrl.hash = innerUrl.hash + '?' + hParams
|
||||||
if (p) {
|
|
||||||
innerUrl.searchParams.set(key, p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (isFirefox()) {
|
if (isFirefox()) {
|
||||||
// Some platforms don't support the sync redirect (yet), so fall back to
|
// Some platforms don't support the sync redirect (yet), so fall back to
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
|
<link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
|
<link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/dist/styles.css" />
|
||||||
<link rel="icon" href="/static/img/icon.png" />
|
<link rel="icon" href="/static/img/icon.png" />
|
||||||
<script src="/dist/walletEntryPoint.js"></script>
|
<script src="/dist/walletEntryPoint.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
Loading…
Reference in New Issue
Block a user