From 939729004a8f5fecde19e679a0672843c496662f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 2 May 2022 19:21:34 -0300 Subject: tip and refund stories and test --- .../taler-wallet-webextension/src/cta/Refund.tsx | 350 ++++++++++++++++----- 1 file changed, 266 insertions(+), 84 deletions(-) (limited to 'packages/taler-wallet-webextension/src/cta/Refund.tsx') diff --git a/packages/taler-wallet-webextension/src/cta/Refund.tsx b/packages/taler-wallet-webextension/src/cta/Refund.tsx index 23231328a..f69fc4311 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund.tsx +++ b/packages/taler-wallet-webextension/src/cta/Refund.tsx @@ -21,129 +21,311 @@ */ import { - amountFractionalBase, AmountJson, Amounts, - ApplyRefundResponse, + NotificationType, + Product, } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; -import { SubTitle, Title } from "../components/styled/index.js"; +import { Amount } from "../components/Amount.js"; +import { Loading } from "../components/Loading.js"; +import { LoadingError } from "../components/LoadingError.js"; +import { LogoHeader } from "../components/LogoHeader.js"; +import { Part } from "../components/Part.js"; +import { + Button, + ButtonSuccess, + SubTitle, + WalletAction, +} from "../components/styled/index.js"; import { useTranslationContext } from "../context/translation.js"; +import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; +import { ButtonHandler } from "../mui/handlers.js"; import * as wxApi from "../wxApi.js"; +import { ProductList } from "./Pay.js"; interface Props { talerRefundUri?: string; } export interface ViewProps { - applyResult: ApplyRefundResponse; + state: State; } -export function View({ applyResult }: ViewProps): VNode { +export function View({ state }: ViewProps): VNode { const { i18n } = useTranslationContext(); - return ( -
- GNU Taler Wallet -
+ if (state.status === "loading") { + if (!state.hook) { + return ; + } + return ( + Could not load refund status} + error={state.hook} + /> + ); + } + + if (state.status === "ignored") { + return ( + + + - Refund Status + Digital cash refund -

- - The product {applyResult.info.summary} has received a total - effective refund of{" "} - - . -

- {applyResult.pendingAtExchange ? ( +
+

+ You've ignored the tip. +

+
+
+ ); + } + + if (state.status === "in-progress") { + return ( + + + + + Digital cash refund + +

- - Refund processing is still in progress. - + The refund is in progress.

- ) : null} - {!Amounts.isZero(applyResult.amountRefundGone) ? ( +
+
+ Total to refund} + text={} + kind="negative" + /> +
+ {state.products && state.products.length ? ( +
+ +
+ ) : undefined} +
+ +
+
+ ); + } + + if (state.status === "completed") { + return ( + + + + + Digital cash refund + +

- - The refund amount of{" "} - could not be - applied. - + this refund is already accepted.

- ) : null} -
-
+ + + ); + } + + return ( + + + + + Digital cash refund + +
+

+ + The merchant "{state.merchantName}" is offering you + a refund. + +

+
+
+ Total to refund} + text={} + kind="negative" + /> +
+ {state.products && state.products.length ? ( +
+ +
+ ) : undefined} +
+ + Confirm refund + + +
+
); } -export function RefundPage({ talerRefundUri }: Props): VNode { - const [applyResult, setApplyResult] = useState< - ApplyRefundResponse | undefined - >(undefined); - const { i18n } = useTranslationContext(); - const [errMsg, setErrMsg] = useState(undefined); + +type State = Loading | Ready | Ignored | InProgress | Completed; + +interface Loading { + status: "loading"; + hook: HookError | undefined; +} +interface Ready { + status: "ready"; + hook: undefined; + merchantName: string; + products: Product[] | undefined; + amount: AmountJson; + accept: ButtonHandler; + ignore: ButtonHandler; + orderId: string; +} +interface Ignored { + status: "ignored"; + hook: undefined; + merchantName: string; +} +interface InProgress { + status: "in-progress"; + hook: undefined; + merchantName: string; + products: Product[] | undefined; + amount: AmountJson; + progress: number; +} +interface Completed { + status: "completed"; + hook: undefined; + merchantName: string; + products: Product[] | undefined; + amount: AmountJson; +} + +export function useComponentState( + talerRefundUri: string | undefined, + api: typeof wxApi, +): State { + const [ignored, setIgnored] = useState(false); + + const info = useAsyncAsHook(async () => { + if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND"); + const refund = await api.prepareRefund({ talerRefundUri }); + return { refund, uri: talerRefundUri }; + }); useEffect(() => { - if (!talerRefundUri) return; - const doFetch = async (): Promise => { - try { - const result = await wxApi.applyRefund(talerRefundUri); - setApplyResult(result); - } catch (e) { - if (e instanceof Error) { - setErrMsg(e.message); - console.log("err message", e.message); - } - } + api.onUpdateNotification([NotificationType.RefreshMelted], () => { + info?.retry(); + }); + }); + + if (!info || info.hasError) { + return { + status: "loading", + hook: info, }; - doFetch(); - }, [talerRefundUri]); + } - console.log("rendering"); + const { refund, uri } = info.response; - if (!talerRefundUri) { - return ( - - missing taler refund uri - - ); + const doAccept = async (): Promise => { + await api.applyRefund(uri); + info.retry(); + }; + + const doIgnore = async (): Promise => { + setIgnored(true); + }; + + if (ignored) { + return { + status: "ignored", + hook: undefined, + merchantName: info.response.refund.info.merchant.name, + }; } - if (errMsg) { - return ( - - Error: {errMsg} - - ); + const pending = refund.total > refund.applied + refund.failed; + const completed = refund.total > 0 && refund.applied === refund.total; + + if (pending) { + return { + status: "in-progress", + hook: undefined, + amount: Amounts.parseOrThrow(info.response.refund.amountEffectivePaid), + merchantName: info.response.refund.info.merchant.name, + products: info.response.refund.info.products, + progress: (refund.applied + refund.failed) / refund.total, + }; } - if (!applyResult) { + if (completed) { + return { + status: "completed", + hook: undefined, + amount: Amounts.parseOrThrow(info.response.refund.amountEffectivePaid), + merchantName: info.response.refund.info.merchant.name, + products: info.response.refund.info.products, + }; + } + + return { + status: "ready", + hook: undefined, + amount: Amounts.parseOrThrow(info.response.refund.amountEffectivePaid), + merchantName: info.response.refund.info.merchant.name, + products: info.response.refund.info.products, + orderId: info.response.refund.info.orderId, + accept: { + onClick: doAccept, + }, + ignore: { + onClick: doIgnore, + }, + }; +} + +export function RefundPage({ talerRefundUri }: Props): VNode { + const { i18n } = useTranslationContext(); + + const state = useComponentState(talerRefundUri, wxApi); + + if (!talerRefundUri) { return ( - Updating refund status + missing taler refund uri ); } - return ; + return ; } -export function renderAmount(amount: AmountJson | string): VNode { - let a; - if (typeof amount === "string") { - a = Amounts.parse(amount); - } else { - a = amount; - } - if (!a) { - return (invalid amount); - } - const x = a.value + a.fraction / amountFractionalBase; +function ProgressBar({ value }: { value: number }): VNode { return ( - - {x} {a.currency} - +
+
+
); } - -function AmountView({ amount }: { amount: AmountJson | string }): VNode { - return renderAmount(amount); -} -- cgit v1.2.3