fix 7426: URI fragment routing

This commit is contained in:
Sebastian 2022-11-07 12:44:09 -03:00
parent afa87c2cf4
commit 3eafb64912
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
4 changed files with 183 additions and 135 deletions

View File

@ -10,11 +10,12 @@
"pretty": "prettier --write src" "pretty": "prettier --write src"
}, },
"dependencies": { "dependencies": {
"@gnu-taler/taler-util": "workspace:*",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"history": "4.10.1",
"jed": "1.1.1", "jed": "1.1.1",
"preact": "10.6.5", "preact": "10.6.5",
"preact-router": "3.2.1", "preact-router": "3.2.1",
"@gnu-taler/taler-util": "workspace:*",
"qrcode-generator": "^1.4.4", "qrcode-generator": "^1.4.4",
"react": "npm:@preact/compat@^17.1.2", "react": "npm:@preact/compat@^17.1.2",
"swr": "1.3.0" "swr": "1.3.0"

View File

@ -17,8 +17,8 @@
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import Match from "preact-router/match"; import Match from "preact-router/match";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { NavigationBar } from "./NavigationBar"; import { NavigationBar } from "./NavigationBar.js";
import { Sidebar } from "./SideBar"; import { Sidebar } from "./SideBar.js";
interface MenuProps { interface MenuProps {
title: string; title: string;

View File

@ -32,6 +32,8 @@ import { useLocalStorage, useNotNullLocalStorage } from "../../hooks/index.js";
import { Translate, useTranslator } from "../../i18n/index.js"; import { Translate, useTranslator } from "../../i18n/index.js";
import "../../scss/main.scss"; import "../../scss/main.scss";
import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util"; import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
import { createHashHistory } from "history";
import Router, { Route, route } from "preact-router";
interface BankUiSettings { interface BankUiSettings {
allowRegistrations: boolean; allowRegistrations: boolean;
@ -94,7 +96,7 @@ const PageContextDefault: PageContextType = [
isLoggedIn: false, isLoggedIn: false,
isRawPayto: false, isRawPayto: false,
showPublicHistories: false, showPublicHistories: false,
tryRegister: false,
withdrawalInProgress: false, withdrawalInProgress: false,
}, },
() => { () => {
@ -158,7 +160,6 @@ interface WireTransferRequestType {
interface PageStateType { interface PageStateType {
isLoggedIn: boolean; isLoggedIn: boolean;
isRawPayto: boolean; isRawPayto: boolean;
tryRegister: boolean;
showPublicHistories: boolean; showPublicHistories: boolean;
hasError: boolean; hasError: boolean;
hasInfo: boolean; hasInfo: boolean;
@ -480,7 +481,6 @@ function usePageState(
state: PageStateType = { state: PageStateType = {
isLoggedIn: false, isLoggedIn: false,
isRawPayto: false, isRawPayto: false,
tryRegister: false,
showPublicHistories: false, showPublicHistories: false,
hasError: false, hasError: false,
hasInfo: false, hasInfo: false,
@ -502,17 +502,18 @@ function usePageState(
//when moving from one page to another //when moving from one page to another
//clean up the info and error bar //clean up the info and error bar
function removeLatestInfo(val: any): ReturnType<typeof retSetter> { function removeLatestInfo(val: any): ReturnType<typeof retSetter> {
const updater = typeof val === 'function' ? val : (c:any) => val const updater = typeof val === "function" ? val : (c: any) => val;
return retSetter((current:any) => { return retSetter((current: any) => {
const cleanedCurrent: PageStateType = {...current, const cleanedCurrent: PageStateType = {
...current,
hasInfo: false, hasInfo: false,
info: undefined, info: undefined,
hasError: false, hasError: false,
errors: undefined, errors: undefined,
timestamp: new Date().getTime() timestamp: new Date().getTime(),
} };
return updater(cleanedCurrent) return updater(cleanedCurrent);
}) });
} }
return [retObj, removeLatestInfo]; return [retObj, removeLatestInfo];
@ -926,7 +927,7 @@ async function registrationCall(
const headers = new Headers(); const headers = new Headers();
headers.append("Content-Type", "application/json"); headers.append("Content-Type", "application/json");
const url = new URL("access-api/testing/register", baseUrl); const url = new URL("access-api/testing/register", baseUrl);
let res: any; let res: Response;
try { try {
res = await fetch(url.href, { res = await fetch(url.href, {
method: "POST", method: "POST",
@ -953,6 +954,16 @@ async function registrationCall(
} }
if (!res.ok) { if (!res.ok) {
const response = await res.json(); const response = await res.json();
if (res.status === 409) {
pageStateSetter((prevState) => ({
...prevState,
hasError: true,
error: {
title: `That username is already taken`,
debug: JSON.stringify(response),
},
}));
} else {
pageStateSetter((prevState) => ({ pageStateSetter((prevState) => ({
...prevState, ...prevState,
hasError: true, hasError: true,
@ -961,11 +972,12 @@ async function registrationCall(
debug: JSON.stringify(response), debug: JSON.stringify(response),
}, },
})); }));
}
} else { } else {
// registration was ok
pageStateSetter((prevState) => ({ pageStateSetter((prevState) => ({
...prevState, ...prevState,
isLoggedIn: true, isLoggedIn: true,
tryRegister: false,
})); }));
backendStateSetter((prevState) => ({ backendStateSetter((prevState) => ({
...prevState, ...prevState,
@ -973,6 +985,7 @@ async function registrationCall(
username: req.username, username: req.username,
password: req.password, password: req.password,
})); }));
route("/account");
} }
} }
@ -1072,7 +1085,10 @@ function BankFrame(Props: any): VNode {
This part of the demo shows how a bank that supports Taler This part of the demo shows how a bank that supports Taler
directly would work. In addition to using your own bank account, directly would work. In addition to using your own bank account,
you can also see the transaction history of some{" "} you can also see the transaction history of some{" "}
<a href="#" onClick={goPublicAccounts(pageStateSetter)}> <a
href="/public-accounts"
onClick={goPublicAccounts(pageStateSetter)}
>
Public Accounts Public Accounts
</a> </a>
. .
@ -1298,7 +1314,8 @@ function PaytoWireTransfer(Props: any): VNode {
transactionData, transactionData,
backendState, backendState,
pageStateSetter, pageStateSetter,
() => submitDataSetter((p) => ({ () =>
submitDataSetter((p) => ({
amount: undefined, amount: undefined,
iban: undefined, iban: undefined,
subject: undefined, subject: undefined,
@ -1537,7 +1554,7 @@ function QrCodeSection({
//Taler Wallet WebExtension is listening to headers response and tab updates. //Taler Wallet WebExtension is listening to headers response and tab updates.
//In the SPA there is no header response with the Taler URI so //In the SPA there is no header response with the Taler URI so
//this hack manually triggers the tab update after the QR is in the DOM. //this hack manually triggers the tab update after the QR is in the DOM.
window.location.href = `${window.location.href.split("#")[0]}#`; window.location.hash = `/account/${new Date().getTime()}`;
}, []); }, []);
return ( return (
@ -1786,10 +1803,7 @@ function RegistrationButton(Props: any): VNode {
<button <button
class="pure-button pure-button-secondary btn-cancel" class="pure-button pure-button-secondary btn-cancel"
onClick={() => { onClick={() => {
pageStateSetter((prevState: PageStateType) => ({ route("/register");
...prevState,
tryRegister: true,
}));
}} }}
> >
{i18n`Register`} {i18n`Register`}
@ -1816,13 +1830,11 @@ function LoginForm(Props: any): VNode {
ref.current?.focus(); ref.current?.focus();
}, []); }, []);
const errors = !submitData ? undefined : undefinedIfEmpty({ const errors = !submitData
username: !submitData.username ? undefined
? i18n`Missing username` : undefinedIfEmpty({
: undefined, username: !submitData.username ? i18n`Missing username` : undefined,
password: !submitData.password password: !submitData.password ? i18n`Missing password` : undefined,
? i18n`Missing password`
: undefined,
}); });
return ( return (
@ -1893,7 +1905,7 @@ function LoginForm(Props: any): VNode {
submitDataSetter({ submitDataSetter({
password: "", password: "",
repeatPassword: "", repeatPassword: "",
username:"", username: "",
}); });
}} }}
> >
@ -2043,10 +2055,7 @@ function RegistrationForm(Props: any): VNode {
password: "", password: "",
repeatPassword: "", repeatPassword: "",
}); });
pageStateSetter((prevState: PageStateType) => ({ route("/account");
...prevState,
tryRegister: false,
}));
}} }}
> >
{i18n`Cancel`} {i18n`Cancel`}
@ -2069,8 +2078,8 @@ function Transactions(Props: any): VNode {
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`, `access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
); );
useEffect(() => { useEffect(() => {
mutate() mutate();
}, [balanceValue]) }, [balanceValue]);
if (typeof error !== "undefined") { if (typeof error !== "undefined") {
console.log("transactions not found error", error); console.log("transactions not found error", error);
switch (error.status) { switch (error.status) {
@ -2152,12 +2161,17 @@ function Account(Props: any): VNode {
// revalidateOnReconnect: false, // revalidateOnReconnect: false,
}); });
const [pageState, setPageState] = useContext(PageContext); const [pageState, setPageState] = useContext(PageContext);
const { withdrawalInProgress, withdrawalId, isLoggedIn, talerWithdrawUri, timestamp } = const {
pageState; withdrawalInProgress,
withdrawalId,
isLoggedIn,
talerWithdrawUri,
timestamp,
} = pageState;
const i18n = useTranslator(); const i18n = useTranslator();
useEffect(() => { useEffect(() => {
mutate() mutate();
}, [timestamp]) }, [timestamp]);
/** /**
* This part shows a list of transactions: with 5 elements by * This part shows a list of transactions: with 5 elements by
@ -2294,7 +2308,11 @@ function Account(Props: any): VNode {
<section id="main"> <section id="main">
<article> <article>
<h2>{i18n`Latest transactions:`}</h2> <h2>{i18n`Latest transactions:`}</h2>
<Transactions balanceValue={balanceValue} pageNumber="0" accountLabel={accountLabel} /> <Transactions
balanceValue={balanceValue}
pageNumber="0"
accountLabel={accountLabel}
/>
</article> </article>
</section> </section>
</BankFrame> </BankFrame>
@ -2440,16 +2458,10 @@ function PublicHistories(Props: any): VNode {
); );
} }
/** function PublicHistoriesPage(): VNode {
* If the user is logged in, it displays // const [backendState, backendStateSetter] = useBackendState();
* the balance, otherwise it offers to login.
*/
export function BankHome(): VNode {
const [backendState, backendStateSetter] = useBackendState();
const [pageState, pageStateSetter] = usePageState(); const [pageState, pageStateSetter] = usePageState();
const i18n = useTranslator(); // const i18n = useTranslator();
if (pageState.showPublicHistories)
return ( return (
<SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}> <SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}>
<PageContext.Provider value={[pageState, pageStateSetter]}> <PageContext.Provider value={[pageState, pageStateSetter]}>
@ -2472,18 +2484,13 @@ export function BankHome(): VNode {
</PageContext.Provider> </PageContext.Provider>
</SWRWithoutCredentials> </SWRWithoutCredentials>
); );
}
if (pageState.tryRegister) { function RegistrationPage(): VNode {
console.log("allow registrations?", bankUiSettings.allowRegistrations); const [backendState, backendStateSetter] = useBackendState();
if (bankUiSettings.allowRegistrations) const [pageState, pageStateSetter] = usePageState();
return ( const i18n = useTranslator();
<PageContext.Provider value={[pageState, pageStateSetter]}> if (!bankUiSettings.allowRegistrations) {
<BankFrame>
<RegistrationForm backendStateSetter={backendStateSetter} />
</BankFrame>
</PageContext.Provider>
);
return ( return (
<PageContext.Provider value={[pageState, pageStateSetter]}> <PageContext.Provider value={[pageState, pageStateSetter]}>
<BankFrame> <BankFrame>
@ -2492,7 +2499,34 @@ export function BankHome(): VNode {
</PageContext.Provider> </PageContext.Provider>
); );
} }
if (pageState.isLoggedIn) { return (
<PageContext.Provider value={[pageState, pageStateSetter]}>
<BankFrame>
<RegistrationForm backendStateSetter={backendStateSetter} />
</BankFrame>
</PageContext.Provider>
);
}
function AccountPage(): VNode {
const [backendState, backendStateSetter] = useBackendState();
const [pageState, pageStateSetter] = usePageState();
const i18n = useTranslator();
if (!pageState.isLoggedIn) {
return (
<PageContext.Provider value={[pageState, pageStateSetter]}>
<BankFrame>
<h1 class="nav">{i18n`Welcome to ${bankUiSettings.bankName}!`}</h1>
<LoginForm
pageStateSetter={pageStateSetter}
backendStateSetter={backendStateSetter}
/>
</BankFrame>
</PageContext.Provider>
);
}
if (typeof backendState === "undefined") { if (typeof backendState === "undefined") {
pageStateSetter((prevState) => ({ pageStateSetter((prevState) => ({
...prevState, ...prevState,
@ -2519,17 +2553,28 @@ export function BankHome(): VNode {
</PageContext.Provider> </PageContext.Provider>
</SWRWithCredentials> </SWRWithCredentials>
); );
} // end of logged-in state. }
function Redirect({ to }: { to: string }): VNode {
useEffect(() => {
debugger;
route(to, true);
}, []);
return <div>being redirected to {to}</div>;
}
/**
* If the user is logged in, it displays
* the balance, otherwise it offers to login.
*/
export function BankHome(): VNode {
const history = createHashHistory();
return ( return (
<PageContext.Provider value={[pageState, pageStateSetter]}> <Router history={history}>
<BankFrame> <Route path="/public-accounts" component={PublicHistoriesPage} />
<h1 class="nav">{i18n`Welcome to ${bankUiSettings.bankName}!`}</h1> <Route path="/register" component={RegistrationPage} />
<LoginForm <Route path="/account/:id*" component={AccountPage} />
pageStateSetter={pageStateSetter} <Route default component={Redirect} to="/account" />
backendStateSetter={backendStateSetter} </Router>
/>
</BankFrame>
</PageContext.Provider>
); );
} }

View File

@ -105,6 +105,7 @@ importers:
esbuild-sass-plugin: ^2.4.0 esbuild-sass-plugin: ^2.4.0
eslint: ^8.26.0 eslint: ^8.26.0
eslint-config-preact: ^1.2.0 eslint-config-preact: ^1.2.0
history: 4.10.1
jed: 1.1.1 jed: 1.1.1
po2json: ^0.4.5 po2json: ^0.4.5
preact: 10.6.5 preact: 10.6.5
@ -117,6 +118,7 @@ importers:
dependencies: dependencies:
'@gnu-taler/taler-util': link:../taler-util '@gnu-taler/taler-util': link:../taler-util
date-fns: 2.29.3 date-fns: 2.29.3
history: 4.10.1
jed: 1.1.1 jed: 1.1.1
preact: 10.6.5 preact: 10.6.5
preact-router: 3.2.1_preact@10.6.5 preact-router: 3.2.1_preact@10.6.5