diff options
author | Florian Dold <florian.dold@gmail.com> | 2019-08-30 17:27:59 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2019-08-30 17:27:59 +0200 |
commit | 5ec344290efd937fa82c0704bc7c204a0bf14c78 (patch) | |
tree | 7d9594180bbc7b5fa2b4a8dbe24272e7a82301f3 /src/webex/pages | |
parent | defbf625bdef0f8a666b72b8ce99de5e01af6b91 (diff) | |
download | wallet-core-5ec344290efd937fa82c0704bc7c204a0bf14c78.tar.xz |
support for tipping protocol changes
Diffstat (limited to 'src/webex/pages')
-rw-r--r-- | src/webex/pages/tip.tsx | 203 |
1 files changed, 82 insertions, 121 deletions
diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx index c13120c43..a3f5c38c3 100644 --- a/src/webex/pages/tip.tsx +++ b/src/webex/pages/tip.tsx @@ -14,7 +14,6 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - /** * Page shown to the user to confirm creation * of a reserve, usually requested by the bank. @@ -28,152 +27,114 @@ import URI = require("urijs"); import * as i18n from "../../i18n"; -import { - acceptTip, - getReserveCreationInfo, - getTipStatus, -} from "../wxApi"; +import { acceptTip, getReserveCreationInfo, getTipStatus } from "../wxApi"; -import { - WithdrawDetailView, - renderAmount, -} from "../renderHtml"; +import { WithdrawDetailView, renderAmount } from "../renderHtml"; import * as Amounts from "../../amounts"; -import { TipToken } from "../../talerTypes"; -import { ReserveCreationInfo, TipStatus } from "../../walletTypes"; +import { useState, useEffect } from "react"; +import { TipStatus } from "../../walletTypes"; -interface TipDisplayProps { - tipToken: TipToken; +interface LoadingButtonProps { + loading: boolean; } -interface TipDisplayState { - tipStatus?: TipStatus; - rci?: ReserveCreationInfo; - working: boolean; - discarded: boolean; +function LoadingButton( + props: + & React.PropsWithChildren<LoadingButtonProps> + & React.DetailedHTMLProps< + React.ButtonHTMLAttributes<HTMLButtonElement>, + HTMLButtonElement + >, +) { + return ( + <button + className="pure-button pure-button-primary" + type="button" + {...props} + > + {props.loading ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /></span> : null} + {props.children} + </button> + ); } -class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { - constructor(props: TipDisplayProps) { - super(props); - this.state = { working: false, discarded: false }; - } - - async update() { - const tipStatus = await getTipStatus(this.props.tipToken); - this.setState({ tipStatus }); - const rci = await getReserveCreationInfo(tipStatus.exchangeUrl, tipStatus.amount); - this.setState({ rci }); - } - - componentDidMount() { - this.update(); - const port = chrome.runtime.connect(); - port.onMessage.addListener((msg: any) => { - if (msg.notify) { - console.log("got notified"); - this.update(); - } - }); - this.update(); - } - - renderExchangeInfo() { - const rci = this.state.rci; - if (!rci) { - return <p>Waiting for info about exchange ...</p>; - } - const totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount; - return ( - <div> - <p> - The tip is handled by the exchange <strong>{rci.exchangeInfo.baseUrl}</strong>.{" "} - The exchange provider will charge - {" "} - <strong>{renderAmount(totalCost)}</strong> - {" "}. - </p> - <WithdrawDetailView rci={rci} /> - </div> - ); +function TipDisplay(props: { talerTipUri: string }) { + const [tipStatus, setTipStatus] = useState<TipStatus | undefined>(undefined); + const [discarded, setDiscarded] = useState(false); + const [loading, setLoading] = useState(false); + const [finished, setFinished] = useState(false); + + useEffect(() => { + const doFetch = async () => { + const ts = await getTipStatus(props.talerTipUri); + setTipStatus(ts); + }; + doFetch(); + }, []); + + if (discarded) { + return <span>You've discarded the tip.</span>; } - accept() { - this.setState({ working: true}); - acceptTip(this.props.tipToken); + if (finished) { + return <span>Tip has been accepted!</span>; } - discard() { - this.setState({ discarded: true }); + if (!tipStatus) { + return <span>Loading ...</span>; } - render(): JSX.Element { - const ts = this.state.tipStatus; - if (!ts) { - return <p>Processing ...</p>; - } - - const renderAccepted = () => ( - <> - <p>You've accepted this tip! <a href={ts.nextUrl}>Go back to merchant</a></p> - {this.renderExchangeInfo()} - </> - ); - - const renderButtons = () => ( - <> + const discard = () => { + setDiscarded(true); + }; + + const accept = async () => { + setLoading(true); + await acceptTip(props.talerTipUri); + setFinished(true); + }; + + return ( + <div> + <h2>Tip Received!</h2> + <p> + You received a tip of <strong>{renderAmount(tipStatus.amount)}</strong>{" "} + from <span> </span> + <strong>{tipStatus.merchantOrigin}</strong>. + </p> + <p> + The tip is handled by the exchange{" "} + <strong>{tipStatus.exchangeUrl}</strong>. This exchange will charge fees + of <strong>{renderAmount(tipStatus.totalFees)}</strong> for this + operation. + </p> <form className="pure-form"> - <button - className="pure-button pure-button-primary" - type="button" - disabled={!(this.state.rci && this.state.tipStatus && this.state.tipStatus.tipRecord)} - onClick={() => this.accept()}> - { this.state.working - ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span> - : null } - Accept tip - </button> + <LoadingButton loading={loading} onClick={() => accept()}> + AcceptTip + </LoadingButton> {" "} - <button className="pure-button" type="button" onClick={() => this.discard()}> + <button className="pure-button" type="button" onClick={() => discard()}> Discard tip </button> </form> - { this.renderExchangeInfo() } - </> - ); - - const renderDiscarded = () => ( - <p>You've discarded this tip. <a href={ts.nextUrl}>Go back to merchant.</a></p> - ); - - return ( - <div> - <h2>Tip Received!</h2> - <p>You received a tip of <strong>{renderAmount(ts.amount)}</strong> from <span> </span> - <strong>{ts.merchantDomain}</strong>.</p> - { - this.state.discarded - ? renderDiscarded() - : ts.accepted - ? renderAccepted() - : renderButtons() - } - </div> - ); - } + </div> + ); } async function main() { try { const url = new URI(document.location.href); const query: any = URI.parseQuery(url.query()); + const talerTipUri = query.talerTipUri; + if (typeof talerTipUri !== "string") { + throw Error("talerTipUri must be a string"); + } - const tipToken = TipToken.checked(JSON.parse(query.tip_token)); - - ReactDOM.render(<TipDisplay tipToken={tipToken} />, - document.getElementById("container")!); - + ReactDOM.render( + <TipDisplay talerTipUri={talerTipUri} />, + document.getElementById("container")!, + ); } catch (e) { // TODO: provide more context information, maybe factor it out into a // TODO:generic error reporting function or component. |