invoice and transfer details
This commit is contained in:
parent
d84424202d
commit
e759684fd0
@ -25,6 +25,7 @@ import { ButtonHandler, SelectFieldHandler, TextFieldHandler } from "../../mui/h
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
amount: string;
|
amount: string;
|
||||||
|
onClose: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -47,11 +48,11 @@ export namespace State {
|
|||||||
|
|
||||||
export interface BaseInfo {
|
export interface BaseInfo {
|
||||||
error: undefined;
|
error: undefined;
|
||||||
|
cancel: ButtonHandler;
|
||||||
}
|
}
|
||||||
export interface ShowQr extends BaseInfo {
|
export interface ShowQr extends BaseInfo {
|
||||||
status: "show-qr";
|
status: "show-qr";
|
||||||
talerUri: string;
|
talerUri: string;
|
||||||
close: () => void;
|
|
||||||
}
|
}
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
|
@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ amount: amountStr }: Props,
|
{ amount: amountStr, onClose }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const amount = Amounts.parseOrThrow(amountStr)
|
const amount = Amounts.parseOrThrow(amountStr)
|
||||||
@ -53,7 +53,9 @@ export function useComponentState(
|
|||||||
status: "show-qr",
|
status: "show-qr",
|
||||||
talerUri,
|
talerUri,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
close: () => { null },
|
cancel: {
|
||||||
|
onClick: onClose
|
||||||
|
}
|
||||||
// chosenAmount: amount,
|
// chosenAmount: amount,
|
||||||
// toBeReceived: amount,
|
// toBeReceived: amount,
|
||||||
}
|
}
|
||||||
@ -105,6 +107,9 @@ export function useComponentState(
|
|||||||
setTalerUri(uri)
|
setTalerUri(uri)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
cancel: {
|
||||||
|
onClick: onClose
|
||||||
|
},
|
||||||
chosenAmount: amount,
|
chosenAmount: amount,
|
||||||
toBeReceived: amount,
|
toBeReceived: amount,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
@ -29,9 +29,7 @@ export default {
|
|||||||
export const ShowQr = createExample(ShowQrView, {
|
export const ShowQr = createExample(ShowQrView, {
|
||||||
talerUri:
|
talerUri:
|
||||||
"taler://pay-pull/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
"taler://pay-pull/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
||||||
close: () => {
|
cancel: {},
|
||||||
null;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Ready = createExample(ReadyView, {
|
export const Ready = createExample(ReadyView, {
|
||||||
@ -40,6 +38,7 @@ export const Ready = createExample(ReadyView, {
|
|||||||
value: 1,
|
value: 1,
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
},
|
},
|
||||||
|
cancel: {},
|
||||||
toBeReceived: {
|
toBeReceived: {
|
||||||
currency: "ARS",
|
currency: "ARS",
|
||||||
value: 1,
|
value: 1,
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
|
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
@ -44,7 +45,7 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ShowQrView({ talerUri, close }: State.ShowQr): VNode {
|
export function ShowQrView({ talerUri, cancel }: State.ShowQr): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<WalletAction>
|
<WalletAction>
|
||||||
@ -57,7 +58,7 @@ export function ShowQrView({ talerUri, close }: State.ShowQr): VNode {
|
|||||||
<QR text={talerUri} />
|
<QR text={talerUri} />
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<Link upperCased onClick={close}>
|
<Link upperCased onClick={cancel.onClick}>
|
||||||
<i18n.Translate>Close</i18n.Translate>
|
<i18n.Translate>Close</i18n.Translate>
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
@ -70,6 +71,7 @@ export function ReadyView({
|
|||||||
exchangeUrl,
|
exchangeUrl,
|
||||||
subject,
|
subject,
|
||||||
showQr,
|
showQr,
|
||||||
|
cancel,
|
||||||
operationError,
|
operationError,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
toBeReceived,
|
toBeReceived,
|
||||||
@ -83,6 +85,16 @@ export function ReadyView({
|
|||||||
<SubTitle>
|
<SubTitle>
|
||||||
<i18n.Translate>Digital invoice</i18n.Translate>
|
<i18n.Translate>Digital invoice</i18n.Translate>
|
||||||
</SubTitle>
|
</SubTitle>
|
||||||
|
{operationError && (
|
||||||
|
<ErrorTalerOperation
|
||||||
|
title={
|
||||||
|
<i18n.Translate>
|
||||||
|
Could not finish the invoice creation
|
||||||
|
</i18n.Translate>
|
||||||
|
}
|
||||||
|
error={operationError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<section style={{ textAlign: "left" }}>
|
<section style={{ textAlign: "left" }}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Subject"
|
label="Subject"
|
||||||
@ -145,6 +157,11 @@ export function ReadyView({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<Link upperCased onClick={cancel.onClick}>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import { AbsoluteTime, AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
import { ButtonHandler } from "../../mui/handlers.js";
|
import { ButtonHandler } from "../../mui/handlers.js";
|
||||||
@ -25,6 +25,7 @@ import { LoadingUriView, ReadyView } from "./views.js";
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerPayPullUri: string;
|
talerPayPullUri: string;
|
||||||
|
onClose: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -46,10 +47,13 @@ export namespace State {
|
|||||||
|
|
||||||
export interface BaseInfo {
|
export interface BaseInfo {
|
||||||
error: undefined;
|
error: undefined;
|
||||||
|
cancel: ButtonHandler;
|
||||||
}
|
}
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
amount: AmountJson,
|
amount: AmountJson,
|
||||||
|
summary: string | undefined,
|
||||||
|
expiration: AbsoluteTime | undefined,
|
||||||
error: undefined;
|
error: undefined;
|
||||||
accept: ButtonHandler;
|
accept: ButtonHandler;
|
||||||
operationError?: TalerErrorDetail;
|
operationError?: TalerErrorDetail;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import { AbsoluteTime, Amounts, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerPayPullUri }: Props,
|
{ talerPayPullUri, onClose }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
@ -45,13 +45,18 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { amount, peerPullPaymentIncomingId } = hook.response
|
const { amount: purseAmount, contractTerms, peerPullPaymentIncomingId } = hook.response
|
||||||
|
|
||||||
|
const amount: string = contractTerms?.amount
|
||||||
|
const summary: string | undefined = contractTerms?.summary
|
||||||
|
const expiration: TalerProtocolTimestamp | undefined = contractTerms?.purse_expiration
|
||||||
|
|
||||||
async function accept(): Promise<void> {
|
async function accept(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const resp = await api.acceptPeerPullPayment({
|
const resp = await api.acceptPeerPullPayment({
|
||||||
peerPullPaymentIncomingId
|
peerPullPaymentIncomingId
|
||||||
})
|
})
|
||||||
|
await onClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail)
|
setOperationError(e.errorDetail)
|
||||||
@ -69,6 +74,11 @@ export function useComponentState(
|
|||||||
accept: {
|
accept: {
|
||||||
onClick: accept
|
onClick: accept
|
||||||
},
|
},
|
||||||
|
summary,
|
||||||
|
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
|
||||||
|
cancel: {
|
||||||
|
onClick: onClose
|
||||||
|
},
|
||||||
operationError
|
operationError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,4 +33,5 @@ export const Ready = createExample(ReadyView, {
|
|||||||
fraction: 0,
|
fraction: 0,
|
||||||
},
|
},
|
||||||
accept: {},
|
accept: {},
|
||||||
|
cancel: {},
|
||||||
});
|
});
|
||||||
|
@ -20,19 +20,10 @@ import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
|||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
import { QR } from "../../components/QR.js";
|
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||||
import {
|
import { Time } from "../../components/Time.js";
|
||||||
Link,
|
|
||||||
SubTitle,
|
|
||||||
SvgIcon,
|
|
||||||
WalletAction,
|
|
||||||
} from "../../components/styled/index.js";
|
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import { Grid } from "../../mui/Grid.js";
|
|
||||||
import { TextField } from "../../mui/TextField.js";
|
|
||||||
import editIcon from "../../svg/edit_24px.svg";
|
|
||||||
import { ExchangeDetails, InvoiceDetails } from "../../wallet/Transaction.js";
|
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||||
@ -48,7 +39,10 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
|||||||
|
|
||||||
export function ReadyView({
|
export function ReadyView({
|
||||||
operationError,
|
operationError,
|
||||||
|
cancel,
|
||||||
accept,
|
accept,
|
||||||
|
expiration,
|
||||||
|
summary,
|
||||||
amount,
|
amount,
|
||||||
}: State.Ready): VNode {
|
}: State.Ready): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -70,16 +64,32 @@ export function ReadyView({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<section style={{ textAlign: "left" }}>
|
<section style={{ textAlign: "left" }}>
|
||||||
|
<Part
|
||||||
|
title={<i18n.Translate>Subject</i18n.Translate>}
|
||||||
|
text={<div>{summary}</div>}
|
||||||
|
/>
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>Amount</i18n.Translate>}
|
title={<i18n.Translate>Amount</i18n.Translate>}
|
||||||
text={<Amount value={amount} />}
|
text={<Amount value={amount} />}
|
||||||
/>
|
/>
|
||||||
|
<Part
|
||||||
|
title={<i18n.Translate>Valid until</i18n.Translate>}
|
||||||
|
text={<Time timestamp={expiration} format="dd MMMM yyyy, HH:mm" />}
|
||||||
|
kind="neutral"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<Button variant="contained" color="success" onClick={accept.onClick}>
|
<Button variant="contained" color="success" onClick={accept.onClick}>
|
||||||
<i18n.Translate>Pay</i18n.Translate>
|
<i18n.Translate>
|
||||||
|
Pay {<Amount value={amount} />}
|
||||||
|
</i18n.Translate>
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<Link upperCased onClick={cancel.onClick}>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import { AcceptedView, IgnoredView, LoadingUriView, ReadyView } from "./views.js
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerTipUri?: string;
|
talerTipUri?: string;
|
||||||
cancel: () => Promise<void>;
|
onCancel: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -58,6 +58,7 @@ export namespace State {
|
|||||||
amount: AmountJson;
|
amount: AmountJson;
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
error: undefined;
|
error: undefined;
|
||||||
|
cancel: ButtonHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Ignored extends BaseInfo {
|
export interface Ignored extends BaseInfo {
|
||||||
@ -70,7 +71,6 @@ export namespace State {
|
|||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
accept: ButtonHandler;
|
accept: ButtonHandler;
|
||||||
cancel: () => Promise<void>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerTipUri, cancel }: Props,
|
{ talerTipUri, onCancel }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const [tipIgnored, setTipIgnored] = useState(false);
|
const [tipIgnored, setTipIgnored] = useState(false);
|
||||||
@ -58,6 +58,9 @@ export function useComponentState(
|
|||||||
exchangeBaseUrl: tip.exchangeBaseUrl,
|
exchangeBaseUrl: tip.exchangeBaseUrl,
|
||||||
amount: Amounts.parseOrThrow(tip.tipAmountEffective),
|
amount: Amounts.parseOrThrow(tip.tipAmountEffective),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
cancel: {
|
||||||
|
onClick: onCancel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tipIgnored) {
|
if (tipIgnored) {
|
||||||
@ -80,7 +83,6 @@ export function useComponentState(
|
|||||||
accept: {
|
accept: {
|
||||||
onClick: doAccept,
|
onClick: doAccept,
|
||||||
},
|
},
|
||||||
cancel,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ describe("Tip CTA states", () => {
|
|||||||
it("should tell the user that the URI is missing", async () => {
|
it("should tell the user that the URI is missing", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: undefined, cancel: async () => { null } }, {
|
useComponentState({ talerTipUri: undefined, onCancel: async () => { null } }, {
|
||||||
prepareTip: async () => ({}),
|
prepareTip: async () => ({}),
|
||||||
acceptTip: async () => ({}),
|
acceptTip: async () => ({}),
|
||||||
} as any),
|
} as any),
|
||||||
@ -62,7 +62,7 @@ describe("Tip CTA states", () => {
|
|||||||
|
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, {
|
useComponentState({ talerTipUri: "taler://tip/asd", onCancel: async () => { null } }, {
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
accepted: tipAccepted,
|
accepted: tipAccepted,
|
||||||
@ -114,7 +114,7 @@ describe("Tip CTA states", () => {
|
|||||||
it("should be ignored after clicking the ignore button", async () => {
|
it("should be ignored after clicking the ignore button", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, {
|
useComponentState({ talerTipUri: "taler://tip/asd", onCancel: async () => { null } }, {
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
exchangeBaseUrl: "exchange url",
|
exchangeBaseUrl: "exchange url",
|
||||||
@ -160,7 +160,7 @@ describe("Tip CTA states", () => {
|
|||||||
it("should render accepted if the tip has been used previously", async () => {
|
it("should render accepted if the tip has been used previously", async () => {
|
||||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
mountHook(() =>
|
mountHook(() =>
|
||||||
useComponentState({ talerTipUri: "taler://tip/asd", cancel: async () => { null } }, {
|
useComponentState({ talerTipUri: "taler://tip/asd", onCancel: async () => { null } }, {
|
||||||
prepareTip: async () =>
|
prepareTip: async () =>
|
||||||
({
|
({
|
||||||
accepted: true,
|
accepted: true,
|
||||||
|
@ -14,17 +14,18 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
|
import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import { LoadingUriView, ReadyView, ShowQrView } from "./views.js";
|
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import { LoadingUriView, ReadyView, ShowQrView } from "./views.js";
|
||||||
import { ButtonHandler, SelectFieldHandler, TextFieldHandler } from "../../mui/handlers.js";
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
amount: string;
|
amount: string;
|
||||||
|
onClose: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -47,11 +48,11 @@ export namespace State {
|
|||||||
|
|
||||||
export interface BaseInfo {
|
export interface BaseInfo {
|
||||||
error: undefined;
|
error: undefined;
|
||||||
|
cancel: ButtonHandler;
|
||||||
}
|
}
|
||||||
export interface ShowQr extends BaseInfo {
|
export interface ShowQr extends BaseInfo {
|
||||||
status: "show-qr";
|
status: "show-qr";
|
||||||
talerUri: string;
|
talerUri: string;
|
||||||
close: () => void;
|
|
||||||
}
|
}
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
|
@ -21,7 +21,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ amount: amountStr }: Props,
|
{ amount: amountStr, onClose }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const amount = Amounts.parseOrThrow(amountStr)
|
const amount = Amounts.parseOrThrow(amountStr)
|
||||||
@ -30,13 +30,14 @@ export function useComponentState(
|
|||||||
const [talerUri, setTalerUri] = useState("")
|
const [talerUri, setTalerUri] = useState("")
|
||||||
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)
|
||||||
|
|
||||||
|
|
||||||
if (talerUri) {
|
if (talerUri) {
|
||||||
return {
|
return {
|
||||||
status: "show-qr",
|
status: "show-qr",
|
||||||
talerUri,
|
talerUri,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
close: () => { null },
|
cancel: {
|
||||||
|
onClick: onClose,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +63,11 @@ export function useComponentState(
|
|||||||
return {
|
return {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
invalid: !subject || Amounts.isZero(amount),
|
invalid: !subject || Amounts.isZero(amount),
|
||||||
|
cancel: {
|
||||||
|
onClick: onClose,
|
||||||
|
},
|
||||||
subject: {
|
subject: {
|
||||||
|
error: !subject ? "cant be empty" : undefined,
|
||||||
value: subject,
|
value: subject,
|
||||||
onInput: async (e) => setSubject(e)
|
onInput: async (e) => setSubject(e)
|
||||||
},
|
},
|
||||||
|
@ -29,9 +29,7 @@ export default {
|
|||||||
export const ShowQr = createExample(ShowQrView, {
|
export const ShowQr = createExample(ShowQrView, {
|
||||||
talerUri:
|
talerUri:
|
||||||
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
"taler://pay-push/exchange.taler.ar/HS585JK0QCXHJ8Z8QWZA3EBAY5WY7XNC1RR2MHJXSH2Z4WP0YPJ0",
|
||||||
close: () => {
|
cancel: {},
|
||||||
null;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Ready = createExample(ReadyView, {
|
export const Ready = createExample(ReadyView, {
|
||||||
@ -40,6 +38,7 @@ export const Ready = createExample(ReadyView, {
|
|||||||
value: 1,
|
value: 1,
|
||||||
fraction: 0,
|
fraction: 0,
|
||||||
},
|
},
|
||||||
|
cancel: {},
|
||||||
toBeReceived: {
|
toBeReceived: {
|
||||||
currency: "ARS",
|
currency: "ARS",
|
||||||
value: 1,
|
value: 1,
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
|
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
@ -38,7 +39,7 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ShowQrView({ talerUri, close }: State.ShowQr): VNode {
|
export function ShowQrView({ talerUri, cancel }: State.ShowQr): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<WalletAction>
|
<WalletAction>
|
||||||
@ -51,7 +52,7 @@ export function ShowQrView({ talerUri, close }: State.ShowQr): VNode {
|
|||||||
<QR text={talerUri} />
|
<QR text={talerUri} />
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<Link upperCased onClick={close}>
|
<Link upperCased onClick={cancel.onClick}>
|
||||||
<i18n.Translate>Close</i18n.Translate>
|
<i18n.Translate>Close</i18n.Translate>
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
@ -64,7 +65,9 @@ export function ReadyView({
|
|||||||
toBeReceived,
|
toBeReceived,
|
||||||
chosenAmount,
|
chosenAmount,
|
||||||
showQr,
|
showQr,
|
||||||
|
operationError,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
|
cancel,
|
||||||
invalid,
|
invalid,
|
||||||
}: State.Ready): VNode {
|
}: State.Ready): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -74,6 +77,16 @@ export function ReadyView({
|
|||||||
<SubTitle>
|
<SubTitle>
|
||||||
<i18n.Translate>Digital cash transfer</i18n.Translate>
|
<i18n.Translate>Digital cash transfer</i18n.Translate>
|
||||||
</SubTitle>
|
</SubTitle>
|
||||||
|
{operationError && (
|
||||||
|
<ErrorTalerOperation
|
||||||
|
title={
|
||||||
|
<i18n.Translate>
|
||||||
|
Could not finish the transfer creation
|
||||||
|
</i18n.Translate>
|
||||||
|
}
|
||||||
|
error={operationError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<section style={{ textAlign: "left" }}>
|
<section style={{ textAlign: "left" }}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Subject"
|
label="Subject"
|
||||||
@ -112,6 +125,13 @@ export function ReadyView({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<section>
|
||||||
|
<Link upperCased onClick={cancel.onClick}>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import { AbsoluteTime, AmountJson, TalerErrorDetail } from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
import { ButtonHandler } from "../../mui/handlers.js";
|
import { ButtonHandler } from "../../mui/handlers.js";
|
||||||
@ -25,6 +25,7 @@ import { LoadingUriView, ReadyView } from "./views.js";
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
talerPayPushUri: string;
|
talerPayPushUri: string;
|
||||||
|
onClose: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
@ -46,10 +47,13 @@ export namespace State {
|
|||||||
|
|
||||||
export interface BaseInfo {
|
export interface BaseInfo {
|
||||||
error: undefined;
|
error: undefined;
|
||||||
|
cancel: ButtonHandler;
|
||||||
}
|
}
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
amount: AmountJson,
|
amount: AmountJson,
|
||||||
|
summary: string | undefined;
|
||||||
|
expiration: AbsoluteTime | undefined;
|
||||||
error: undefined;
|
error: undefined;
|
||||||
accept: ButtonHandler;
|
accept: ButtonHandler;
|
||||||
operationError?: TalerErrorDetail;
|
operationError?: TalerErrorDetail;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util";
|
import { AbsoluteTime, Amounts, TalerErrorDetail, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
|
||||||
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
@ -22,7 +22,7 @@ import * as wxApi from "../../wxApi.js";
|
|||||||
import { Props, State } from "./index.js";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ talerPayPushUri }: Props,
|
{ talerPayPushUri, onClose }: Props,
|
||||||
api: typeof wxApi,
|
api: typeof wxApi,
|
||||||
): State {
|
): State {
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
@ -45,13 +45,18 @@ export function useComponentState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { amount, peerPushPaymentIncomingId } = hook.response
|
const { amount: purseAmount, contractTerms, peerPushPaymentIncomingId } = hook.response
|
||||||
|
|
||||||
|
const amount: string = contractTerms?.amount
|
||||||
|
const summary: string | undefined = contractTerms?.summary
|
||||||
|
const expiration: TalerProtocolTimestamp | undefined = contractTerms?.purse_expiration
|
||||||
|
|
||||||
async function accept(): Promise<void> {
|
async function accept(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const resp = await api.acceptPeerPushPayment({
|
const resp = await api.acceptPeerPushPayment({
|
||||||
peerPushPaymentIncomingId
|
peerPushPaymentIncomingId
|
||||||
})
|
})
|
||||||
|
await onClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TalerError) {
|
if (e instanceof TalerError) {
|
||||||
setOperationError(e.errorDetail)
|
setOperationError(e.errorDetail)
|
||||||
@ -67,6 +72,11 @@ export function useComponentState(
|
|||||||
accept: {
|
accept: {
|
||||||
onClick: accept
|
onClick: accept
|
||||||
},
|
},
|
||||||
|
summary,
|
||||||
|
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
|
||||||
|
cancel: {
|
||||||
|
onClick: onClose
|
||||||
|
},
|
||||||
operationError
|
operationError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,4 +33,5 @@ export const Ready = createExample(ReadyView, {
|
|||||||
fraction: 0,
|
fraction: 0,
|
||||||
},
|
},
|
||||||
accept: {},
|
accept: {},
|
||||||
|
cancel: {},
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,8 @@ import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
|
|||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
import { LogoHeader } from "../../components/LogoHeader.js";
|
import { LogoHeader } from "../../components/LogoHeader.js";
|
||||||
import { Part } from "../../components/Part.js";
|
import { Part } from "../../components/Part.js";
|
||||||
import { SubTitle, WalletAction } from "../../components/styled/index.js";
|
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js";
|
||||||
|
import { Time } from "../../components/Time.js";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
@ -38,7 +39,10 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
|||||||
|
|
||||||
export function ReadyView({
|
export function ReadyView({
|
||||||
accept,
|
accept,
|
||||||
|
summary,
|
||||||
|
expiration,
|
||||||
amount,
|
amount,
|
||||||
|
cancel,
|
||||||
operationError,
|
operationError,
|
||||||
}: State.Ready): VNode {
|
}: State.Ready): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -59,16 +63,32 @@ export function ReadyView({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<section style={{ textAlign: "left" }}>
|
<section style={{ textAlign: "left" }}>
|
||||||
|
<Part
|
||||||
|
title={<i18n.Translate>Subject</i18n.Translate>}
|
||||||
|
text={<div>{summary}</div>}
|
||||||
|
/>
|
||||||
<Part
|
<Part
|
||||||
title={<i18n.Translate>Amount</i18n.Translate>}
|
title={<i18n.Translate>Amount</i18n.Translate>}
|
||||||
text={<Amount value={amount} />}
|
text={<Amount value={amount} />}
|
||||||
/>
|
/>
|
||||||
|
<Part
|
||||||
|
title={<i18n.Translate>Valid until</i18n.Translate>}
|
||||||
|
text={<Time timestamp={expiration} format="dd MMMM yyyy, HH:mm" />}
|
||||||
|
kind="neutral"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<Button variant="contained" color="success" onClick={accept.onClick}>
|
<Button variant="contained" color="success" onClick={accept.onClick}>
|
||||||
<i18n.Translate>Pickup</i18n.Translate>
|
<i18n.Translate>
|
||||||
|
Receive {<Amount value={amount} />}
|
||||||
|
</i18n.Translate>
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<Link upperCased onClick={cancel.onClick}>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
</WalletAction>
|
</WalletAction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -258,7 +258,7 @@ export function Application(): VNode {
|
|||||||
<Route
|
<Route
|
||||||
path={Pages.ctaTips}
|
path={Pages.ctaTips}
|
||||||
component={TipPage}
|
component={TipPage}
|
||||||
cancel={() => redirectTo(Pages.balance)}
|
onCancel={() => redirectTo(Pages.balance)}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaWithdraw}
|
path={Pages.ctaWithdraw}
|
||||||
@ -278,16 +278,22 @@ export function Application(): VNode {
|
|||||||
<Route
|
<Route
|
||||||
path={Pages.ctaInvoiceCreate.pattern}
|
path={Pages.ctaInvoiceCreate.pattern}
|
||||||
component={InvoiceCreatePage}
|
component={InvoiceCreatePage}
|
||||||
|
onClose={() => redirectTo(Pages.balance)}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaTransferCreate.pattern}
|
path={Pages.ctaTransferCreate.pattern}
|
||||||
component={TransferCreatePage}
|
component={TransferCreatePage}
|
||||||
|
onClose={() => redirectTo(Pages.balance)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={Pages.ctaInvoicePay}
|
||||||
|
component={InvoicePayPage}
|
||||||
|
onClose={() => redirectTo(Pages.balance)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path={Pages.ctaInvoicePay} component={InvoicePayPage} />
|
|
||||||
<Route
|
<Route
|
||||||
path={Pages.ctaTransferPickup}
|
path={Pages.ctaTransferPickup}
|
||||||
component={TransferPickupPage}
|
component={TransferPickupPage}
|
||||||
|
onClose={() => redirectTo(Pages.balance)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/**
|
{/**
|
||||||
|
@ -317,7 +317,6 @@ export function createDenominationPairTimeline(left: FeeDescription[], right: Fe
|
|||||||
|
|
||||||
//now both lists are non empty and (starts,ends) at the same time
|
//now both lists are non empty and (starts,ends) at the same time
|
||||||
while (li < left.length && ri < right.length && Amounts.cmp(left[li].value, right[ri].value) === 0) {
|
while (li < left.length && ri < right.length && Amounts.cmp(left[li].value, right[ri].value) === 0) {
|
||||||
// console.log('start', li, ri, left[li], right[ri])
|
|
||||||
|
|
||||||
if (AbsoluteTime.cmp(left[li].from, timeCut) !== 0 && AbsoluteTime.cmp(right[ri].from, timeCut) !== 0) {
|
if (AbsoluteTime.cmp(left[li].from, timeCut) !== 0 && AbsoluteTime.cmp(right[ri].from, timeCut) !== 0) {
|
||||||
// timeCut comes from the latest "until" (expiration from the previous)
|
// timeCut comes from the latest "until" (expiration from the previous)
|
||||||
@ -325,7 +324,6 @@ export function createDenominationPairTimeline(left: FeeDescription[], right: Fe
|
|||||||
// it should be the same as the "from" from one of the latest left or right
|
// it should be the same as the "from" from one of the latest left or right
|
||||||
// otherwise it means that there is missing a gap object in the middle
|
// otherwise it means that there is missing a gap object in the middle
|
||||||
// the list is not complete and the behavior is undefined
|
// the list is not complete and the behavior is undefined
|
||||||
console.log(li, ri, timeCut)
|
|
||||||
throw Error('one of the list is not completed: list[i].until !== list[i+1].from')
|
throw Error('one of the list is not completed: list[i].until !== list[i+1].from')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -692,8 +692,6 @@ describe("Denomination timeline pair creation", () => {
|
|||||||
|
|
||||||
|
|
||||||
const pairs = createDenominationPairTimeline(left, right)
|
const pairs = createDenominationPairTimeline(left, right)
|
||||||
|
|
||||||
console.log(JSON.stringify(pairs, undefined, 2))
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -228,7 +228,6 @@ interface ListOfKnownCurrencies {
|
|||||||
*/
|
*/
|
||||||
export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> {
|
export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> {
|
||||||
return callBackend("listCurrencies", {}).then((result) => {
|
return callBackend("listCurrencies", {}).then((result) => {
|
||||||
console.log("result list", result);
|
|
||||||
const auditors = result.trustedAuditors.map(
|
const auditors = result.trustedAuditors.map(
|
||||||
(a: Record<string, string>) => ({
|
(a: Record<string, string>) => ({
|
||||||
name: a.currency,
|
name: a.currency,
|
||||||
|
Loading…
Reference in New Issue
Block a user