merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit 1e8fbfc481346f39cc05a7532dcfb92a8240a7d4
parent 329acdc5035845cf06352c158a6c6479bdf38fd3
Author: ms <ms@taler.net>
Date:   Mon,  4 Apr 2022 17:52:49 +0200

copying pybank's look.  WIP

Diffstat:
Mpackages/bank/src/pages/home/index.tsx | 303++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
1 file changed, 199 insertions(+), 104 deletions(-)

diff --git a/packages/bank/src/pages/home/index.tsx b/packages/bank/src/pages/home/index.tsx @@ -80,7 +80,9 @@ interface Amount { */ interface PageStateType { isLoggedIn: boolean; + isRawPayto: boolean; tryRegister: boolean; + tryManualTransfer: boolean; showPublicHistories: boolean; hasError: boolean; withdrawalInProgress: boolean; @@ -119,7 +121,7 @@ function goPublicAccounts(pageStateSetter: StateUpdater<PageStateType>) { * replace comma with a dot. Returns 'false' whenever * the input is invalid, the valid amount otherwise. */ -function validateAmount(maybeAmount: string): string { +function validateAmount(maybeAmount: string): any { const amountRegex = "^[0-9]+(\.[0-9]+)?$"; if (typeof maybeAmount !== "undefined" || maybeAmount !== "") { console.log("Maybe valid amount", maybeAmount); @@ -278,7 +280,9 @@ function useAccountState( function usePageState( state: PageStateType = { isLoggedIn: false, + isRawPayto: false, tryRegister: false, + tryManualTransfer: false, showPublicHistories: false, hasError: false, withdrawalInProgress: false, @@ -668,14 +672,17 @@ async function registrationCall( * Functional components. * *************************/ -function ErrorBanner(Props: any): VNode { +function ErrorBanner(Props: any): VNode | null { const [pageState, pageStateSetter] = Props.pageState; const i18n = useTranslator(); - if (!pageState.hasError) return; + if (!pageState.hasError) return null; return ( <p class="informational informational-fail">{pageState.error} &nbsp;&nbsp;<a href="#" onClick={() => { - pageStateSetter((prevState: PageStateType) =>({...prevState, hasError: false}))}}> + pageStateSetter(function (prevState: PageStateType) { + delete prevState.error; // delete error message + return {...prevState, hasError: false} // delete error state + })}}> {i18n`Clear`} </a> </p>); @@ -698,7 +705,7 @@ function BankFrame(Props: any): VNode { This part of the demo shows how a bank that supports Taler directly would work. In addition to using your own bank account, you can also see the transaction history of - some <a href="#" onclick={goPublicAccounts(pageStateSetter)}>Public Accounts</a>. + some <a href="#" onClick={goPublicAccounts(pageStateSetter)}>Public Accounts</a>. </Translate></p> </div> <a href="https://taler.net/"> @@ -737,51 +744,120 @@ function BankFrame(Props: any): VNode { } function PaytoWireTransfer(Props: any): VNode { - const {backendState, pageStateSetter} = Props; const currency = useContext(CurrencyContext); + const [pageState, pageStateSetter] = useContext(PageContext); // NOTE: used for go-back button? const i18n = useTranslator(); const amountRegex = "^[0-9]+(\.[0-9]+)?$"; + const ibanRegex = "^[A-Z][A-Z][0-9]+$"; var amountInput = ""; + var receiverInput = ""; + var subjectInput = ""; var transactionData: TransactionRequestType; - - return <div> - <input - type="text" - placeholder="amount" - pattern={amountRegex} - onInput={(e): void => { - amountInput = e.currentTarget.value; - }} /> - <label>{currency}</label> - <input - type="text" - placeholder="payto address" // changing this breaks tests. - required - onInput={(e): void => { - transactionData = { - ...transactionData, - paytoUri: e.currentTarget.value, - }; - }} /> - <button onClick={() => { - amountInput = validateAmount(amountInput); - /** - * By invalid amounts, the validator prints error messages - * on the console, and the browser colourizes the amount input - * box to indicate a error. - */ - if (!amountInput) return; - transactionData = { - ...transactionData, - amount: `${currency}:${amountInput}` - }; - createTransactionCall( - transactionData, - backendState, - pageStateSetter - ); - }}>{i18n`Create wire transfer`}</button> - </div> + + console.log("wire form page state", pageState); + const goBackForm = <a href="#" onClick={ + () => pageStateSetter((prevState: PageStateType) => ({...prevState, tryManualTransfer: false})) + }>{i18n`Go back`}</a>; + const goBackRawPayto = <a href="#" onClick={ + () => pageStateSetter((prevState: PageStateType) => ({...prevState, isRawPayto: false})) + }>{i18n`Go back`}</a>; + if (!pageState.isRawPayto) { + console.log("wire transfer form"); + return (<article> + <div> + <h2>{i18n`Wire transfer`}</h2> + <p>{i18n`Transfer money to another account of this bank:`}<br /><br /></p> + <div name="wire-transfer-form"> + <input + type="text" + placeholder="receiver iban" + required + pattern={ibanRegex} + onInput={(e): void => { + receiverInput = e.currentTarget.value; + }} /><br /><br /> + <input + type="text" + placeholder="subject" + onInput={(e): void => { + subjectInput = e.currentTarget.value; + }} /><br /><br /> + <input + type="text" + placeholder="amount" + pattern={amountRegex} + onInput={(e): void => { + amountInput = e.currentTarget.value; + }} />&nbsp;<label>{`${currency}:X.Y`}</label><br /><br /> + <input + type="submit" + onClick={() => { + amountInput = validateAmount(amountInput); + /** + * By invalid amounts, the validator prints error messages + * on the console, and the browser colourizes the amount input + * box to indicate a error. + */ + if (!amountInput) return; + if (!RegExp(ibanRegex).test(receiverInput)) return; + transactionData = { + paytoUri: `payto://iban/${receiverInput}?message=${subjectInput}`, + amount: `${currency}:${amountInput}` + }; + createTransactionCall( + transactionData, + Props.backendState, + pageStateSetter + ); + }} /> + </div> + <p><a + href="#" + onClick={() => { + console.log("switch to raw payto form"); + pageStateSetter((prevState) => ({...prevState, isRawPayto: true})); + }}>{i18n`Want to try the raw payto://-format?`} + </a></p> + </div> + {goBackForm} + </article>); + } + console.log("rendering raw payto form"); + return (<article> + <div> + <h2>{i18n`Wire transfer`}</h2> + <p>{i18n`Transfer money via the Payto system:`}<br /><br /> + Address pattern: <code style="font-size: 15px"> + payto://iban/[receiver-iban]?message=[subject]&amount=[{currency}:X.Y] + </code> + </p> + <div name="payto-form"> + <input name="address" + size={90} + required + placeholder={i18n`payto address`} + pattern="payto://x-taler-bank/[a-z\.]+(:[0-9]+)?/[0-9a-zA-Z]+\?message=[a-zA-Z0-9 ]+&amount={currency}:[0-9]+(\.[0-9]+)?" + onInput={(e): void => { + transactionData = { + ...transactionData, + paytoUri: e.currentTarget.value, + }; + }} /> + <input class="pure-button pure-button-primary" + type="submit" + value={i18n`Confirm`} + onClick={() => { + if (typeof transactionData.paytoUri === "undefined" || + transactionData.paytoUri.length === 0) return; + createTransactionCall( + transactionData, + Props.backendState, + pageStateSetter); + }} /> + </div> + </div> + <p>{goBackRawPayto}</p> + </article>); } /** @@ -794,7 +870,11 @@ function TalerWithdrawal(Props: any): VNode { var submitAmount = ""; // without currency. const amountRegex = "^[0-9]+(\.[0-9]+)?$"; - var submitButton = <button + var submitButton = <input + id="select-exchange" + class="pure-button pure-button-primary" + type="submit" + value={i18n`Start withdrawal`} onClick={() => { submitAmount = validateAmount(submitAmount); /** @@ -807,17 +887,15 @@ function TalerWithdrawal(Props: any): VNode { `${currency}:${submitAmount}`, backendState, pageStateSetter - )}}>{i18n`Charge Taler wallet`} - </button>; + )}} />; return (<article> <div> <h2>{i18n`Withdraw Money into a Taler wallet`}</h2> <div id="reserve-form" class="pure-form" - method="post" name="tform"> - {i18n`Amount to withdraw`} + {i18n`Amount to withdraw`}:&nbsp; <select id="reserve-amount" name="withdraw-amount" class="amount" autofocus> <option value="5.00">5.00</option> <option value="10.00">10.00</option> @@ -828,9 +906,9 @@ function TalerWithdrawal(Props: any): VNode { type="text" readonly class="currency-indicator" - size={balance.currency.length} - tabindex="-1" value="{{ currency }}" /> - {submitButton} + size={currency.length} + tabIndex={-1} value={currency} /> + &nbsp;{submitButton} </div> </div> </article>); @@ -885,7 +963,7 @@ function LoginForm(Props: any): VNode { */ function RegistrationForm(Props: any): VNode { const [pageState, pageStateSetter] = useContext(PageContext); - var submitData: CredentialsRequestType = {}; + var submitData: CredentialsRequestType; const i18n = useTranslator(); return ( @@ -924,8 +1002,10 @@ function RegistrationForm(Props: any): VNode { <button class="pure-button pure-button-primary" onClick={() => { + console.log("maybe submitting the registration.."); if (!("password" in submitData) || !("username" in submitData)) return; if (submitData.password.length === 0 || submitData.username.length === 0) return; + console.log("submitting the registration.."); registrationCall( submitData, Props.backendStateSetter, // will store BE URL, if OK. @@ -1000,15 +1080,41 @@ function Transactions(Props: any): VNode { } /** - * Show only the account's balance. + * Show only the account's balance. NOTE: the backend state + * is mostly needed to provide the user's credentials to POST + * to the bank. */ function Account(Props: any): VNode { + const { accountLabel, backendState } = Props; + const [pageState, pageStateSetter] = useContext(PageContext); const { + withdrawalInProgress, + tryManualTransfer, withdrawalOutcome, transferOutcome, - talerWithdrawUri, - accountLabel } = Props; - const pageState = useContext(PageContext); + talerWithdrawUri } = pageState; + const i18n = useTranslator(); + const logOut = ( + <a + href="#" + class="pure-button logout-button" + onClick={() => { + setTxPageNumber(0); + pageStateSetter((prevState: PageStateType) => { + const { + talerWithdrawUri, + withdrawalOutcome, + withdrawalId, ...rest } = prevState; + return { + ...rest, + isLoggedIn: false, + withdrawalInProgress: false, + isRawPayto: false, + tryManualTransfer: false, + }; + }); + }}>[{i18n`Logout`}]</a>); + /** * This part shows a list of transactions: with 5 elements by * default and offers a "load more" button. @@ -1018,30 +1124,28 @@ function Account(Props: any): VNode { for (let i = 0; i <= txPageNumber; i++) { txsPages.push(<Transactions accountLabel={accountLabel} pageNumber={i} />) } - const i18n = useTranslator(); /** * Getting the bank account balance. */ const { data, error } = useSWR(`access-api/accounts/${accountLabel}`); if (typeof error !== "undefined") { console.log("account error", error); - /** * FIXME: try only one invocation of pageStateSetter, * after having decided the error message in the case-branch. */ switch(error.status) { case 404: { - pageState[1]((prevState) => ({ + pageStateSetter((prevState: PageStateType) => ({ ...prevState, hasError: true, isLoggedIn: false, - error: i18n`Username or account label not found.` + error: i18n`Username or account label '${accountLabel}' not found. Won't login.` })); return <p>Profile not found...</p>; } case 401: { - pageState[1]((prevState) => ({ + pageStateSetter((prevState: PageStateType) => ({ ...prevState, hasError: true, isLoggedIn: false, @@ -1050,7 +1154,7 @@ function Account(Props: any): VNode { return <p>Wrong credentials...</p>; } default: { - pageState[1]((prevState) => ({ + pageStateSetter((prevState: PageStateType) => ({ ...prevState, hasError: true, isLoggedIn: false, @@ -1107,28 +1211,43 @@ function Account(Props: any): VNode { </Fragment>); } const balance = parseAmount(data.balance.amount) + if (tryManualTransfer) { + return ( + <BankFrame> + {logOut}<br /> + <CurrencyContext.Provider value={balance.currency}> + <PaytoWireTransfer backendState={backendState} /> + </CurrencyContext.Provider></BankFrame>); + } return (<BankFrame> <div> <h1 class="nav"> <Translate>Welcome, {accountLabel} ({getIbanFromPayto(data.paytoUri)})!</Translate> </h1> - <a - href="#" - class="pure-button logout-button" - onClick={() => { - setTxPageNumber(0); - pageStateSetter((prevState) => { - const { - talerWithdrawUri, - withdrawalOutcome, - withdrawalId, ...rest } = prevState; - return {...rest, isLoggedIn: false, withdrawalInProgress: false}; - }) - }}>[{i18n`Logout`}]</a><br /> + {logOut}<br /> </div> <section id="menu"> <p>{i18n`Bank account balance:`} <br /> <b>{`${balance.value} ${balance.currency}`}</b></p> </section> + <CurrencyContext.Provider value={balance.currency}> + {Props.children} + </CurrencyContext.Provider> + { + withdrawalInProgress && !transferOutcome && + <TalerWithdrawal + backendState={backendState} + pageStateSetter={pageStateSetter} /> + } + <section id="main"> + <article> + <h2>{i18n`Latest transactions:`}</h2> + <Transactions pageNumber="0" accountLabel={accountLabel} /> + <p><a href="#" onClick={() => + pageStateSetter((prevState: PageStateType) => + ({...prevState, tryManualTransfer: true})) + }>{i18n`Transfer money manually`}</a></p> + </article> + </section> </BankFrame>); } @@ -1191,23 +1310,21 @@ function PublicHistories(Props: any): VNode { switch(error.status) { case 404: { console.log("public accounts: 404", error); - Props.pageStateSetter((prevState) => ({ + Props.pageStateSetter((prevState: PageStateType) => ({ ...prevState, hasError: true, showPublicHistories: false, error: i18n`List of public accounts was not found.` })); - return; } default: { console.log("public accounts: non-404 error", error); - Props.pageStateSetter((prevState) => ({ + Props.pageStateSetter((prevState: PageStateType) => ({ ...prevState, hasError: true, showPublicHistories: false, error: i18n`List of public accounts could not be retrieved.` })); - return; } } } @@ -1298,7 +1415,7 @@ export function BankHome(): VNode { password={backendState.password} backendUrl={backendState.url}> <PageContext.Provider value={[pageState, pageStateSetter]}> - <Account> + <Account accountLabel={backendState.username} backendState={backendState}> { /** * No action is currently being performed (page is 'pristine'): @@ -1349,15 +1466,6 @@ export function BankHome(): VNode { pageStateSetter);}}>{i18n`Abort withdrawal`}</button> </div> } - - { /** - * Profile page is pristine: offer the wire transfer form. - */ - !pageState.withdrawalInProgress && - !pageState.transferOutcome && - <PaytoWireTransfer pageStateSetter={pageStateSetter} - backendState={backendState} /> - } </Account> </PageContext.Provider> </SWRWithCredentials> @@ -1388,16 +1496,3 @@ export function BankHome(): VNode { </PageContext.Provider> ); } - -/* -<SWRWithoutCredentials baseUrl={getRootPath()}> - <LoginForm - pageStateSetter={pageStateSetter} - backendStateSetter={backendStateSetter} /> - <p>Not a customer yet? <a onClick={() => { - pageStateSetter((prevState) => ({...prevState, tryRegister: true}))}}>Register!</a></p> - <p>See our public <a onClick={() => { - pageStateSetter((prevState) => ( - {...prevState, showPublicHistories: true}))}}>transactions</a></p> - </SWRWithoutCredentials> -*/