merchant-backoffice

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

commit 0dc4e08923a9c4ee743898b6590a28c8151b160e
parent f4b71ececef38778ae813e7557f6ed6b78bfd591
Author: ms <ms@taler.net>
Date:   Mon, 13 Dec 2021 14:33:08 +0100

bank: import and configure SWR

Diffstat:
Mpackages/bank/package.json | 5++++-
Mpackages/bank/src/pages/home/index.tsx | 188++++++++++++++++++++++++++++++++++++-------------------------------------------
2 files changed, 90 insertions(+), 103 deletions(-)

diff --git a/packages/bank/package.json b/packages/bank/package.json @@ -32,7 +32,8 @@ "preact": "^10.5.15", "preact-render-to-string": "^5.1.19", "preact-router": "^3.2.1", - "qrcode-generator": "^1.4.4" + "qrcode-generator": "^1.4.4", + "swr": "1.1" }, "devDependencies": { "@creativebulma/bulma-tooltip": "^1.2.0", @@ -42,6 +43,8 @@ "@storybook/addon-links": "6.2.9", "@storybook/preact": "6.2.9", "@storybook/preset-scss": "^1.0.3", + "@testing-library/preact": "^2.0.1", + "@testing-library/preact-hooks": "^1.1.0", "@types/enzyme": "^3.10.10", "@types/jest": "^27.0.2", "@typescript-eslint/eslint-plugin": "^5.3.0", diff --git a/packages/bank/src/pages/home/index.tsx b/packages/bank/src/pages/home/index.tsx @@ -1,6 +1,8 @@ -import { h, Fragment } from "preact"; -import {useState} from "preact/hooks"; +import useSWR, { SWRConfig } from "swr"; +import { h, Fragment, ComponentChildren, VNode } from "preact"; +import { useState, useEffect, StateUpdater } from "preact/hooks"; import axios from "axios"; +import { Buffer } from 'buffer'; /********************************************** @@ -13,8 +15,8 @@ import axios from "axios"; */ interface BackendStateType { url: string; - username: string | undefined; - password: string | undefined; + username: string; + password: string; } /** @@ -31,20 +33,23 @@ interface RegistrationRequestType { interface PageStateType { isLoggedIn: boolean; hasProblem: boolean; + error?: string; } /** * Bank account specific information. */ interface AccountStateType { - balance: string | undefined; + balance: string; /* FIXME: Need history here. */ +} - /** - * Error message to diplay when one of the - * account state entries failed to arrive. - */ - error: string | undefined; +/******************* + * Helpers. * + ******************/ + +const getRootPath = () => { + return typeof window !== undefined ? window.location.origin + window.location.pathname : '/'; } /******************* @@ -56,59 +61,40 @@ interface AccountStateType { * login credentials and backend's * base URL. */ -function useBackendState( - state: BackendStateType = { - url: window.location.href, // Should never change. - username: undefined, - password: undefined, -}): [ - BackendStateType, - (newState: BackendStateType) => void | - ((fn: (oldState: BackendStateType) => void) => void) +type BackendStateTypeOpt = BackendStateType | undefined; +function useBackendState(state?: BackendStateType): [ + BackendStateTypeOpt, StateUpdater<BackendStateTypeOpt> ] { - return useState<BackendStateType>(state); + if (state) return useState<BackendStateTypeOpt>(state); + return useState<BackendStateTypeOpt>(); } -function useAccountState<AccountStateType>( - state: AccountStateType = { - balance: undefined, - error: undefined -}): [ - AccountStateType, (fn: (state: AccountStateType) => void) => void -] { - return useState<AccountStateType>(state); +/** + * Keep mere business information, like account balance or + * transactions history. + */ +type AccountStateTypeOpt = AccountStateType | undefined; +function useAccountState(state?: AccountStateType): [ + AccountStateTypeOpt, StateUpdater<AccountStateTypeOpt>] { + if (state) return useState<AccountStateTypeOpt>(state); + return useState<AccountStateTypeOpt>(); } function usePageState( state: PageStateType = { isLoggedIn: false, hasProblem: false, -}): [ - PageStateType, (newState: PageStateType) => any | - ((fn: (state: PageStateType) => void) => void) -] { +}): [PageStateType, StateUpdater<PageStateType>] { return useState<PageStateType>(state); } -/************ - * Helpers. * - ***********/ - -async function post(url: string, data: any): Promise<number> { - // Mock 200 OK responses, at this moment. - return new Promise( - // (res, rej) => {setTimeout(() => {res(404);});} - (res, rej) => {setTimeout(() => {res(200);});} - ); -} - -async function get(url: string): Promise<number> { - // Mock 200 OK responses, at this moment. - return new Promise( - (res, rej) => {setTimeout(() => {res(404);});} - // (res, rej) => {setTimeout(() => {res(200);});} - ); -} +/** + * Request preparators. + * + * These functions aim at sanitizing the input received + * from users - for example via a HTML form - and create + * a HTTP request object out of that. + */ /****************** * HTTP wrappers. * @@ -163,8 +149,7 @@ async function accountInfoCall( } } }; - const resp = await get(url) - handleResp(resp) + handleResp(200) } /** @@ -177,10 +162,9 @@ async function accountInfoCall( async function registrationCall( url: string, req: RegistrationRequestType, - // On success, that will update the username and password to the state. - backendStateSetter: (fn: (state: BackendStateType) => void) => void, - // Communicate the request outcome to the page. - pageStateSetter: (fn: (state: PageStateType) => void) => void, + backendStateSetter: StateUpdater<BackendStateTypeOpt>, + pageStateSetter: StateUpdater<PageStateType>, + // pageStateSetter: (fn: (state: PageStateType) => void) => void, ) { console.log("Try to register", req); var handleResp = (respStatus: number) => { @@ -189,6 +173,7 @@ async function registrationCall( pageStateSetter(state => ({...state, isLoggedIn: true})); backendStateSetter(state => ({ ...state, + url: url, username: req.username, password: req.password, })); @@ -199,8 +184,7 @@ async function registrationCall( } } } - var resp = await post(url, req); - handleResp(resp); + handleResp(200); } /************************** @@ -215,52 +199,49 @@ export function Account(props: {balance: string}) { } /** + * Factor out login credentials. + */ +function SWRWithCredentials(props: any): VNode { + const { username, password } = props; + const headers = new Headers(); + headers.append( + "Authorization", + `Basic ${Buffer.from(username + ":" + password).toString("base64")}` + ); + return ( + <SWRConfig value={{fetcher: (url) => fetch(url, {headers: headers}).then(r => (r.json()))}}> + {props.children} + </SWRConfig>); +} + +/** * If the user is logged in, it displays * the balance, otherwise it offers to login. */ -export function BankHome() { - var [backendState, backendStateSetter] = useBackendState<BackendStateType>(); - var [pageState, pageStateSetter] = usePageState<PageStateType>(); - var [accountState, accountStateSetter] = useAccountState<AccountStateType>(); - - // Prepare/check registration request. - var registrationData: {}; // Untyped collector of user input. - let prepareRegistrationRequest = (): RegistrationRequestType => { - - console.log("Preparing registration request", registrationData); - if (!("username" in registrationData)) { - pageStateSetter({...pageState, hasProblem: true}); - return; - } - if (!("password" in registrationData)) { - pageStateSetter({...pageState, hasProblem: true}); - return; - } - const u: string = registrationData["username"] - const p: string = registrationData["password"] - // Here, input is valid. - return {username: u, password: p}; - } +export function BankHome(): VNode { + var [backendState, backendStateSetter] = useBackendState(); + var [pageState, pageStateSetter] = usePageState(); + var [accountState, accountStateSetter] = useAccountState(); if (pageState.hasProblem) { return <p>Page has a problem.</p>; } + /** + * Credentials were correct, now try to render the + * bank account page, with balance and transactions + * history */ if (pageState.isLoggedIn) { - if (typeof accountState.error !== "undefined") { - console.log(accountState); - return <p>The page could not load correctly: {accountState.error}</p>; + if (typeof backendState === "undefined") { + console.log("Credentials not found in state, even after login."); + pageStateSetter((state) => ({...state, hasProblem: true})); + return <p>Page has a problem</p>; } - if (typeof accountState.balance === "undefined") { - // Need one: request and trigger new state! - accountInfoCall(backendState, accountStateSetter); - return; - } - - return <Fragment> - <p>Welcome {backendState.username}!</p> - <Account balance={accountState.balance} /> - </Fragment>; + return <SWRWithCredentials + username={backendState.username} + password={backendState.password}> + <p>Hey!</p> + </SWRWithCredentials> /** * FIXME: need to offer a Taler withdraw button here. @@ -275,27 +256,30 @@ export function BankHome() { */ } - // Proceede collecting registration data. + var registrationData: RegistrationRequestType; return <div> <input type="text" placeholder="username" + required onInput={(e): void => { registrationData = {...registrationData, username: e.currentTarget.value}; }} / > <input type="text" placeholder="password" + required onInput={(e): void => { registrationData = {...registrationData, password: e.currentTarget.value}; }} / > <button - onClick={() => {registrationCall( - backendState.url, - prepareRegistrationRequest(), - backendStateSetter, - pageStateSetter, - )}}>Submit</button> + onClick={() => { + registrationCall( + getRootPath(), + registrationData, + backendStateSetter, + pageStateSetter, + )}}>Submit</button> </div> }