aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-10-11 15:59:55 -0300
committerSebastian <sebasjm@gmail.com>2021-10-11 15:59:55 -0300
commitbe8e3f4b1d090a536967f132a7fd4742bbcd5343 (patch)
treed184ba3cf40510009e4121c6daa5653e0be475b0 /packages/taler-wallet-webextension/src
parent78fb5f79a8690ee490c96b271dadd37f4c9442d6 (diff)
downloadwallet-core-be8e3f4b1d090a536967f132a7fd4742bbcd5343.tar.xz
fixing withdrawal process
Diffstat (limited to 'packages/taler-wallet-webextension/src')
-rw-r--r--packages/taler-wallet-webextension/src/components/SelectList.tsx8
-rw-r--r--packages/taler-wallet-webextension/src/components/styled/index.tsx23
-rw-r--r--packages/taler-wallet-webextension/src/cta/Pay.tsx31
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx148
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw.tsx171
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts48
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useBalances.ts (renamed from packages/taler-wallet-webextension/src/hooks/useBalances.tsx)11
-rw-r--r--packages/taler-wallet-webextension/src/popup/Balance.stories.tsx128
-rw-r--r--packages/taler-wallet-webextension/src/popup/BalancePage.tsx129
-rw-r--r--packages/taler-wallet-webextension/src/popup/Debug.tsx1
-rw-r--r--packages/taler-wallet-webextension/src/popup/History.stories.tsx6
-rw-r--r--packages/taler-wallet-webextension/src/popup/History.tsx30
-rw-r--r--packages/taler-wallet-webextension/src/popup/Settings.tsx19
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx11
-rw-r--r--packages/taler-wallet-webextension/src/wallet/BalancePage.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Settings.tsx49
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts5
18 files changed, 604 insertions, 218 deletions
diff --git a/packages/taler-wallet-webextension/src/components/SelectList.tsx b/packages/taler-wallet-webextension/src/components/SelectList.tsx
index 7890c3fa4..536e5b89a 100644
--- a/packages/taler-wallet-webextension/src/components/SelectList.tsx
+++ b/packages/taler-wallet-webextension/src/components/SelectList.tsx
@@ -19,7 +19,7 @@ import { NiceSelect } from "./styled/index";
import { h } from "preact";
interface Props {
- value: string;
+ value?: string;
onChange: (s: string) => void;
label: string;
list: {
@@ -41,9 +41,11 @@ export function SelectList({ name, value, list, canBeNull, onChange, label, desc
console.log(e.currentTarget.value, value)
onChange(e.currentTarget.value)
}}>
- <option selected>
+ {value !== undefined ? <option selected>
{list[value]}
- </option>
+ </option> : <option selected disabled>
+ Select one option
+ </option>}
{Object.keys(list)
.filter((l) => l !== value)
.map(key => <option value={key} key={key}>{list[key]}</option>)
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index e77e7d542..7c3bb3943 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -129,6 +129,12 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
}
}
`
+export const Middle = styled.div`
+ justify-content: space-around;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+`
export const PopupBox = styled.div<{ noPadding?: boolean }>`
height: 290px;
@@ -138,11 +144,10 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>`
justify-content: space-between;
& > section {
- padding-left: ${({ noPadding }) => noPadding ? '0px' : '8px'};
- padding-right: ${({ noPadding }) => noPadding ? '0px' : '8px'};
+ padding: ${({ noPadding }) => noPadding ? '0px' : '8px'};
// this margin will send the section up when used with a header
margin-bottom: auto;
- overflow: auto;
+ overflow-y: auto;
table td {
padding: 5px 10px;
@@ -153,6 +158,16 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>`
}
}
+ & > section[data-expanded] {
+ height: 100%;
+ }
+
+ & > section[data-centered] {
+ justify-content: center;
+ display: flex;
+ /* flex-direction: column; */
+ }
+
& > header {
flex-direction: row;
justify-content: space-between;
@@ -596,7 +611,7 @@ export const NiceSelect = styled.div`
position: relative;
display: flex;
- width: 10em;
+ /* width: 10em; */
overflow: hidden;
border-radius: .25em;
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx
index 8e02cf6bb..675b14ff9 100644
--- a/packages/taler-wallet-webextension/src/cta/Pay.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx
@@ -88,7 +88,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
const [payErrMsg, setPayErrMsg] = useState<string | undefined>(undefined);
const balance = useBalances()
- const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || [])
+ const balanceWithoutError = balance?.hasError ? [] : (balance?.response.balances || [])
const foundBalance = balanceWithoutError.find(b => payStatus && Amounts.parseOrThrow(b.available).currency === Amounts.parseOrThrow(payStatus?.amountRaw).currency)
const foundAmount = foundBalance ? Amounts.parseOrThrow(foundBalance.available) : undefined
@@ -143,17 +143,21 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
}
- return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} balance={foundAmount} />;
+ return <PaymentRequestView uri={talerPayUri}
+ payStatus={payStatus} payResult={payResult}
+ onClick={onClick} payErrMsg={payErrMsg}
+ balance={foundAmount} />;
}
export interface PaymentRequestViewProps {
payStatus: PreparePayResult;
+ payResult?: ConfirmPayResult;
onClick: () => void;
payErrMsg?: string;
uri: string;
balance: AmountJson | undefined;
}
-export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance }: PaymentRequestViewProps) {
+export function PaymentRequestView({ uri, payStatus, payResult, onClick, payErrMsg, balance }: PaymentRequestViewProps) {
let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw);
const contractTerms: ContractTerms = payStatus.contractTerms;
@@ -195,6 +199,16 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance
}
function ButtonsSection() {
+ if (payResult) {
+ if (payResult.type === ConfirmPayResultType.Pending) {
+ return <section>
+ <div>
+ <p>Processing...</p>
+ </div>
+ </section>
+ }
+ return null
+ }
if (payErrMsg) {
return <section>
<div>
@@ -208,7 +222,7 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance
if (payStatus.status === PreparePayResultType.PaymentPossible) {
return <Fragment>
<section>
- <ButtonSuccess upperCased>
+ <ButtonSuccess upperCased onClick={onClick}>
{i18n.str`Pay`} {amountToString(payStatus.amountEffective)}
</ButtonSuccess>
</section>
@@ -252,6 +266,15 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance
{payStatus.status === PreparePayResultType.AlreadyConfirmed &&
(payStatus.paid ? <SuccessBox> Already paid </SuccessBox> : <WarningBox> Already claimed </WarningBox>)
}
+ {payResult && payResult.type === ConfirmPayResultType.Done && (
+ <SuccessBox>
+ <h3>Payment complete</h3>
+ <p>{!payResult.contractTerms.fulfillment_message ?
+ "You will now be sent back to the merchant you came from." :
+ payResult.contractTerms.fulfillment_message
+ }</p>
+ </SuccessBox>
+ )}
<section>
{payStatus.status !== PreparePayResultType.InsufficientBalance && Amounts.isNonZero(totalFees) &&
<Part big title="Total to pay" text={amountToString(payStatus.amountEffective)} kind='negative' />
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx
index 94fdea8fb..69073f500 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx
@@ -31,6 +31,7 @@ export default {
title: 'cta/withdraw',
component: TestedComponent,
argTypes: {
+ onSwitchExchange: { action: 'onRetry' },
},
};
@@ -381,6 +382,15 @@ const termsXml = `<?xml version="1.0" encoding="utf-8"?>
`;
export const WithdrawNewTermsXML = createExample(TestedComponent, {
+ knownExchanges: [{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.demo.taler.net',
+ paytoUris: ['asd'],
+ },{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.test.taler.net',
+ paytoUris: ['asd'],
+ }],
details: {
exchangeInfo: {
baseUrl: 'exchange.demo.taler.net'
@@ -391,9 +401,15 @@ export const WithdrawNewTermsXML = createExample(TestedComponent, {
value: 0
},
} as ExchangeWithdrawDetails,
- amount: 'USD:2',
+ amount: {
+ currency: 'USD',
+ value: 2,
+ fraction: 10000000
+ },
+
+ onSwitchExchange: async () => { },
terms: {
- value : {
+ value: {
type: 'xml',
document: new DOMParser().parseFromString(termsXml, "text/xml"),
},
@@ -402,6 +418,15 @@ export const WithdrawNewTermsXML = createExample(TestedComponent, {
})
export const WithdrawNewTermsReviewingXML = createExample(TestedComponent, {
+ knownExchanges: [{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.demo.taler.net',
+ paytoUris: ['asd'],
+ },{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.test.taler.net',
+ paytoUris: ['asd'],
+ }],
details: {
exchangeInfo: {
baseUrl: 'exchange.demo.taler.net'
@@ -412,9 +437,15 @@ export const WithdrawNewTermsReviewingXML = createExample(TestedComponent, {
value: 0
},
} as ExchangeWithdrawDetails,
- amount: 'USD:2',
+ amount: {
+ currency: 'USD',
+ value: 2,
+ fraction: 10000000
+ },
+
+ onSwitchExchange: async () => { },
terms: {
- value : {
+ value: {
type: 'xml',
document: new DOMParser().parseFromString(termsXml, "text/xml"),
},
@@ -424,6 +455,15 @@ export const WithdrawNewTermsReviewingXML = createExample(TestedComponent, {
})
export const WithdrawNewTermsAcceptedXML = createExample(TestedComponent, {
+ knownExchanges: [{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.demo.taler.net',
+ paytoUris: ['asd'],
+ },{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.test.taler.net',
+ paytoUris: ['asd'],
+ }],
details: {
exchangeInfo: {
baseUrl: 'exchange.demo.taler.net'
@@ -434,9 +474,14 @@ export const WithdrawNewTermsAcceptedXML = createExample(TestedComponent, {
value: 0
},
} as ExchangeWithdrawDetails,
- amount: 'USD:2',
+ amount: {
+ currency: 'USD',
+ value: 2,
+ fraction: 10000000
+ },
+ onSwitchExchange: async () => { },
terms: {
- value : {
+ value: {
type: 'xml',
document: new DOMParser().parseFromString(termsXml, "text/xml"),
},
@@ -446,6 +491,15 @@ export const WithdrawNewTermsAcceptedXML = createExample(TestedComponent, {
})
export const WithdrawNewTermsShowAfterAcceptedXML = createExample(TestedComponent, {
+ knownExchanges: [{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.demo.taler.net',
+ paytoUris: ['asd'],
+ },{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.test.taler.net',
+ paytoUris: ['asd'],
+ }],
details: {
exchangeInfo: {
baseUrl: 'exchange.demo.taler.net'
@@ -456,9 +510,15 @@ export const WithdrawNewTermsShowAfterAcceptedXML = createExample(TestedComponen
value: 0
},
} as ExchangeWithdrawDetails,
- amount: 'USD:2',
+ amount: {
+ currency: 'USD',
+ value: 2,
+ fraction: 10000000
+ },
+
+ onSwitchExchange: async () => { },
terms: {
- value : {
+ value: {
type: 'xml',
document: new DOMParser().parseFromString(termsXml, "text/xml"),
},
@@ -469,6 +529,15 @@ export const WithdrawNewTermsShowAfterAcceptedXML = createExample(TestedComponen
})
export const WithdrawChangedTermsXML = createExample(TestedComponent, {
+ knownExchanges: [{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.demo.taler.net',
+ paytoUris: ['asd'],
+ },{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.test.taler.net',
+ paytoUris: ['asd'],
+ }],
details: {
exchangeInfo: {
baseUrl: 'exchange.demo.taler.net'
@@ -479,9 +548,15 @@ export const WithdrawChangedTermsXML = createExample(TestedComponent, {
value: 0
},
} as ExchangeWithdrawDetails,
- amount: 'USD:2',
+ amount: {
+ currency: 'USD',
+ value: 2,
+ fraction: 10000000
+ },
+
+ onSwitchExchange: async () => { },
terms: {
- value : {
+ value: {
type: 'xml',
document: new DOMParser().parseFromString(termsXml, "text/xml"),
},
@@ -490,6 +565,15 @@ export const WithdrawChangedTermsXML = createExample(TestedComponent, {
})
export const WithdrawNotFoundTermsXML = createExample(TestedComponent, {
+ knownExchanges: [{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.demo.taler.net',
+ paytoUris: ['asd'],
+ },{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.test.taler.net',
+ paytoUris: ['asd'],
+ }],
details: {
exchangeInfo: {
baseUrl: 'exchange.demo.taler.net'
@@ -500,13 +584,28 @@ export const WithdrawNotFoundTermsXML = createExample(TestedComponent, {
value: 0
},
} as ExchangeWithdrawDetails,
- amount: 'USD:2',
+ amount: {
+ currency: 'USD',
+ value: 2,
+ fraction: 10000000
+ },
+
+ onSwitchExchange: async () => { },
terms: {
status: 'notfound'
},
})
export const WithdrawAcceptedTermsXML = createExample(TestedComponent, {
+ knownExchanges: [{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.demo.taler.net',
+ paytoUris: ['asd'],
+ },{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.test.taler.net',
+ paytoUris: ['asd'],
+ }],
details: {
exchangeInfo: {
baseUrl: 'exchange.demo.taler.net'
@@ -517,7 +616,13 @@ export const WithdrawAcceptedTermsXML = createExample(TestedComponent, {
value: 0
},
} as ExchangeWithdrawDetails,
- amount: 'USD:2',
+ amount: {
+ currency: 'USD',
+ value: 2,
+ fraction: 10000000
+ },
+
+ onSwitchExchange: async () => { },
terms: {
status: 'accepted'
},
@@ -525,6 +630,15 @@ export const WithdrawAcceptedTermsXML = createExample(TestedComponent, {
export const WithdrawAcceptedTermsWithoutFee = createExample(TestedComponent, {
+ knownExchanges: [{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.demo.taler.net',
+ paytoUris: ['asd'],
+ },{
+ currency: 'USD',
+ exchangeBaseUrl: 'exchange.test.taler.net',
+ paytoUris: ['asd'],
+ }],
details: {
exchangeInfo: {
baseUrl: 'exchange.demo.taler.net'
@@ -535,9 +649,15 @@ export const WithdrawAcceptedTermsWithoutFee = createExample(TestedComponent, {
value: 0
},
} as ExchangeWithdrawDetails,
- amount: 'USD:2',
+ amount: {
+ currency: 'USD',
+ value: 2,
+ fraction: 10000000
+ },
+
+ onSwitchExchange: async () => { },
terms: {
- value : {
+ value: {
type: 'xml',
document: new DOMParser().parseFromString(termsXml, "text/xml"),
},
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
index 46451e72c..52295f1af 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
@@ -21,18 +21,21 @@
* @author Florian Dold
*/
-import { AmountLike, Amounts, i18n, WithdrawUriInfoResponse } from '@gnu-taler/taler-util';
+import { AmountJson, Amounts, ExchangeListItem, i18n, WithdrawUriInfoResponse } from '@gnu-taler/taler-util';
import { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw';
-import { useEffect, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
+import { Fragment } from 'preact/jsx-runtime';
import { CheckboxOutlined } from '../components/CheckboxOutlined';
import { ExchangeXmlTos } from '../components/ExchangeToS';
import { LogoHeader } from '../components/LogoHeader';
import { Part } from '../components/Part';
-import { ButtonDestructive, ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, TermsOfService, WalletAction } from '../components/styled';
+import { SelectList } from '../components/SelectList';
+import { ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, TermsOfService, WalletAction } from '../components/styled';
+import { useAsyncAsHook } from '../hooks/useAsyncAsHook';
import {
- acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification, setExchangeTosAccepted
+ acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, setExchangeTosAccepted, listExchanges
} from "../wxApi";
-import { h } from 'preact';
+import { wxMain } from '../wxBackend.js';
interface Props {
talerWithdrawUri?: string;
@@ -40,7 +43,8 @@ interface Props {
export interface ViewProps {
details: ExchangeWithdrawDetails;
- amount: string;
+ amount: AmountJson;
+ onSwitchExchange: (ex: string) => void;
onWithdraw: () => Promise<void>;
onReview: (b: boolean) => void;
onAccept: (b: boolean) => void;
@@ -50,7 +54,8 @@ export interface ViewProps {
terms: {
value?: TermsDocument;
status: TermsStatus;
- }
+ },
+ knownExchanges: ExchangeListItem[]
};
@@ -68,15 +73,18 @@ interface TermsDocumentHtml {
href: string,
}
-function amountToString(text: AmountLike) {
+function amountToString(text: AmountJson) {
const aj = Amounts.jsonifyAmount(text)
const amount = Amounts.stringifyValue(aj)
return `${amount} ${aj.currency}`
}
-export function View({ details, amount, onWithdraw, terms, reviewing, onReview, onAccept, accepted, confirmed }: ViewProps) {
+export function View({ details, knownExchanges, amount, onWithdraw, onSwitchExchange, terms, reviewing, onReview, onAccept, accepted, confirmed }: ViewProps) {
const needsReview = terms.status === 'changed' || terms.status === 'new'
+ const [switchingExchange, setSwitchingExchange] = useState<string | undefined>(undefined)
+ const exchanges = knownExchanges.reduce((prev, ex) => ({ ...prev, [ex.exchangeBaseUrl]: ex.exchangeBaseUrl }), {})
+
return (
<WalletAction>
<LogoHeader />
@@ -84,7 +92,7 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview,
{i18n.str`Digital cash withdrawal`}
</h2>
<section>
- <Part title="Total to withdraw" text={amountToString(Amounts.sub(Amounts.parseOrThrow(amount), details.withdrawFee).amount)} kind='positive' />
+ <Part title="Total to withdraw" text={amountToString(Amounts.sub(amount, details.withdrawFee).amount)} kind='positive' />
<Part title="Chosen amount" text={amountToString(amount)} kind='neutral' />
{Amounts.isNonZero(details.withdrawFee) &&
<Part title="Exchange fee" text={amountToString(details.withdrawFee)} kind='negative' />
@@ -93,11 +101,21 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview,
</section>
{!reviewing &&
<section>
- <LinkSuccess
- upperCased
- >
- {i18n.str`Edit exchange`}
- </LinkSuccess>
+ {switchingExchange !== undefined ? <Fragment>
+ <div>
+ <SelectList label="Known exchanges" list={exchanges} name="" onChange={onSwitchExchange} />
+ </div>
+ <p>
+ This is the list of known exchanges
+ </p>
+ <LinkSuccess upperCased onClick={() => onSwitchExchange(switchingExchange)}>
+ {i18n.str`Confirm exchange selection`}
+ </LinkSuccess>
+ </Fragment>
+ : <LinkSuccess upperCased onClick={() => setSwitchingExchange("")}>
+ {i18n.str`Switch exchange`}
+ </LinkSuccess>}
+
</section>
}
{!reviewing && accepted &&
@@ -140,6 +158,9 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview,
</section>
}
+ {/**
+ * Main action section
+ */}
<section>
{terms.status === 'new' && !accepted && !reviewing &&
<ButtonSuccess
@@ -178,80 +199,55 @@ export function View({ details, amount, onWithdraw, terms, reviewing, onReview,
)
}
-export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element {
- const [uriInfo, setUriInfo] = useState<WithdrawUriInfoResponse | undefined>(undefined);
- const [details, setDetails] = useState<ExchangeWithdrawDetails | undefined>(undefined);
- const [cancelled, setCancelled] = useState(false);
- const [selecting, setSelecting] = useState(false);
- const [error, setError] = useState<boolean>(false);
- const [updateCounter, setUpdateCounter] = useState(1);
+export function WithdrawPageWithParsedURI({ uri, uriInfo }: { uri: string, uriInfo: WithdrawUriInfoResponse }) {
+ const [customExchange, setCustomExchange] = useState<string | undefined>(undefined)
+ const [errorAccepting, setErrorAccepting] = useState<string | undefined>(undefined)
+
const [reviewing, setReviewing] = useState<boolean>(false)
const [accepted, setAccepted] = useState<boolean>(false)
const [confirmed, setConfirmed] = useState<boolean>(false)
- useEffect(() => {
- return onUpdateNotification(() => {
- console.log('updating...')
- setUpdateCounter(updateCounter + 1);
- });
- }, []);
-
- useEffect(() => {
- console.log('on effect yes', talerWithdrawUri)
- if (!talerWithdrawUri) return
- const fetchData = async (): Promise<void> => {
- try {
- const res = await getWithdrawalDetailsForUri({ talerWithdrawUri });
- setUriInfo(res);
- } catch (e) {
- console.error('error', JSON.stringify(e, undefined, 2))
- setError(true)
- }
- };
- fetchData();
- }, [selecting, talerWithdrawUri, updateCounter]);
+ const knownExchangesHook = useAsyncAsHook(() => listExchanges())
- useEffect(() => {
- async function fetchData() {
- if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return
- try {
- const res = await getExchangeWithdrawalInfo({
- exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl,
- amount: Amounts.parseOrThrow(uriInfo.amount),
- tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf']
- })
- setDetails(res)
- } catch (e) {
- setError(true)
- }
- }
- fetchData()
- }, [uriInfo])
+ const knownExchanges = !knownExchangesHook || knownExchangesHook.hasError ? [] : knownExchangesHook.response.exchanges
+ const withdrawAmount = Amounts.parseOrThrow(uriInfo.amount)
+ const thisCurrencyExchanges = knownExchanges.filter(ex => ex.currency === withdrawAmount.currency)
- if (!talerWithdrawUri) {
- return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
+ const exchange = customExchange || uriInfo.defaultExchangeBaseUrl || thisCurrencyExchanges[0]?.exchangeBaseUrl
+ const detailsHook = useAsyncAsHook(async () => {
+ if (!exchange) throw Error('no default exchange')
+ return getExchangeWithdrawalInfo({
+ exchangeBaseUrl: exchange,
+ amount: withdrawAmount,
+ tosAcceptedFormat: ['text/json', 'text/xml', 'text/pdf']
+ })
+ })
+
+ if (!detailsHook) {
+ return <span><i18n.Translate>Getting withdrawal details.</i18n.Translate></span>;
+ }
+ if (detailsHook.hasError) {
+ return <span><i18n.Translate>Problems getting details: {detailsHook.message}</i18n.Translate></span>;
}
+ const details = detailsHook.response
+
const onAccept = async (): Promise<void> => {
- if (!details) {
- throw Error("can't accept, no exchange selected");
- }
try {
- await setExchangeTosAccepted(details.exchangeDetails.exchangeBaseUrl, details.tosRequested?.tosEtag)
+ await setExchangeTosAccepted(details.exchangeInfo.baseUrl, details.tosRequested?.tosEtag)
setAccepted(true)
} catch (e) {
- setError(true)
+ if (e instanceof Error) {
+ setErrorAccepting(e.message)
+ }
}
}
const onWithdraw = async (): Promise<void> => {
- if (!details) {
- throw Error("can't accept, no exchange selected");
- }
setConfirmed(true)
- console.log("accepting exchange", details.exchangeInfo.baseUrl);
+ console.log("accepting exchange", details.exchangeDetails.exchangeBaseUrl);
try {
- const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl);
+ const res = await acceptWithdrawal(uri, details.exchangeInfo.baseUrl);
console.log("accept withdrawal response", res);
if (res.confirmTransferUrl) {
document.location.href = res.confirmTransferUrl;
@@ -261,19 +257,6 @@ 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>;
- }
- if (!uriInfo) {
- return <span><i18n.Translate>Loading...</i18n.Translate></span>;
- }
- if (!details) {
- return <span><i18n.Translate>Getting withdrawal details.</i18n.Translate></span>;
- }
-
let termsContent: TermsDocument | undefined = undefined;
if (details.tosRequested) {
if (details.tosRequested.tosContentType === 'text/xml') {
@@ -295,14 +278,32 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element
return <View onWithdraw={onWithdraw}
// setCancelled={setCancelled} setSelecting={setSelecting}
- details={details} amount={uriInfo.amount}
+ details={details} amount={withdrawAmount}
terms={{
status, value: termsContent
}}
+ onSwitchExchange={setCustomExchange}
+ knownExchanges={knownExchanges}
confirmed={confirmed}
accepted={accepted} onAccept={onAccept}
reviewing={reviewing} onReview={setReviewing}
// terms={[]}
/>
}
+export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element {
+ const uriInfoHook = useAsyncAsHook(() => !talerWithdrawUri ? Promise.reject(undefined) :
+ getWithdrawalDetailsForUri({ talerWithdrawUri })
+ )
+
+ if (!talerWithdrawUri) {
+ return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
+ }
+ if (!uriInfoHook) {
+ return <span><i18n.Translate>Loading...</i18n.Translate></span>;
+ }
+ if (uriInfoHook.hasError) {
+ return <span><i18n.Translate>This URI is not valid anymore: {uriInfoHook.message}</i18n.Translate></span>;
+ }
+ return <WithdrawPageWithParsedURI uri={talerWithdrawUri} uriInfo={uriInfoHook.response} />
+}
diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
new file mode 100644
index 000000000..2131d45cb
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
@@ -0,0 +1,48 @@
+/*
+ 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 { ExchangesListRespose } from "@gnu-taler/taler-util";
+import { useEffect, useState } from "preact/hooks";
+import * as wxApi from "../wxApi";
+
+interface HookOk<T> {
+ hasError: false;
+ response: T;
+}
+
+interface HookError {
+ hasError: true;
+ message: string;
+}
+
+export type HookResponse<T> = HookOk<T> | HookError | undefined;
+
+export function useAsyncAsHook<T> (fn: (() => Promise<T>)): HookResponse<T> {
+ const [result, setHookResponse] = useState<HookResponse<T>>(undefined);
+ useEffect(() => {
+ async function doAsync() {
+ try {
+ const response = await fn();
+ setHookResponse({ hasError: false, response });
+ } catch (e) {
+ if (e instanceof Error) {
+ setHookResponse({ hasError: true, message: e.message });
+ }
+ }
+ }
+ doAsync()
+ }, []);
+ return result;
+}
diff --git a/packages/taler-wallet-webextension/src/hooks/useBalances.tsx b/packages/taler-wallet-webextension/src/hooks/useBalances.ts
index 503b7a492..37424fb05 100644
--- a/packages/taler-wallet-webextension/src/hooks/useBalances.tsx
+++ b/packages/taler-wallet-webextension/src/hooks/useBalances.ts
@@ -20,12 +20,13 @@ import * as wxApi from "../wxApi";
interface BalancesHookOk {
- error: false;
+ hasError: false;
response: BalancesResponse;
}
interface BalancesHookError {
- error: true;
+ hasError: true;
+ message: string;
}
export type BalancesHook = BalancesHookOk | BalancesHookError | undefined;
@@ -37,10 +38,12 @@ export function useBalances(): BalancesHook {
try {
const response = await wxApi.getBalance();
console.log("got balance", balance);
- setBalance({ error: false, response });
+ setBalance({ hasError: false, response });
} catch (e) {
console.error("could not retrieve balances", e);
- setBalance({ error: true });
+ if (e instanceof Error) {
+ setBalance({ hasError: true, message: e.message });
+ }
}
}
checkBalance()
diff --git a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
index a0655d379..382f9b549 100644
--- a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
@@ -35,14 +35,15 @@ export const NotYetLoaded = createExample(TestedComponent, {
export const GotError = createExample(TestedComponent, {
balance: {
- error: true
+ hasError: true,
+ message: 'Network error'
},
Linker: NullLink,
});
export const EmptyBalance = createExample(TestedComponent, {
balance: {
- error: false,
+ hasError: false,
response: {
balances: []
},
@@ -52,7 +53,7 @@ export const EmptyBalance = createExample(TestedComponent, {
export const SomeCoins = createExample(TestedComponent, {
balance: {
- error: false,
+ hasError: false,
response: {
balances: [{
available: 'USD:10.5',
@@ -68,7 +69,7 @@ export const SomeCoins = createExample(TestedComponent, {
export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, {
balance: {
- error: false,
+ hasError: false,
response: {
balances: [{
available: 'USD:2.23',
@@ -82,22 +83,135 @@ export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, {
Linker: NullLink,
});
+export const SomeCoinsAndOutgoingMoney = createExample(TestedComponent, {
+ balance: {
+ hasError: false,
+ response: {
+ balances: [{
+ available: 'USD:2.23',
+ hasPendingTransactions: false,
+ pendingIncoming: 'USD:0',
+ pendingOutgoing: 'USD:5.11',
+ requiresUserInput: false
+ }]
+ },
+ },
+ Linker: NullLink,
+});
+
+export const SomeCoinsAndMovingMoney = createExample(TestedComponent, {
+ balance: {
+ hasError: false,
+ response: {
+ balances: [{
+ available: 'USD:2.23',
+ hasPendingTransactions: false,
+ pendingIncoming: 'USD:2',
+ pendingOutgoing: 'USD:5.11',
+ requiresUserInput: false
+ }]
+ },
+ },
+ Linker: NullLink,
+});
+
export const SomeCoinsInTwoCurrencies = createExample(TestedComponent, {
balance: {
- error: false,
+ hasError: false,
response: {
balances: [{
available: 'USD:2',
hasPendingTransactions: false,
- pendingIncoming: 'USD:5',
+ pendingIncoming: 'USD:5.1',
pendingOutgoing: 'USD:0',
requiresUserInput: false
},{
available: 'EUR:4',
hasPendingTransactions: false,
- pendingIncoming: 'EUR:5',
+ pendingIncoming: 'EUR:0',
+ pendingOutgoing: 'EUR:3.01',
+ requiresUserInput: false
+ }]
+ },
+ },
+ Linker: NullLink,
+});
+
+export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
+ balance: {
+ hasError: false,
+ response: {
+ balances: [{
+ available: 'USD:1',
+ hasPendingTransactions: false,
+ pendingIncoming: 'USD:0',
+ pendingOutgoing: 'USD:0',
+ requiresUserInput: false
+ },{
+ available: 'COL:2000',
+ hasPendingTransactions: false,
+ pendingIncoming: 'USD:0',
+ pendingOutgoing: 'USD:0',
+ requiresUserInput: false
+ },{
+ available: 'EUR:4',
+ hasPendingTransactions: false,
+ pendingIncoming: 'EUR:15',
+ pendingOutgoing: 'EUR:0',
+ requiresUserInput: false
+ }]
+ },
+ },
+ Linker: NullLink,
+});
+
+
+export const SomeCoinsInFiveCurrencies = createExample(TestedComponent, {
+ balance: {
+ hasError: false,
+ response: {
+ balances: [{
+ available: 'USD:13451',
+ hasPendingTransactions: false,
+ pendingIncoming: 'USD:0',
+ pendingOutgoing: 'USD:0',
+ requiresUserInput: false
+ },{
+ available: 'EUR:202.02',
+ hasPendingTransactions: false,
+ pendingIncoming: 'EUR:0',
+ pendingOutgoing: 'EUR:0',
+ requiresUserInput: false
+ },{
+ available: 'ARS:30',
+ hasPendingTransactions: false,
+ pendingIncoming: 'USD:0',
+ pendingOutgoing: 'USD:0',
+ requiresUserInput: false
+ },{
+ available: 'JPY:51223233',
+ hasPendingTransactions: false,
+ pendingIncoming: 'EUR:0',
+ pendingOutgoing: 'EUR:0',
+ requiresUserInput: false
+ },{
+ available: 'JPY:51223233',
+ hasPendingTransactions: false,
+ pendingIncoming: 'EUR:0',
pendingOutgoing: 'EUR:0',
requiresUserInput: false
+ },{
+ available: 'DEMOKUDOS:6',
+ hasPendingTransactions: false,
+ pendingIncoming: 'USD:0',
+ pendingOutgoing: 'USD:0',
+ requiresUserInput: false
+ },{
+ available: 'TESTKUDOS:6',
+ hasPendingTransactions: false,
+ pendingIncoming: 'USD:5',
+ pendingOutgoing: 'USD:0',
+ requiresUserInput: false
}]
},
},
diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
index e3bada8d4..8e5c5c42e 100644
--- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
@@ -19,8 +19,9 @@ import {
Balance, BalancesResponse,
i18n
} from "@gnu-taler/taler-util";
-import { JSX, h } from "preact";
-import { PopupBox, Centered, ButtonPrimary } from "../components/styled/index";
+import { JSX, h, Fragment } from "preact";
+import { ErrorMessage } from "../components/ErrorMessage";
+import { PopupBox, Centered, ButtonPrimary, ErrorBox, Middle } from "../components/styled/index";
import { BalancesHook, useBalances } from "../hooks/useBalances";
import { PageLink, renderAmount } from "../renderHtml";
@@ -34,34 +35,6 @@ export interface BalanceViewProps {
Linker: typeof PageLink;
goToWalletManualWithdraw: () => void;
}
-export function BalanceView({ balance, Linker, goToWalletManualWithdraw }: BalanceViewProps) {
- if (!balance) {
- return <span />
- }
-
- if (balance.error) {
- return (
- <div>
- <p>{i18n.str`Error: could not retrieve balance information.`}</p>
- <p>
- Click <Linker pageName="welcome">here</Linker> for help and
- diagnostics.
- </p>
- </div>
- )
- }
- if (balance.response.balances.length === 0) {
- return (
- <p><i18n.Translate>
- You have no balance to show. Need some{" "}
- <Linker pageName="/welcome">help</Linker> getting started?
- </i18n.Translate></p>
- )
- }
- return <ShowBalances wallet={balance.response}
- onWithdraw={goToWalletManualWithdraw}
- />
-}
function formatPending(entry: Balance): JSX.Element {
let incoming: JSX.Element | undefined;
@@ -74,11 +47,20 @@ function formatPending(entry: Balance): JSX.Element {
if (!Amounts.isZero(pendingIncoming)) {
incoming = (
<span><i18n.Translate>
- <span style={{ color: "darkgreen" }}>
+ <span style={{ color: "darkgreen" }} title="incoming amount">
{"+"}
{renderAmount(entry.pendingIncoming)}
</span>{" "}
- incoming
+ </i18n.Translate></span>
+ );
+ }
+ if (!Amounts.isZero(pendingOutgoing)) {
+ payment = (
+ <span><i18n.Translate>
+ <span style={{ color: "darkred" }} title="outgoing amount">
+ {"-"}
+ {renderAmount(entry.pendingOutgoing)}
+ </span>{" "}
</i18n.Translate></span>
);
}
@@ -89,36 +71,85 @@ function formatPending(entry: Balance): JSX.Element {
}
if (l.length === 1) {
- return <span>({l})</span>;
+ return <span>{l}</span>;
}
return (
<span>
- ({l[0]}, {l[1]})
+ {l[0]}, {l[1]}
</span>
);
}
-function ShowBalances({ wallet, onWithdraw }: { wallet: BalancesResponse, onWithdraw: () => void }) {
- return <PopupBox>
- <section>
- <Centered>{wallet.balances.map((entry) => {
+export function BalanceView({ balance, Linker, goToWalletManualWithdraw }: BalanceViewProps) {
+
+ function Content() {
+ if (!balance) {
+ return <span />
+ }
+
+ if (balance.hasError) {
+ return (<section>
+ <ErrorBox>{balance.message}</ErrorBox>
+ <p>
+ Click <Linker pageName="welcome">here</Linker> for help and
+ diagnostics.
+ </p>
+ </section>)
+ }
+ if (balance.response.balances.length === 0) {
+ return (<section data-expanded>
+ <Middle>
+ <p><i18n.Translate>
+ You have no balance to show. Need some{" "}
+ <Linker pageName="/welcome">help</Linker> getting started?
+ </i18n.Translate></p>
+ </Middle>
+ </section>)
+ }
+ return <section data-expanded data-centered>
+ <table style={{width:'100%'}}>{balance.response.balances.map((entry) => {
const av = Amounts.parseOrThrow(entry.available);
- const v = av.value + av.fraction / amountFractionalBase;
- return (
- <p key={av.currency}>
- <span>
- <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
- <span>{av.currency}</span>
- </span>
- {formatPending(entry)}
- </p>
+ // Create our number formatter.
+ let formatter;
+ try {
+ formatter = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: av.currency,
+ currencyDisplay: 'symbol'
+ // These options are needed to round to whole numbers if that's what you want.
+ //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
+ //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
+ });
+ } catch {
+ formatter = new Intl.NumberFormat('en-US', {
+ // style: 'currency',
+ // currency: av.currency,
+ // These options are needed to round to whole numbers if that's what you want.
+ //minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
+ //maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
+ });
+ }
+
+ const v = formatter.format(av.value + av.fraction / amountFractionalBase);
+ const fontSize = v.length < 8 ? '3em' : (v.length < 13 ? '2em' : '1em')
+ return (<tr>
+ <td style={{ height: 50, fontSize, width: '60%', textAlign: 'right', padding: 0 }}>{v}</td>
+ <td style={{ maxWidth: '2em', overflowX: 'hidden' }}>{av.currency}</td>
+ <td style={{ fontSize: 'small', color: 'gray' }}>{formatPending(entry)}</td>
+ </tr>
);
- })}</Centered>
+ })}</table>
</section>
+ }
+
+ return <PopupBox>
+ {/* <section> */}
+ <Content />
+ {/* </section> */}
<footer>
<div />
- <ButtonPrimary onClick={onWithdraw} >Withdraw</ButtonPrimary>
+ <ButtonPrimary onClick={goToWalletManualWithdraw}>Withdraw</ButtonPrimary>
</footer>
</PopupBox>
}
diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx b/packages/taler-wallet-webextension/src/popup/Debug.tsx
index 3968b0191..ccc747466 100644
--- a/packages/taler-wallet-webextension/src/popup/Debug.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Debug.tsx
@@ -28,7 +28,6 @@ export function DeveloperPage(props: any): JSX.Element {
<button onClick={openExtensionPage("/static/popup.html")}>wallet tab</button>
<br />
<button onClick={confirmReset}>reset</button>
- <button onClick={reload}>reload chrome extension</button>
<Diagnostics diagnostics={status} timedOut={timedOut} />
</div>
);
diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
index ca9f545fe..daa263a81 100644
--- a/packages/taler-wallet-webextension/src/popup/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
@@ -105,7 +105,7 @@ const exampleData = {
} as TransactionRefund,
}
-export const Empty = createExample(TestedComponent, {
+export const EmptyWithBalance = createExample(TestedComponent, {
list: [],
balances: [{
available: 'TESTKUDOS:10',
@@ -116,6 +116,10 @@ export const Empty = createExample(TestedComponent, {
}]
});
+export const EmptyWithNoBalance = createExample(TestedComponent, {
+ list: [],
+ balances: []
+});
export const One = createExample(TestedComponent, {
list: [exampleData.withdraw],
diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx b/packages/taler-wallet-webextension/src/popup/History.tsx
index 77d603886..1447da9b0 100644
--- a/packages/taler-wallet-webextension/src/popup/History.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.tsx
@@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { AmountString, Balance, Transaction, TransactionsResponse } from "@gnu-taler/taler-util";
+import { AmountString, Balance, i18n, Transaction, TransactionsResponse } from "@gnu-taler/taler-util";
import { h, JSX } from "preact";
import { useEffect, useState } from "preact/hooks";
import { PopupBox } from "../components/styled";
@@ -28,7 +28,7 @@ export function HistoryPage(props: any): JSX.Element {
TransactionsResponse | undefined
>(undefined);
const balance = useBalances()
- const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || [])
+ const balanceWithoutError = balance?.hasError ? [] : (balance?.response.balances || [])
useEffect(() => {
const fetchData = async (): Promise<void> => {
@@ -64,16 +64,24 @@ export function HistoryView({ list, balances }: { list: Transaction[], balances:
Balance: <span>{amountToString(balances[0].available)}</span>
</div>}
</header>}
- <section>
- {list.slice(0, 3).map((tx, i) => (
- <TransactionItem key={i} tx={tx} multiCurrency={multiCurrency}/>
- ))}
- </section>
+ {list.length === 0 ? <section data-expanded data-centered>
+ <p><i18n.Translate>
+ You have no history yet, here you will be able to check your last transactions.
+ </i18n.Translate></p>
+ </section> :
+ <section>
+ {list.slice(0, 3).map((tx, i) => (
+ <TransactionItem key={i} tx={tx} multiCurrency={multiCurrency} />
+ ))}
+ </section>
+ }
<footer style={{ justifyContent: 'space-around' }}>
- <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>
+ {list.length > 0 &&
+ <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>
}
diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx b/packages/taler-wallet-webextension/src/popup/Settings.tsx
index 52e72ee2f..8595c87ff 100644
--- a/packages/taler-wallet-webextension/src/popup/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx
@@ -20,6 +20,7 @@ import { VNode, h } from "preact";
import { Checkbox } from "../components/Checkbox";
import { EditableText } from "../components/EditableText";
import { SelectList } from "../components/SelectList";
+import { PopupBox } from "../components/styled";
import { useDevContext } from "../context/devContext";
import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
@@ -67,10 +68,10 @@ const names: LangsNames = {
export function SettingsView({ lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode {
return (
- <div>
- <section style={{ height: 300, overflow: 'auto' }}>
- <h2><i18n.Translate>Wallet</i18n.Translate></h2>
- <SelectList
+ <PopupBox>
+ <section>
+ {/* <h2><i18n.Translate>Wallet</i18n.Translate></h2> */}
+ {/* <SelectList
value={lang}
onChange={changeLang}
name="lang"
@@ -84,7 +85,7 @@ export function SettingsView({ lang, changeLang, deviceName, setDeviceName, perm
name="device-id"
label={i18n.str`Device name`}
description="(This is how you will recognize the wallet in the backup provider)"
- />
+ /> */}
<h2><i18n.Translate>Permissions</i18n.Translate></h2>
<Checkbox label="Automatically open wallet based on page content"
name="perm"
@@ -98,6 +99,12 @@ export function SettingsView({ lang, changeLang, deviceName, setDeviceName, perm
enabled={developerMode} onToggle={toggleDeveloperMode}
/>
</section>
- </div>
+ <footer style={{ justifyContent: 'space-around' }}>
+ <a target="_blank"
+ rel="noopener noreferrer"
+ style={{ color: 'darkgreen', textDecoration: 'none' }}
+ href={chrome.extension ? chrome.extension.getURL(`/static/wallet.html#/settings`) : '#'}>VIEW MORE SETTINGS</a>
+ </footer>
+ </PopupBox>
)
} \ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
index 1b145345f..cccda203e 100644
--- a/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Balance.stories.tsx
@@ -35,14 +35,15 @@ export const NotYetLoaded = createExample(TestedComponent, {
export const GotError = createExample(TestedComponent, {
balance: {
- error: true
+ hasError: true,
+ message: 'Network error'
},
Linker: NullLink,
});
export const EmptyBalance = createExample(TestedComponent, {
balance: {
- error: false,
+ hasError: false,
response: {
balances: []
},
@@ -52,7 +53,7 @@ export const EmptyBalance = createExample(TestedComponent, {
export const SomeCoins = createExample(TestedComponent, {
balance: {
- error: false,
+ hasError: false,
response: {
balances: [{
available: 'USD:10.5',
@@ -68,7 +69,7 @@ export const SomeCoins = createExample(TestedComponent, {
export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, {
balance: {
- error: false,
+ hasError: false,
response: {
balances: [{
available: 'USD:2.23',
@@ -84,7 +85,7 @@ export const SomeCoinsAndIncomingMoney = createExample(TestedComponent, {
export const SomeCoinsInTwoCurrencies = createExample(TestedComponent, {
balance: {
- error: false,
+ hasError: false,
response: {
balances: [{
available: 'USD:2',
diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
index e06e884ce..eb5a0447c 100644
--- a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
@@ -41,7 +41,7 @@ export function BalanceView({ balance, Linker, goToWalletManualWithdraw }: Balan
return <span />
}
- if (balance.error) {
+ if (balance.hasError) {
return (
<div>
<p>{i18n.str`Error: could not retrieve balance information.`}</p>
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx
index 2bb59fcdb..43b0a6630 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -29,7 +29,7 @@ export function HistoryPage(props: any): JSX.Element {
TransactionsResponse | undefined
>(undefined);
const balance = useBalances()
- const balanceWithoutError = balance?.error ? [] : (balance?.response.balances || [])
+ const balanceWithoutError = balance?.hasError ? [] : (balance?.response.balances || [])
useEffect(() => {
const fetchData = async (): Promise<void> => {
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
index 52e72ee2f..d1eb012fc 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -15,23 +15,29 @@
*/
-import { i18n } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
+import { ExchangeListItem, i18n } from "@gnu-taler/taler-util";
+import { VNode, h, Fragment } from "preact";
import { Checkbox } from "../components/Checkbox";
import { EditableText } from "../components/EditableText";
import { SelectList } from "../components/SelectList";
+import { ButtonPrimary, ButtonSuccess, WalletBox } from "../components/styled";
import { useDevContext } from "../context/devContext";
import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { useLang } from "../hooks/useLang";
+import * as wxApi from "../wxApi";
export function SettingsPage(): VNode {
const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
const { devMode, toggleDevMode } = useDevContext()
const { name, update } = useBackupDeviceName()
const [lang, changeLang] = useLang()
+ const exchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
+
return <SettingsView
lang={lang} changeLang={changeLang}
+ knownExchanges={!exchangesHook || exchangesHook.hasError ? [] : exchangesHook.response.exchanges}
deviceName={name} setDeviceName={update}
permissionsEnabled={permissionsEnabled} togglePermissions={togglePermissions}
developerMode={devMode} toggleDeveloperMode={toggleDevMode}
@@ -47,6 +53,7 @@ export interface ViewProps {
togglePermissions: () => void;
developerMode: boolean;
toggleDeveloperMode: () => void;
+ knownExchanges: Array<ExchangeListItem>;
}
import { strings as messages } from '../i18n/strings'
@@ -65,26 +72,24 @@ const names: LangsNames = {
}
-export function SettingsView({ lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode {
+export function SettingsView({ knownExchanges, lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode {
return (
- <div>
- <section style={{ height: 300, overflow: 'auto' }}>
- <h2><i18n.Translate>Wallet</i18n.Translate></h2>
- <SelectList
- value={lang}
- onChange={changeLang}
- name="lang"
- list={names}
- label={i18n.str`Language`}
- description="(Choose your preferred lang)"
- />
- <EditableText
- value={deviceName}
- onChange={setDeviceName}
- name="device-id"
- label={i18n.str`Device name`}
- description="(This is how you will recognize the wallet in the backup provider)"
- />
+ <WalletBox>
+ <section>
+
+ <h2><i18n.Translate>Known exchanges</i18n.Translate></h2>
+ {!knownExchanges || !knownExchanges.length ? <div>
+ No exchange yet!
+ </div> :
+ <dl>
+ {knownExchanges.map(e => <Fragment>
+ <dt>{e.currency}</dt>
+ <dd>{e.exchangeBaseUrl}</dd>
+ <dd>{e.paytoUris}</dd>
+ </Fragment>)}
+ </dl>
+ }
+ <ButtonPrimary>add exchange</ButtonPrimary>
<h2><i18n.Translate>Permissions</i18n.Translate></h2>
<Checkbox label="Automatically open wallet based on page content"
name="perm"
@@ -98,6 +103,6 @@ export function SettingsView({ lang, changeLang, deviceName, setDeviceName, perm
enabled={developerMode} onToggle={toggleDeveloperMode}
/>
</section>
- </div>
+ </WalletBox>
)
} \ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts
index 8a0881a6c..664cc564b 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -43,6 +43,7 @@ import {
AcceptManualWithdrawalResult,
AcceptManualWithdrawalRequest,
AmountJson,
+ ExchangesListRespose,
} from "@gnu-taler/taler-util";
import { AddBackupProviderRequest, BackupProviderState, OperationFailedError, RemoveBackupProviderRequest } from "@gnu-taler/taler-wallet-core";
import { BackupInfo } from "@gnu-taler/taler-wallet-core";
@@ -170,6 +171,10 @@ export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> {
});
}
+export function listExchanges(): Promise<ExchangesListRespose> {
+ return callBackend("listExchanges", {})
+}
+
/**
* Get information about the current state of wallet backups.
*/