some ui fixing from belen comments

This commit is contained in:
Sebastian 2021-09-27 13:06:50 -03:00
parent 8cde98947b
commit b1bf3538e6
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
6 changed files with 212 additions and 118 deletions

View File

@ -407,7 +407,7 @@ export class Amounts {
return `${a.currency}:${s}`; return `${a.currency}:${s}`;
} }
static stringifyValue(a: AmountJson): string { static stringifyValue(a: AmountJson, minFractional: number = 0): string {
const av = a.value + Math.floor(a.fraction / amountFractionalBase); const av = a.value + Math.floor(a.fraction / amountFractionalBase);
const af = a.fraction % amountFractionalBase; const af = a.fraction % amountFractionalBase;
let s = av.toString(); let s = av.toString();
@ -416,7 +416,7 @@ export class Amounts {
s = s + "."; s = s + ".";
let n = af; let n = af;
for (let i = 0; i < amountFractionalLength; i++) { for (let i = 0; i < amountFractionalLength; i++) {
if (!n) { if (!n && i >= minFractional) {
break; break;
} }
s = s + Math.floor((n / amountFractionalBase) * 10).toString(); s = s + Math.floor((n / amountFractionalBase) * 10).toString();

View File

@ -22,7 +22,7 @@ export function ErrorMessage({ title, description }: { title?: string|VNode; des
const [showErrorDetail, setShowErrorDetail] = useState(false); const [showErrorDetail, setShowErrorDetail] = useState(false);
if (!title) if (!title)
return null; return null;
return <ErrorBox> return <ErrorBox style={{paddingTop: 0, paddingBottom: 0}}>
<div> <div>
<p>{title}</p> <p>{title}</p>
{ description && <button onClick={() => { setShowErrorDetail(v => !v); }}> { description && <button onClick={() => { setShowErrorDetail(v => !v); }}>

View File

@ -520,8 +520,7 @@ export const ErrorBox = styled.div`
justify-content: space-between; justify-content: space-between;
flex-direction: column; flex-direction: column;
/* margin: 0.5em; */ /* margin: 0.5em; */
padding-left: 1em; padding: 1em;
padding-right: 1em;
/* width: 100%; */ /* width: 100%; */
color: #721c24; color: #721c24;
background: #f8d7da; background: #f8d7da;
@ -539,6 +538,19 @@ export const ErrorBox = styled.div`
} }
} }
` `
export const SuccessBox = styled(ErrorBox)`
color: #0f5132;
background-color: #d1e7dd;
border-color: #badbcc;
`
export const WarningBox = styled(ErrorBox)`
color: #664d03;
background-color: #fff3cd;
border-color: #ffecb5;
`
export const PopupNavigation = styled.div<{ devMode?: boolean }>` export const PopupNavigation = styled.div<{ devMode?: boolean }>`
background-color:#0042b2; background-color:#0042b2;
height: 35px; height: 35px;

View File

@ -30,7 +30,7 @@ export default {
}, },
}; };
export const InsufficientBalance = createExample(TestedComponent, { export const NoBalance = createExample(TestedComponent, {
payStatus: { payStatus: {
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,
noncePriv: '', noncePriv: '',
@ -46,6 +46,27 @@ export const InsufficientBalance = createExample(TestedComponent, {
} }
}); });
export const NoEnoughBalance = createExample(TestedComponent, {
payStatus: {
status: PreparePayResultType.InsufficientBalance,
noncePriv: '',
proposalId: "proposal1234",
contractTerms: {
merchant: {
name: 'someone'
},
summary: 'some beers',
amount: 'USD:10',
} as Partial<ContractTerms> as any,
amountRaw: 'USD:10',
},
balance: {
currency: 'USD',
fraction: 40000000,
value: 9
}
});
export const PaymentPossible = createExample(TestedComponent, { export const PaymentPossible = createExample(TestedComponent, {
uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0', uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0',
payStatus: { payStatus: {
@ -66,6 +87,26 @@ export const PaymentPossible = createExample(TestedComponent, {
} }
}); });
export const PaymentPossibleWithFee = createExample(TestedComponent, {
uri: 'taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0',
payStatus: {
status: PreparePayResultType.PaymentPossible,
amountEffective: 'USD:10.20',
amountRaw: 'USD:10',
noncePriv: '',
contractTerms: {
nonce: '123213123',
merchant: {
name: 'someone'
},
amount: 'USD:10',
summary: 'some beers',
} as Partial<ContractTerms> as any,
contractTermsHash: '123456',
proposalId: 'proposal1234'
}
});
export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, { export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
payStatus: { payStatus: {
status: PreparePayResultType.AlreadyConfirmed, status: PreparePayResultType.AlreadyConfirmed,
@ -102,3 +143,22 @@ export const AlreadyConfirmedWithoutFullfilment = createExample(TestedComponent,
paid: false, paid: false,
} }
}); });
export const AlreadyPaid = 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! ',
summary: 'some beers',
amount: 'USD:10',
} as Partial<ContractTerms> as any,
contractTermsHash: '123456',
proposalId: 'proposal1234',
paid: true,
}
});

View File

@ -24,56 +24,45 @@
*/ */
// import * as i18n from "../i18n"; // import * as i18n from "../i18n";
import { renderAmount, ProgressButton } from "../renderHtml"; import { AmountJson, AmountLike, Amounts, ConfirmPayResult, ConfirmPayResultDone, ConfirmPayResultType, ContractTerms, getJsonI18n, i18n, PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util";
import * as wxApi from "../wxApi"; import { Fragment, JSX, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { useState, useEffect } from "preact/hooks";
import { AmountLike, ConfirmPayResultDone, getJsonI18n, i18n } from "@gnu-taler/taler-util";
import {
PreparePayResult,
ConfirmPayResult,
AmountJson,
PreparePayResultType,
Amounts,
ContractTerms,
ConfirmPayResultType,
} from "@gnu-taler/taler-util";
import { JSX, VNode, h, Fragment } from "preact";
import { ButtonDestructive, ButtonSuccess, ButtonWarning, LinkSuccess, LinkWarning, WalletAction } from "../components/styled";
import { LogoHeader } from "../components/LogoHeader"; import { LogoHeader } from "../components/LogoHeader";
import { Part } from "../components/Part"; import { Part } from "../components/Part";
import { QR } from "../components/QR"; import { QR } from "../components/QR";
import { ButtonSuccess, LinkSuccess, SuccessBox, WalletAction, WarningBox } from "../components/styled";
import { useBalances } from "../hooks/useBalances";
import * as wxApi from "../wxApi";
interface Props { interface Props {
talerPayUri?: string talerPayUri?: string
} }
export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) { // export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) {
const fulfillmentUrl = payStatus.contractTerms.fulfillment_url; // const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
let message; // let message;
if (fulfillmentUrl) { // if (fulfillmentUrl) {
message = ( // 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 {
message = <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"> // return <section class="main">
<h1>GNU Taler Wallet</h1> // <h1>GNU Taler Wallet</h1>
<article class="fade"> // <article class="fade">
{message} // {message}
</article> // </article>
</section> // </section>
} // }
const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultDone> => { const doPayment = async (payStatus: PreparePayResult): Promise<ConfirmPayResultDone> => {
if (payStatus.status !== "payment-possible") { if (payStatus.status !== "payment-possible") {
@ -98,6 +87,12 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined); const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined);
const [payErrMsg, setPayErrMsg] = useState<string | undefined>(""); const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
const balance = useBalances()
const balanceWithoutError = balance?.error ? [] : (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
useEffect(() => { useEffect(() => {
if (!talerPayUri) return; if (!talerPayUri) return;
const doFetch = async (): Promise<void> => { const doFetch = async (): Promise<void> => {
@ -115,24 +110,24 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
return <span>Loading payment information ...</span>; 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) {
const obj = { // const obj = {
fulfillment_message: payResult.contractTerms.fulfillment_message, // fulfillment_message: payResult.contractTerms.fulfillment_message,
fulfillment_message_i18n: // fulfillment_message_i18n:
payResult.contractTerms.fulfillment_message_i18n, // payResult.contractTerms.fulfillment_message_i18n,
}; // };
const msg = getJsonI18n(obj, "fulfillment_message"); // const msg = getJsonI18n(obj, "fulfillment_message");
return ( // return (
<div> // <div>
<p>Payment succeeded.</p> // <p>Payment succeeded.</p>
<p>{msg}</p> // <p>{msg}</p>
</div> // </div>
); // );
} else { // } else {
return <span>Redirecting ...</span>; // return <span>Redirecting ...</span>;
} // }
} // }
const onClick = async () => { const onClick = async () => {
try { try {
@ -147,7 +142,7 @@ export function PayPage({ talerPayUri }: Props): JSX.Element {
} }
return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} />; return <PaymentRequestView uri={talerPayUri} payStatus={payStatus} onClick={onClick} payErrMsg={payErrMsg} balance={foundAmount} />;
} }
export interface PaymentRequestViewProps { export interface PaymentRequestViewProps {
@ -155,8 +150,9 @@ export interface PaymentRequestViewProps {
onClick: () => void; onClick: () => void;
payErrMsg?: string; payErrMsg?: string;
uri: string; uri: string;
balance: AmountJson | undefined;
} }
export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg }: PaymentRequestViewProps) { export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg, balance }: PaymentRequestViewProps) {
let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw); let totalFees: AmountJson = Amounts.getZero(payStatus.amountRaw);
const contractTerms: ContractTerms = payStatus.contractTerms; const contractTerms: ContractTerms = payStatus.contractTerms;
@ -183,71 +179,98 @@ export function PaymentRequestView({ uri, payStatus, onClick, payErrMsg }: Payme
merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>; merchantName = <strong>(pub: {contractTerms.merchant_pub})</strong>;
} }
const [showQR, setShowQR] = useState<boolean>(false) function Alternative() {
const privateUri = payStatus.status !== PreparePayResultType.AlreadyConfirmed ? `${uri}&n=${payStatus.noncePriv}` : uri const [showQR, setShowQR] = useState<boolean>(false)
return <WalletAction> const privateUri = payStatus.status !== PreparePayResultType.AlreadyConfirmed ? `${uri}&n=${payStatus.noncePriv}` : uri
<LogoHeader /> return <section>
<h2> <LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}>
{i18n.str`Digital cash payment`} {!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`}
</h2> </LinkSuccess>
<section> {showQR && <div>
{payStatus.status === PreparePayResultType.InsufficientBalance ? <QR text={privateUri} />
<Part title="Insufficient balance" text="No enough coins to pay" kind='negative' /> : Scan the QR code or <a href={privateUri}>click here</a>
<Part big title="Total amount with fee" text={amountToString(payStatus.amountEffective)} kind='negative' /> </div>}
}
<Part big title="Purchase amount" text={amountToString(payStatus.amountRaw)} kind='neutral' />
{Amounts.isNonZero(totalFees) && <Part big title="Fee" text={amountToString(totalFees)} kind='negative' />}
<Part title="Merchant" text={contractTerms.merchant.name} kind='neutral' />
<Part title="Purchase" text={contractTerms.summary} kind='neutral' />
{contractTerms.order_id && <Part title="Receipt" text={`#${contractTerms.order_id}`} kind='neutral' />}
</section> </section>
{showQR && <section> }
<QR text={privateUri} />
Scan the QR code or <a href={privateUri}>click here</a> function ButtonsSection() {
</section>} if (payErrMsg) {
<section> return <section>
{payErrMsg ? (
<div> <div>
<p>Payment failed: {payErrMsg}</p> <p>Payment failed: {payErrMsg}</p>
<button class="pure-button button-success" onClick={onClick} > <button class="pure-button button-success" onClick={onClick} >
{i18n.str`Retry`} {i18n.str`Retry`}
</button> </button>
</div> </div>
) : ( </section>
payStatus.status === PreparePayResultType.PaymentPossible ? <Fragment> }
<LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}> if (payStatus.status === PreparePayResultType.PaymentPossible) {
{!showQR ? i18n.str`Complete with mobile wallet` : i18n.str`Hide QR`} return <Fragment>
</LinkSuccess> <section>
<ButtonSuccess upperCased> <ButtonSuccess upperCased>
{i18n.str`Confirm payment`} {i18n.str`Pay`} {amountToString(payStatus.amountEffective)}
</ButtonSuccess> </ButtonSuccess>
</Fragment> : ( </section>
payStatus.status === PreparePayResultType.InsufficientBalance ? <Fragment> <Alternative />
<LinkSuccess upperCased onClick={() => setShowQR(qr => !qr)}> </Fragment>
{!showQR ? i18n.str`Pay with other device` : i18n.str`Hide QR`} }
</LinkSuccess> if (payStatus.status === PreparePayResultType.InsufficientBalance) {
<ButtonDestructive upperCased disabled> return <Fragment>
{i18n.str`No enough coins`} <section>
</ButtonDestructive> {balance ? <WarningBox>
</Fragment> : Your balance of {amountToString(balance)} is not enough to pay for this purchase
<Fragment> </WarningBox> : <WarningBox>
{payStatus.contractTerms.fulfillment_message && <div> Your balance is not enough to pay for this purchase.
{payStatus.contractTerms.fulfillment_message} </WarningBox>}
</div>} </section>
<LinkWarning upperCased href={payStatus.contractTerms.fulfillment_url}> <section>
{i18n.str`Already paid`} <ButtonSuccess upperCased>
</LinkWarning> {i18n.str`Withdraw digital cash`}
</Fragment> </ButtonSuccess>
</section>
<Alternative />
</Fragment>
}
if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
return <Fragment>
<section>
{payStatus.paid && contractTerms.fulfillment_message && <Part title="Merchant message" text={contractTerms.fulfillment_message} kind='neutral' />}
</section>
{!payStatus.paid && <Alternative />}
</Fragment>
}
return <span />
}
) return <WalletAction>
)} <LogoHeader />
<h2>
{i18n.str`Digital cash payment`}
</h2>
{payStatus.status === PreparePayResultType.AlreadyConfirmed &&
(payStatus.paid ? <SuccessBox> Already paid </SuccessBox> : <WarningBox> Already confirmed </WarningBox>)
}
<section>
{payStatus.status !== PreparePayResultType.InsufficientBalance && Amounts.isNonZero(totalFees) &&
<Part big title="Total to pay" text={amountToString(payStatus.amountEffective)} kind='negative' />
}
<Part big title="Purchase amount" text={amountToString(payStatus.amountRaw)} kind='neutral' />
{Amounts.isNonZero(totalFees) && <Fragment>
<Part big title="Fee" text={amountToString(totalFees)} kind='negative' />
</Fragment>
}
<Part title="Merchant" text={contractTerms.merchant.name} kind='neutral' />
<Part title="Purchase" text={contractTerms.summary} kind='neutral' />
{contractTerms.order_id && <Part title="Receipt" text={`#${contractTerms.order_id}`} kind='neutral' />}
</section> </section>
<ButtonsSection />
</WalletAction> </WalletAction>
} }
function amountToString(text: AmountLike) { function amountToString(text: AmountLike) {
const aj = Amounts.jsonifyAmount(text) const aj = Amounts.jsonifyAmount(text)
const amount = Amounts.stringifyValue(aj) const amount = Amounts.stringifyValue(aj, 2)
return `${amount} ${aj.currency}` return `${amount} ${aj.currency}`
} }

View File

@ -32,7 +32,6 @@ export type BalancesHook = BalancesHookOk | BalancesHookError | undefined;
export function useBalances(): BalancesHook { export function useBalances(): BalancesHook {
const [balance, setBalance] = useState<BalancesHook>(undefined); const [balance, setBalance] = useState<BalancesHook>(undefined);
console.log('render balance')
useEffect(() => { useEffect(() => {
async function checkBalance() { async function checkBalance() {
try { try {