first approach to new design for withdraw
This commit is contained in:
parent
a72ec5971e
commit
217f34397f
@ -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.
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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(
|
||||
|
@ -119,7 +119,6 @@ export const decorators = [
|
||||
margin: 0;
|
||||
font-size: 100%;
|
||||
padding: 0;
|
||||
background-color: #f8faf7;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}`}
|
||||
</style>
|
||||
|
16
packages/taler-wallet-webextension/src/components/Part.tsx
Normal file
16
packages/taler-wallet-webextension/src/components/Part.tsx
Normal file
@ -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>
|
||||
}
|
@ -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)`
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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',
|
||||
})
|
||||
|
@ -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 {
|
||||
acceptWithdrawal,
|
||||
onUpdateNotification,
|
||||
getWithdrawalDetailsForUri,
|
||||
} from "../wxApi";
|
||||
import { h } from 'preact';
|
||||
import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
|
||||
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 { WalletAction } from '../components/styled';
|
||||
import { LogoHeader } from '../components/LogoHeader';
|
||||
import { Part } from '../components/Part';
|
||||
import { ButtonSuccess, WalletAction } from '../components/styled';
|
||||
import {
|
||||
acceptWithdrawal, getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, onUpdateNotification
|
||||
} from "../wxApi";
|
||||
|
||||
|
||||
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>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
|
||||
<div>
|
||||
<ButtonSuccess
|
||||
upperCased
|
||||
disabled={!details.exchangeInfo.baseUrl}
|
||||
onClick={accept}
|
||||
>
|
||||
{i18n.str`Accept fees and withdraw`}
|
||||
</ButtonSuccess>
|
||||
</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}
|
||||
/>
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user