diff options
author | Sebastian <sebasjm@gmail.com> | 2021-09-08 15:30:32 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-09-08 15:35:44 -0300 |
commit | 217f34397f95fc988280eee9c376efe0781c69ea (patch) | |
tree | 8cfb4011a7ddcb7409b7617ec8ac1425d08b1512 | |
parent | a72ec5971e7c65e13a22627fc52983b75d4bea71 (diff) |
first approach to new design for withdraw
11 files changed, 160 insertions, 106 deletions
diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 6e71de6ee..7ec182fa2 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -713,6 +713,17 @@ export const codecForGetWithdrawalDetailsForUri = (): Codec<GetWithdrawalDetails .property("talerWithdrawUri", codecForString()) .build("GetWithdrawalDetailsForUriRequest"); +export interface GetExchangeWithdrawalInfo { + exchangeBaseUrl: string; + amount: AmountJson; +} + +export const codecForGetExchangeWithdrawalInfo = (): Codec<GetExchangeWithdrawalInfo> => + buildCodecForObject<GetExchangeWithdrawalInfo>() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForAmountJson()) + .build("GetExchangeWithdrawalInfo"); + export interface AbortProposalRequest { proposalId: string; } @@ -791,7 +802,7 @@ export interface MakeSyncSignatureRequest { /** * Planchet for a coin during refresh. */ - export interface RefreshPlanchetInfo { +export interface RefreshPlanchetInfo { /** * Public key for the coin. */ diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index e6b6e8746..620ad88be 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -92,7 +92,7 @@ interface DenominationSelectionInfo { * * Sent to the wallet frontend to be rendered and shown to the user. */ -interface ExchangeWithdrawDetails { +export interface ExchangeWithdrawDetails { /** * Exchange that the reserve will be created at. */ diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index fec7e6155..cbaf03c3b 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -28,6 +28,7 @@ import { codecForDeleteTransactionRequest, codecForRetryTransactionRequest, codecForSetWalletDeviceIdRequest, + codecForGetExchangeWithdrawalInfo, durationFromSpec, durationMin, getDurationRemaining, @@ -693,6 +694,10 @@ async function dispatchRequestInternal( const req = codecForGetWithdrawalDetailsForUri().decode(payload); return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri); } + case "getExchangeWithdrawalInfo": { + const req = codecForGetExchangeWithdrawalInfo().decode(payload); + return await getExchangeWithdrawalInfo(ws, req.exchangeBaseUrl, req.amount); + } case "acceptManualWithdrawal": { const req = codecForAcceptManualWithdrawalRequet().decode(payload); const res = await acceptManualWithdrawal( diff --git a/packages/taler-wallet-webextension/.storybook/preview.js b/packages/taler-wallet-webextension/.storybook/preview.js index 488663469..0efb96308 100644 --- a/packages/taler-wallet-webextension/.storybook/preview.js +++ b/packages/taler-wallet-webextension/.storybook/preview.js @@ -119,7 +119,6 @@ export const decorators = [ margin: 0; font-size: 100%; padding: 0; - background-color: #f8faf7; font-family: Arial, Helvetica, sans-serif; }`} </style> diff --git a/packages/taler-wallet-webextension/src/components/Part.tsx b/packages/taler-wallet-webextension/src/components/Part.tsx new file mode 100644 index 000000000..87b16de87 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/Part.tsx @@ -0,0 +1,16 @@ +import { AmountLike } from "@gnu-taler/taler-util"; +import { ExtraLargeText, LargeText, SmallLightText } from "./styled"; + +export type Kind = 'positive' | 'negative' | 'neutral'; +interface Props { + title: string, text: AmountLike, kind: Kind, big?: boolean +} +export function Part({ text, title, kind, big }: Props) { + const Text = big ? ExtraLargeText : LargeText; + return <div style={{ margin: '1em' }}> + <SmallLightText style={{ margin: '.5em' }}>{title}</SmallLightText> + <Text style={{ color: kind == 'positive' ? 'green' : (kind == 'negative' ? 'red' : 'black') }}> + {text} + </Text> + </div> +} diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index de045584c..553726de4 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -12,6 +12,16 @@ export const PaymentStatus = styled.div<{ color: string }>` ` export const WalletAction = styled.section` + max-width: 50%; + + margin: auto; + height: 100%; + + & h1:first-child { + margin-top: 0; + } +` +export const WalletActionOld = styled.section` border: solid 5px black; border-radius: 10px; margin-left: auto; @@ -152,7 +162,7 @@ export const PopupBox = styled.div<{ noPadding?: boolean }>` ` -export const Button = styled.button` +export const Button = styled.button<{ upperCased?: boolean }>` display: inline-block; zoom: 1; line-height: normal; @@ -162,6 +172,7 @@ export const Button = styled.button` cursor: pointer; user-select: none; box-sizing: border-box; + text-transform: ${({ upperCased }) => upperCased ? 'uppercase' : 'none'}; font-family: inherit; font-size: 100%; @@ -242,11 +253,11 @@ export const ButtonBoxPrimary = styled(ButtonBox)` ` export const ButtonSuccess = styled(ButtonVariant)` - background-color: rgb(28, 184, 65); + background-color: #388e3c; ` export const ButtonBoxSuccess = styled(ButtonBox)` - color: rgb(28, 184, 65); - border-color: rgb(28, 184, 65); + color: #388e3c; + border-color: #388e3c; ` export const ButtonWarning = styled(ButtonVariant)` diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx b/packages/taler-wallet-webextension/src/cta/Pay.tsx index 24c76ce6c..758bc4b54 100644 --- a/packages/taler-wallet-webextension/src/cta/Pay.tsx +++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx @@ -136,7 +136,9 @@ export function PayPage({ talerPayUri }: Props): JSX.Element { setPayResult(res); } catch (e) { console.error(e); - setPayErrMsg(e.message); + if (e instanceof Error) { + setPayErrMsg(e.message); + } } } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx index 747f855fa..a89a18c15 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.stories.tsx @@ -19,6 +19,9 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { amountFractionalBase, Amounts } from '@gnu-taler/taler-util'; +import { ExchangeRecord } from '@gnu-taler/taler-wallet-core'; +import { ExchangeWithdrawDetails } from '@gnu-taler/taler-wallet-core/src/operations/withdraw'; import { createExample } from '../test-utils'; import { View as TestedComponent } from './Withdraw'; @@ -30,16 +33,29 @@ export default { }, }; -export const CompleteWithExchange = createExample(TestedComponent, { +export const WithdrawWithFee = createExample(TestedComponent, { details: { - amount: 'USD:2', - possibleExchanges: [], - }, - selectedExchange: 'Some exchange' + exchangeInfo: { + baseUrl: 'exchange.demo.taler.net' + } as ExchangeRecord, + withdrawFee: { + currency: 'USD', + fraction: amountFractionalBase*0.5, + value: 0 + }, + } as ExchangeWithdrawDetails, + amount: 'USD:2', }) -export const CompleteWithoutExchange = createExample(TestedComponent, { +export const WithdrawWithoutFee = createExample(TestedComponent, { details: { - amount: 'USD:2', - possibleExchanges: [], - }, + exchangeInfo: { + baseUrl: 'exchange.demo.taler.net' + } as ExchangeRecord, + withdrawFee: { + currency: 'USD', + fraction: 0, + value: 0 + }, + } as ExchangeWithdrawDetails, + amount: 'USD:2', }) diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx index d5f3c89ae..9719b8f5e 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx @@ -21,98 +21,78 @@ * @author Florian Dold */ -import { i18n } from '@gnu-taler/taler-util' -import { renderAmount } from "../renderHtml"; - -import { useState, useEffect } from "preact/hooks"; +import { AmountLike, Amounts, 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 { JSX } from "preact/jsx-runtime"; +import { LogoHeader } from '../components/LogoHeader'; +import { Part } from '../components/Part'; +import { ButtonSuccess, WalletAction } from '../components/styled'; import { - acceptWithdrawal, - onUpdateNotification, - getWithdrawalDetailsForUri, + acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification } from "../wxApi"; -import { h } from 'preact'; -import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util"; -import { JSX } from "preact/jsx-runtime"; -import { WalletAction } from '../components/styled'; + interface Props { talerWithdrawUri?: string; } export interface ViewProps { - details: WithdrawUriInfoResponse; - selectedExchange?: string; + details: ExchangeWithdrawDetails; + amount: string; accept: () => Promise<void>; setCancelled: (b: boolean) => void; setSelecting: (b: boolean) => void; }; -export function View({ details, selectedExchange, accept, setCancelled, setSelecting }: ViewProps) { +function amountToString(text: AmountLike) { + const aj = Amounts.jsonifyAmount(text) + const amount = Amounts.stringifyValue(aj) + return `${amount} ${aj.currency}` +} + + +export function View({ details, amount, accept, setCancelled, setSelecting }: ViewProps) { return ( - <WalletAction> - <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;"> - <h1 style="font-family: monospace; font-size: 250%;"> - <span style="color: #aa3939;">❰</span>Taler Wallet<span style="color: #aa3939;">❱</span> - </h1> - </div> - <div class="fade"> + <WalletAction style={{ textAlign: 'center' }}> + <LogoHeader /> + <h2> + {i18n.str`Digital cash withdrawal`} + </h2> + <section> + <div> + <Part title="Total to withdraw" text={amountToString(Amounts.sub(Amounts.parseOrThrow(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' /> + } + <Part title="Exchange" text={details.exchangeInfo.baseUrl} kind='neutral' big /> + </div> + </section> + <section> + <div> - <h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1> - <p><i18n.Translate> - You are about to withdraw{" "} - <strong>{renderAmount(details.amount)}</strong> from your bank account - into your wallet. - </i18n.Translate></p> - {selectedExchange ? ( - <p><i18n.Translate> - The exchange <strong>{selectedExchange}</strong> will be used as the - Taler payment service provider. - </i18n.Translate></p> - ) : null} - - <div> - <button - class="pure-button button-success" - disabled={!selectedExchange} - onClick={() => accept()} - > - {i18n.str`Accept fees and withdraw`} - </button> - <p> - <span - role="button" - tabIndex={0} - style={{ textDecoration: "underline", cursor: "pointer" }} - onClick={() => setSelecting(true)} - > - {i18n.str`Chose different exchange provider`} - </span> - <br /> - <span - role="button" - tabIndex={0} - style={{ textDecoration: "underline", cursor: "pointer" }} - onClick={() => setCancelled(true)} - > - {i18n.str`Cancel withdraw operation`} - </span> - </p> - </div> + <ButtonSuccess + upperCased + disabled={!details.exchangeInfo.baseUrl} + onClick={accept} + > + {i18n.str`Accept fees and withdraw`} + </ButtonSuccess> </div> - </div> + </section> </WalletAction> ) } export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element { - const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined); - const [selectedExchange, setSelectedExchange] = useState<string | undefined>(undefined); + 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); - const [state, setState] = useState(1) useEffect(() => { return onUpdateNotification(() => { @@ -127,47 +107,59 @@ export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): JSX.Element const fetchData = async (): Promise<void> => { try { const res = await getWithdrawalDetailsForUri({ talerWithdrawUri }); - setDetails(res); - if (res.defaultExchangeBaseUrl) { - setSelectedExchange(res.defaultExchangeBaseUrl); - } + setUriInfo(res); } catch (e) { console.error('error', JSON.stringify(e, undefined, 2)) setError(true) } }; fetchData(); - }, [selectedExchange, selecting, talerWithdrawUri, updateCounter, state]); + }, [selecting, talerWithdrawUri, updateCounter]); + + useEffect(() => { + async function fetchData() { + if (!uriInfo || !uriInfo.defaultExchangeBaseUrl) return + const res = await getExchangeWithdrawalInfo({ + exchangeBaseUrl: uriInfo.defaultExchangeBaseUrl, + amount: Amounts.parseOrThrow(uriInfo.amount) + }) + setDetails(res) + } + fetchData() + }, [uriInfo]) if (!talerWithdrawUri) { return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>; } const accept = async (): Promise<void> => { - if (!selectedExchange) { + if (!details) { throw Error("can't accept, no exchange selected"); } - console.log("accepting exchange", selectedExchange); - const res = await acceptWithdrawal(talerWithdrawUri, selectedExchange); + console.log("accepting exchange", details.exchangeInfo.baseUrl); + const res = await acceptWithdrawal(talerWithdrawUri, details.exchangeInfo.baseUrl); console.log("accept withdrawal response", res); if (res.confirmTransferUrl) { document.location.href = res.confirmTransferUrl; } }; - if (!details) { - return <span><i18n.Translate>Loading...</i18n.Translate></span>; - } 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>; + } return <View accept={accept} setCancelled={setCancelled} setSelecting={setSelecting} - details={details} selectedExchange={selectedExchange} + details={details} amount={uriInfo.amount} /> } diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index cc5430d0d..435753725 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -24,6 +24,7 @@ import { Pages } from "../NavigationBar"; import emptyImg from "../../static/img/empty.png" import { Button, ButtonBox, ButtonBoxDestructive, ButtonDestructive, ButtonPrimary, ExtraLargeText, FontIcon, LargeText, ListOfProducts, PopupBox, Row, RowBorderGray, SmallLightText, WalletBox } from "../components/styled"; import { ErrorMessage } from "../components/ErrorMessage"; +import { Part } from "../components/Part"; export function TransactionPage({ tid }: { tid: string; }): JSX.Element { const [transaction, setTransaction] = useState< @@ -60,7 +61,6 @@ export interface WalletTransactionProps { onBack: () => void, } - export function TransactionView({ transaction, onDelete, onRetry, onBack }: WalletTransactionProps) { function Status() { @@ -90,16 +90,6 @@ export function TransactionView({ transaction, onDelete, onRetry, onBack }: Wall </footer> </WalletBox> } - type Kind = 'positive' | 'negative' | 'neutral'; - function Part({ text, title, kind, big }: { title: string, text: AmountLike, kind: Kind, big?: boolean }) { - const Text = big ? ExtraLargeText : LargeText; - return <div style={{ margin: '1em' }}> - <SmallLightText style={{ margin: '.5em' }}>{title}</SmallLightText> - <Text style={{ color: kind == 'positive' ? 'green' : (kind == 'negative' ? 'red' : 'black') }}> - {text} - </Text> - </div> - } function amountToString(text: AmountLike) { const aj = Amounts.jsonifyAmount(text) diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 52ce27f2b..acb28ffec 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -38,9 +38,11 @@ import { DeleteTransactionRequest, RetryTransactionRequest, SetWalletDeviceIdRequest, + GetExchangeWithdrawalInfo, } from "@gnu-taler/taler-util"; import { AddBackupProviderRequest, BackupProviderState, OperationFailedError, RemoveBackupProviderRequest } from "@gnu-taler/taler-wallet-core"; import { BackupInfo } from "@gnu-taler/taler-wallet-core"; +import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; export interface ExtendedPermissionsResponse { newValue: boolean; @@ -281,6 +283,16 @@ export function getWithdrawalDetailsForUri( return callBackend("getWithdrawalDetailsForUri", req); } + +/** + * Get diagnostics information + */ + export function getExchangeWithdrawalInfo( + req: GetExchangeWithdrawalInfo, +): Promise<ExchangeWithdrawDetails> { + return callBackend("getExchangeWithdrawalInfo", req); +} + export function prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> { return callBackend("prepareTip", req); } |