diff options
author | Sebastian <sebasjm@gmail.com> | 2023-05-18 12:48:01 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-05-18 12:48:01 -0300 |
commit | 20c3d4ef149268887107ddcc2b20a84db363dee6 (patch) | |
tree | 8cdb6fb1c1da2d752dff7808d643299b25f48af8 /packages | |
parent | 4bf113279530ae1fe2a0f748717e7aff320681ea (diff) | |
download | wallet-core-20c3d4ef149268887107ddcc2b20a84db363dee6.tar.xz |
add routing
Diffstat (limited to 'packages')
-rw-r--r-- | packages/exchange-backoffice-ui/package.json | 2 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/src/App.tsx | 2 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/src/Dashboard.tsx (renamed from packages/exchange-backoffice-ui/src/Dashborad.tsx) | 189 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/src/pages.ts | 25 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/src/pages/Home.tsx | 5 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/src/pages/Settings.tsx | 5 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/src/pages/ShowForm.tsx | 20 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/src/pages/Welcome.tsx | 9 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/src/route.ts | 167 | ||||
-rw-r--r-- | packages/exchange-backoffice-ui/tsconfig.json | 2 |
10 files changed, 296 insertions, 130 deletions
diff --git a/packages/exchange-backoffice-ui/package.json b/packages/exchange-backoffice-ui/package.json index 2298ebaa2..f8d6e28bf 100644 --- a/packages/exchange-backoffice-ui/package.json +++ b/packages/exchange-backoffice-ui/package.json @@ -27,8 +27,6 @@ "history": "4.10.1", "jed": "1.1.1", "preact": "10.11.3", - "preact-router": "3.2.1", - "qrcode-generator": "^1.4.4", "swr": "2.0.3" }, "eslintConfig": { diff --git a/packages/exchange-backoffice-ui/src/App.tsx b/packages/exchange-backoffice-ui/src/App.tsx index 95926e634..600131219 100644 --- a/packages/exchange-backoffice-ui/src/App.tsx +++ b/packages/exchange-backoffice-ui/src/App.tsx @@ -1,6 +1,6 @@ import { TranslationProvider } from "@gnu-taler/web-util/browser"; import { h, VNode } from "preact"; -import { Dashboard } from "./Dashborad.js"; +import { Dashboard } from "./Dashboard.js"; import "./scss/main.css"; export function App(): VNode { diff --git a/packages/exchange-backoffice-ui/src/Dashborad.tsx b/packages/exchange-backoffice-ui/src/Dashboard.tsx index 142e1de16..80f33954a 100644 --- a/packages/exchange-backoffice-ui/src/Dashborad.tsx +++ b/packages/exchange-backoffice-ui/src/Dashboard.tsx @@ -11,8 +11,8 @@ import { XMarkIcon, } from "@heroicons/react/24/outline"; import { ComponentChildren, Fragment, VNode, h } from "preact"; -import { useEffect, useReducer, useRef, useState } from "preact/hooks"; -import { NiceForm } from "./NiceForm.js"; +import { ForwardedRef, forwardRef } from "preact/compat"; +import { useRef, useState } from "preact/hooks"; import { v1 as form_902_11e_v1 } from "./forms/902_11e.js"; import { v1 as form_902_12e_v1 } from "./forms/902_12e.js"; import { v1 as form_902_13e_v1 } from "./forms/902_13e.js"; @@ -21,12 +21,8 @@ import { v1 as form_902_1e_v1 } from "./forms/902_1e.js"; import { v1 as form_902_4e_v1 } from "./forms/902_4e.js"; import { v1 as form_902_5e_v1 } from "./forms/902_5e.js"; import { v1 as form_902_9e_v1 } from "./forms/902_9e.js"; -import { FlexibleForm } from "./forms/index.js"; -import { forwardRef } from "preact/compat"; -import { ForwardedRef } from "preact/compat"; -import { createHashHistory } from "history"; - -const history = createHashHistory(); +import { Pages } from "./pages.js"; +import { Router, useCurrentLocation } from "./route.js"; /** * references between forms @@ -56,7 +52,7 @@ const history = createHashHistory(); * 902.4 */ -const allForms = [ +export const allForms = [ { name: "Identification form (902.1e)", icon: DocumentDuplicateIcon, @@ -202,9 +198,10 @@ export function Dashboard({ /> <main class="py-10 px-4 sm:px-6 lg:px-8"> <div class="mx-auto max-w-3xl"> - <Route - onUpdate={(v) => { - showFormOnSidebar(v); + <Router + pageList={pageList} + onNotFound={() => { + return <div>not found</div>; }} /> </div> @@ -216,54 +213,13 @@ export function Dashboard({ ); } -function Route({ onUpdate }: { onUpdate: (v: any) => void }) { - const [page, setPage] = useState<undefined | number>(); - function doSync(path: string) { - try { - if (path.startsWith("/form")) { - const formNumber = path.substring("/form".length); - const num = Number.parseInt(formNumber, 10); - if (!Number.isNaN(num) && num >= 0 && num < allForms.length) { - setPage(num); - } else { - setPage(undefined); - } - } else { - setPage(undefined); - } - } catch (e) { - setPage(undefined); - } - } - useEffect(() => { - doSync(history.location.pathname); - return history.listen((location, action) => { - doSync(location.pathname); - }); - }, []); - if (page !== undefined) { - return <Content onUpdate={onUpdate} selectedForm={page} />; - } - return <div>not found</div>; -} - -function useCurrentLocation() { - const [currentLocation, setCurrentLocation] = useState( - history.location.pathname, - ); - useEffect(() => { - return history.listen((location) => { - setCurrentLocation(location.pathname); - }); - }); - return currentLocation; -} +const pageList = Object.values(Pages); function NavigationBar( { isOpen, setOpen }: { isOpen: boolean; setOpen: (v: boolean) => void }, logRef: ForwardedRef<HTMLPreElement>, ) { - const currentLocation = useCurrentLocation(); + const currentLocation = useCurrentLocation(pageList); return ( <Fragment> <Transition.Root show={isOpen} as={Fragment}> @@ -331,30 +287,33 @@ function NavigationBar( <ul role="list" class="flex flex-1 flex-col gap-y-7"> <li> <ul role="list" class="-mx-2 space-y-1"> - {allForms.map((item, idx) => ( - <li key={item.name}> - <a - href={`#/form${idx}`} - class={classNames( - `/${idx}` === currentLocation - ? "bg-indigo-700 text-white" - : "text-indigo-200 hover:text-white hover:bg-indigo-700", - "group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold", - )} - > - <item.icon + {allForms.map((item, idx) => { + const url = Pages.form.url({ number: String(idx) }); + return ( + <li key={item.name}> + <a + href={url} class={classNames( - `/${idx}` === currentLocation - ? "text-white" - : "text-indigo-200 group-hover:text-white", - "h-6 w-6 shrink-0", + url === currentLocation?.path + ? "bg-indigo-700 text-white" + : "text-indigo-200 hover:text-white hover:bg-indigo-700", + "group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold", )} - aria-hidden="true" - /> - {item.name} - </a> - </li> - ))} + > + <item.icon + class={classNames( + url === currentLocation?.path + ? "text-white" + : "text-indigo-200 group-hover:text-white", + "h-6 w-6 shrink-0", + )} + aria-hidden="true" + /> + {item.name} + </a> + </li> + ); + })} </ul> </li> {/* <li> @@ -384,7 +343,7 @@ function NavigationBar( </li> */} <li class="mt-auto"> <a - href="#" + href={Pages.settings.url} class="group -mx-2 flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-indigo-200 hover:bg-indigo-700 hover:text-white" > <Cog6ToothIcon @@ -416,30 +375,33 @@ function NavigationBar( <ul role="list" class="flex flex-1 flex-col gap-y-7"> <li> <ul role="list" class="-mx-2 space-y-1"> - {allForms.map((item, idx) => ( - <li key={item.name}> - <a - href={`#/form${idx}`} - class={classNames( - `/${idx}` === currentLocation - ? "bg-indigo-700 text-white" - : "text-indigo-200 hover:text-white hover:bg-indigo-700", - "group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold", - )} - > - <item.icon + {allForms.map((item, idx) => { + const url = Pages.form.url({ number: String(idx) }); + return ( + <li key={item.name}> + <a + href={url} class={classNames( - `/${idx}` === currentLocation - ? "text-white" - : "text-indigo-200 group-hover:text-white", - "h-6 w-6 shrink-0", + url === currentLocation?.path + ? "bg-indigo-700 text-white" + : "text-indigo-200 hover:text-white hover:bg-indigo-700", + "group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold", )} - aria-hidden="true" - /> - {item.name} - </a> - </li> - ))} + > + <item.icon + class={classNames( + url === currentLocation?.path + ? "text-white" + : "text-indigo-200 group-hover:text-white", + "h-6 w-6 shrink-0", + )} + aria-hidden="true" + /> + {item.name} + </a> + </li> + ); + })} </ul> </li> {/* <li> @@ -469,7 +431,7 @@ function NavigationBar( </li> */} <li class="mt-auto"> <a - href="#" + href={Pages.settings.url} class="group -mx-2 flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-indigo-200 hover:bg-indigo-700 hover:text-white" > <Cog6ToothIcon @@ -490,31 +452,6 @@ function NavigationBar( ); } -function Content({ - onUpdate, - selectedForm, -}: { - onUpdate: (v: any) => void; - selectedForm: number; -}) { - const showingFrom = allForms[selectedForm].impl; - const storedValue = { - fullName: "loggedIn_user_fullname", - when: { - t_ms: new Date().getTime(), - }, - }; - useEffect(() => { - // initial render - onUpdate(storedValue); - }); - return ( - <Fragment> - <NiceForm initial={storedValue} form={showingFrom} onUpdate={onUpdate} /> - </Fragment> - ); -} - function TopBar({ onOpenSidebar }: { onOpenSidebar: () => void }) { return ( <div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 bg-white px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8"> diff --git a/packages/exchange-backoffice-ui/src/pages.ts b/packages/exchange-backoffice-ui/src/pages.ts new file mode 100644 index 000000000..b46cf5d51 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages.ts @@ -0,0 +1,25 @@ +import { Home } from "./pages/Home.js"; +import { Settings } from "./pages/Settings.js"; +import { ShowForm } from "./pages/ShowForm.js"; +import { Welcome } from "./pages/Welcome.js"; +import { PageEntry, pageDefinition } from "./route.js"; + +const home: PageEntry = { + url: "#/", + view: Home, +}; + +const settings: PageEntry = { + url: "#/settings", + view: Settings, +}; +const welcome: PageEntry<{ asd?: string; name?: string }> = { + url: pageDefinition("#/welcome/:name?"), + view: Welcome, +}; +const form: PageEntry<{ number?: string }> = { + url: pageDefinition("#/form/:number?"), + view: ShowForm, +}; + +export const Pages = { home, settings, welcome, form }; diff --git a/packages/exchange-backoffice-ui/src/pages/Home.tsx b/packages/exchange-backoffice-ui/src/pages/Home.tsx new file mode 100644 index 000000000..838032d63 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/Home.tsx @@ -0,0 +1,5 @@ +import { h } from "preact"; + +export function Home() { + return <div>Home</div>; +} diff --git a/packages/exchange-backoffice-ui/src/pages/Settings.tsx b/packages/exchange-backoffice-ui/src/pages/Settings.tsx new file mode 100644 index 000000000..ccff3b210 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/Settings.tsx @@ -0,0 +1,5 @@ +import { h } from "preact"; + +export function Settings() { + return <div>Settings</div>; +} diff --git a/packages/exchange-backoffice-ui/src/pages/ShowForm.tsx b/packages/exchange-backoffice-ui/src/pages/ShowForm.tsx new file mode 100644 index 000000000..5440ab7e1 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/ShowForm.tsx @@ -0,0 +1,20 @@ +import { h } from "preact"; +import { allForms } from "../Dashboard.js"; +import { NiceForm } from "../NiceForm.js"; + +export function ShowForm({ number }: { number?: string }) { + const selectedForm = Number.parseInt(number ?? "0", 10); + if (Number.isNaN(selectedForm)) { + return <div>WHAT! {number}</div>; + } + const showingFrom = allForms[selectedForm].impl; + const storedValue = { + fullName: "loggedIn_user_fullname", + when: { + t_ms: new Date().getTime(), + }, + }; + return ( + <NiceForm initial={storedValue} form={showingFrom} onUpdate={() => {}} /> + ); +} diff --git a/packages/exchange-backoffice-ui/src/pages/Welcome.tsx b/packages/exchange-backoffice-ui/src/pages/Welcome.tsx new file mode 100644 index 000000000..433fbcf59 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/Welcome.tsx @@ -0,0 +1,9 @@ +import { h } from "preact"; + +export function Welcome({ name, asd }: { asd?: string; name?: string }) { + return ( + <div> + {asd} Hello {name} + </div> + ); +} diff --git a/packages/exchange-backoffice-ui/src/route.ts b/packages/exchange-backoffice-ui/src/route.ts new file mode 100644 index 000000000..ed6d8058d --- /dev/null +++ b/packages/exchange-backoffice-ui/src/route.ts @@ -0,0 +1,167 @@ +import { createHashHistory } from "history"; +import { VNode } from "preact"; +import { useEffect, useState } from "preact/hooks"; +const history = createHashHistory(); + +type PageDefinition<DynamicPart extends Record<string, string>> = { + pattern: string; + (params: DynamicPart): string; +}; + +function replaceAll( + pattern: string, + vars: Record<string, string>, + values: Record<string, string>, +): string { + let result = pattern; + for (const v in vars) { + result = result.replace(vars[v], !values[v] ? "" : values[v]); + } + return result; +} + +export function pageDefinition<T extends Record<string, string>>( + pattern: string, +): PageDefinition<T> { + const patternParams = pattern.match(/(:[\w?]*)/g); + if (!patternParams) + throw Error( + `page definition pattern ${pattern} doesn't have any parameter`, + ); + + const vars = patternParams.reduce((prev, cur) => { + const pName = cur.match(/(\w+)/g); + + //skip things like :? in the path pattern + if (!pName || !pName[0]) return prev; + const name = pName[0]; + return { ...prev, [name]: cur }; + }, {} as Record<string, string>); + + const f = (values: T): string => replaceAll(pattern, vars, values); + f.pattern = pattern; + return f; +} + +export type PageEntry<T = unknown> = T extends Record<string, string> + ? { + url: PageDefinition<T>; + view: (props: T) => VNode; + } + : T extends unknown + ? { + url: string; + view: (props: {}) => VNode; + } + : never; + +export function Router({ + pageList, + onNotFound, +}: { + pageList: Array<PageEntry<any>>; + onNotFound: () => VNode; +}): VNode { + const current = useCurrentLocation(pageList); + if (current !== undefined) { + return current.page.view(current.values ?? {}); + } + return onNotFound(); +} + +type Location = { + page: PageEntry<any>; + path: string; + values: Record<string, string>; +}; +export function useCurrentLocation(pageList: Array<PageEntry<any>>) { + const [currentLocation, setCurrentLocation] = useState<Location>(); + /** + * Search path in the pageList + * get the values from the path found + * add params from searchParams + * + * @param path + * @param params + */ + function doSync(path: string, params: URLSearchParams) { + let result: typeof currentLocation; + for (let idx = 0; idx < pageList.length; idx++) { + const page = pageList[idx]; + if (typeof page.url === "string") { + if (page.url === path) { + const values: Record<string, string> = {}; + params.forEach((v, k) => { + values[k] = v; + }); + result = { page, values, path }; + break; + } + } else { + const values = doestUrlMatchToRoute(path, page.url.pattern); + if (values !== undefined) { + params.forEach((v, k) => { + values[k] = v; + }); + result = { page, values, path }; + break; + } + } + } + setCurrentLocation(result); + } + useEffect(() => { + doSync(window.location.hash, new URLSearchParams(window.location.search)); + return history.listen(() => { + doSync(window.location.hash, new URLSearchParams(window.location.search)); + }); + }, []); + return currentLocation; +} + +function doestUrlMatchToRoute( + url: string, + route: string, +): undefined | Record<string, string> { + const paramsPattern = /(?:\?([^#]*))?$/; + // const paramsPattern = /(?:\?([^#]*))?(#.*)?$/; + const params = url.match(paramsPattern); + const urlWithoutParams = url.replace(paramsPattern, ""); + + const result: Record<string, string> = {}; + if (params && params[1]) { + const paramList = params[1].split("&"); + for (let i = 0; i < paramList.length; i++) { + const idx = paramList[i].indexOf("="); + const name = paramList[i].substring(0, idx); + const value = paramList[i].substring(idx + 1); + result[decodeURIComponent(name)] = decodeURIComponent(value); + } + } + const urlSeg = urlWithoutParams.split("/"); + const routeSeg = route.split("/"); + let max = Math.max(urlSeg.length, routeSeg.length); + for (let i = 0; i < max; i++) { + if (routeSeg[i] && routeSeg[i].charAt(0) === ":") { + const param = routeSeg[i].replace(/(^:|[+*?]+$)/g, ""); + + const flags = (routeSeg[i].match(/[+*?]+$/) || EMPTY)[0] || ""; + const plus = ~flags.indexOf("+"); + const star = ~flags.indexOf("*"); + const val = urlSeg[i] || ""; + + if (!val && !star && (flags.indexOf("?") < 0 || plus)) { + return undefined; + } + result[param] = decodeURIComponent(val); + if (plus || star) { + result[param] = urlSeg.slice(i).map(decodeURIComponent).join("/"); + break; + } + } else if (routeSeg[i] !== urlSeg[i]) { + return undefined; + } + } + return result; +} +const EMPTY: Record<string, string> = {}; diff --git a/packages/exchange-backoffice-ui/tsconfig.json b/packages/exchange-backoffice-ui/tsconfig.json index abb9a9f36..cc5bdf396 100644 --- a/packages/exchange-backoffice-ui/tsconfig.json +++ b/packages/exchange-backoffice-ui/tsconfig.json @@ -5,7 +5,7 @@ "module": "ES6", "lib": [ "DOM", - "ES2016" + "ES2017" ], "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ |