diff options
6 files changed, 349 insertions, 267 deletions
diff --git a/packages/exchange-backoffice-ui/src/Dashboard.tsx b/packages/exchange-backoffice-ui/src/Dashboard.tsx index 80f33954a..9a0ba41d5 100644 --- a/packages/exchange-backoffice-ui/src/Dashboard.tsx +++ b/packages/exchange-backoffice-ui/src/Dashboard.tsx @@ -2,27 +2,20 @@ import { Dialog, Menu, Transition } from "@headlessui/react"; import { ChevronDownIcon, MagnifyingGlassIcon, + UserIcon, } from "@heroicons/react/20/solid"; import { Bars3Icon, BellIcon, Cog6ToothIcon, - DocumentDuplicateIcon, XMarkIcon, } from "@heroicons/react/24/outline"; import { ComponentChildren, Fragment, VNode, h } from "preact"; 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"; -import { v1 as form_902_15e_v1 } from "./forms/902_15e.js"; -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 { Pages } from "./pages.js"; import { Router, useCurrentLocation } from "./route.js"; +import { InformationCircleIcon } from "@heroicons/react/24/solid"; /** * references between forms @@ -52,53 +45,6 @@ import { Router, useCurrentLocation } from "./route.js"; * 902.4 */ -export const allForms = [ - { - name: "Identification form (902.1e)", - icon: DocumentDuplicateIcon, - impl: form_902_1e_v1, - }, - { - name: "Operational legal entity or partnership (902.11e)", - icon: DocumentDuplicateIcon, - impl: form_902_11e_v1, - }, - { - name: "Foundations (902.12e)", - icon: DocumentDuplicateIcon, - impl: form_902_12e_v1, - }, - { - name: "Declaration for trusts (902.13e)", - icon: DocumentDuplicateIcon, - impl: form_902_13e_v1, - }, - { - name: "Information on life insurance policies (902.15e)", - icon: DocumentDuplicateIcon, - impl: form_902_15e_v1, - }, - { - name: "Declaration of beneficial owner (902.9e)", - icon: DocumentDuplicateIcon, - impl: form_902_9e_v1, - }, - { - name: "Customer profile (902.5e)", - icon: DocumentDuplicateIcon, - impl: form_902_5e_v1, - }, - { - name: "Risk profile (902.4e)", - icon: DocumentDuplicateIcon, - impl: form_902_4e_v1, - }, -]; -const teams = [ - { id: 1, name: "Heroicons", href: "#", initial: "H", current: false }, - { id: 2, name: "Tailwind Labs", href: "#", initial: "T", current: false }, - { id: 3, name: "Workcation", href: "#", initial: "W", current: false }, -]; const userNavigation = [ { name: "Your profile", href: "#" }, { name: "Sign out", href: "#" }, @@ -135,7 +81,7 @@ const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; const versionText = VERSION ? GIT_HASH - ? `Version ${VERSION} (${GIT_HASH.substring(0, 8)})` + ? `v${VERSION} (${GIT_HASH.substring(0, 8)})` : VERSION : ""; @@ -173,6 +119,110 @@ const versionText = VERSION * writing text with the correct format */ +function LeftMenu() { + const currentLocation = useCurrentLocation(pageList); + + return ( + <nav class="flex flex-1 flex-col"> + <ul role="list" class="flex flex-1 flex-col gap-y-7"> + <li> + <ul role="list" class="-mx-2 space-y-1"> + <li> + <a + href={Pages.info.url} + class={classNames( + Pages.info.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", + )} + > + <InformationCircleIcon + class={classNames( + Pages.info.url === currentLocation?.path + ? "text-white" + : "text-indigo-200 group-hover:text-white", + "h-6 w-6 shrink-0", + )} + aria-hidden="true" + /> + Info + </a> + </li> + <li> + <a + href={Pages.officer.url} + class={classNames( + Pages.officer.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", + )} + > + <UserIcon + class={classNames( + Pages.officer.url === currentLocation?.path + ? "text-white" + : "text-indigo-200 group-hover:text-white", + "h-6 w-6 shrink-0", + )} + aria-hidden="true" + /> + Officer + </a> + </li> + </ul> + </li> + {/* <li> + <div class="text-xs font-semibold leading-6 text-indigo-200"> + Info + </div> + <ul role="list" class="-mx-2 mt-2 space-y-1"> + <li> + <a + href={Pages.info.url} + class={classNames( + Pages.info.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", + )} + > + <span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border border-indigo-400 bg-indigo-500 text-[0.625rem] font-medium text-white"> + asd + </span> + <span class="truncate">qwe</span> + </a> + </li> + </ul> + </li> */} + <li class="mt-auto"> + <a + href={Pages.settings.url} + class={classNames( + Pages.settings.url === currentLocation?.path + ? "bg-indigo-700 text-white" + : "text-indigo-200 hover:text-white hover:bg-indigo-700", + "group -mx-2 flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold", + )} + > + <Cog6ToothIcon + class={classNames( + Pages.officer.url === currentLocation?.path + ? "text-white" + : "text-indigo-200 group-hover:text-white", + "h-6 w-6 shrink-0", + )} + aria-hidden="true" + /> + Settings + </a> + </li> + </ul> + </nav> + ); +} + export function Dashboard({ children, }: { @@ -185,11 +235,24 @@ export function Dashboard({ if (!logRef.current) return; logRef.current.innerHTML = JSON.stringify(v, undefined, 1); } - - const Nav = forwardRef(NavigationBar); return ( <Fragment> - <Nav ref={logRef} isOpen={sidebarOpen} setOpen={setSidebarOpen} /> + <NavigationBar isOpen={sidebarOpen} setOpen={setSidebarOpen}> + <div class="flex grow flex-col gap-y-5 overflow-y-auto bg-indigo-600 px-6 pb-4"> + <div class="flex h-16 shrink-0 items-center"> + <img + class="h-8 w-auto" + src="https://tailwindui.com/img/logos/mark.svg?color=white" + alt="Taler" + /> + </div> + <LeftMenu /> + <div class="text-white text-sm"> + <pre ref={logRef}></pre> + </div> + <Footer /> + </div> + </NavigationBar> <div class="lg:pl-72"> <TopBar onOpenSidebar={() => { @@ -206,8 +269,6 @@ export function Dashboard({ /> </div> </main> - - <Footer /> </div> </Fragment> ); @@ -215,11 +276,15 @@ export function Dashboard({ const pageList = Object.values(Pages); -function NavigationBar( - { isOpen, setOpen }: { isOpen: boolean; setOpen: (v: boolean) => void }, - logRef: ForwardedRef<HTMLPreElement>, -) { - const currentLocation = useCurrentLocation(pageList); +function NavigationBar({ + isOpen, + setOpen, + children, +}: { + isOpen: boolean; + setOpen: (v: boolean) => void; + children: ComponentChildren; +}) { return ( <Fragment> <Transition.Root show={isOpen} as={Fragment}> @@ -275,87 +340,7 @@ function NavigationBar( </button> </div> </Transition.Child> - <div class="flex grow flex-col gap-y-5 overflow-y-auto bg-indigo-600 px-6 pb-4"> - <div class="flex h-16 shrink-0 items-center"> - <img - class="h-8 w-auto" - src="https://tailwindui.com/img/logos/mark.svg?color=white" - alt="Your Company" - /> - </div> - <nav class="flex flex-1 flex-col"> - <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) => { - const url = Pages.form.url({ number: String(idx) }); - return ( - <li key={item.name}> - <a - href={url} - class={classNames( - 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", - )} - > - <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> - <div class="text-xs font-semibold leading-6 text-indigo-200"> - Your teams - </div> - <ul role="list" class="-mx-2 mt-2 space-y-1"> - {teams.map((team) => ( - <li key={team.name}> - <a - href={team.href} - class={classNames( - team.current - ? "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", - )} - > - <span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border border-indigo-400 bg-indigo-500 text-[0.625rem] font-medium text-white"> - {team.initial} - </span> - <span class="truncate">{team.name}</span> - </a> - </li> - ))} - </ul> - </li> */} - <li class="mt-auto"> - <a - 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 - class="h-6 w-6 shrink-0 text-indigo-200 group-hover:text-white" - aria-hidden="true" - /> - Settings - </a> - </li> - </ul> - </nav> - </div> + {children} </Dialog.Panel> </Transition.Child> </div> @@ -363,90 +348,7 @@ function NavigationBar( </Transition.Root> <div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col"> - <div class="flex grow flex-col gap-y-5 overflow-y-auto bg-indigo-600 px-6 pb-4"> - <div class="flex h-16 shrink-0 items-center"> - <img - class="h-8 w-auto" - src="https://tailwindui.com/img/logos/mark.svg?color=white" - alt="Your Company" - /> - </div> - <nav class="flex flex-1 flex-col"> - <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) => { - const url = Pages.form.url({ number: String(idx) }); - return ( - <li key={item.name}> - <a - href={url} - class={classNames( - 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", - )} - > - <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> - <div class="text-xs font-semibold leading-6 text-indigo-200"> - Your teams - </div> - <ul role="list" class="-mx-2 mt-2 space-y-1"> - {teams.map((team) => ( - <li key={team.name}> - <a - href={team.href} - class={classNames( - team.current - ? "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", - )} - > - <span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border border-indigo-400 bg-indigo-500 text-[0.625rem] font-medium text-white"> - {team.initial} - </span> - <span class="truncate">{team.name}</span> - </a> - </li> - ))} - </ul> - </li> */} - <li class="mt-auto"> - <a - 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 - class="h-6 w-6 shrink-0 text-indigo-200 group-hover:text-white" - aria-hidden="true" - /> - Settings - </a> - </li> - </ul> - </nav> - <div class="text-white text-sm"> - <pre ref={logRef}></pre> - </div> - </div> + {children} </div> </Fragment> ); @@ -468,7 +370,8 @@ function TopBar({ onOpenSidebar }: { onOpenSidebar: () => void }) { <div class="h-6 w-px bg-gray-900/10 lg:hidden" aria-hidden="true" /> <div class="flex flex-1 gap-x-4 self-stretch lg:gap-x-6"> - <form class="relative flex flex-1" action="#" method="GET"> + <div class="relative flex flex-1" /> + {/* <form class="relative flex flex-1" action="#" method="GET"> <label htmlFor="search-field" class="sr-only"> Search </label> @@ -483,15 +386,15 @@ function TopBar({ onOpenSidebar }: { onOpenSidebar: () => void }) { type="search" name="search" /> - </form> + </form> */} <div class="flex items-center gap-x-4 lg:gap-x-6"> - <button + {/* <button type="button" class="-m-2.5 p-2.5 text-gray-400 hover:text-gray-500" > <span class="sr-only">View notifications</span> <BellIcon class="h-6 w-6" aria-hidden="true" /> - </button> + </button> */} {/* Separator */} <div @@ -561,14 +464,11 @@ function TopBar({ onOpenSidebar }: { onOpenSidebar: () => void }) { function Footer() { return ( - <footer class="bg-white"> - <div class="mx-auto px-4 py-2 md:flex md:items-center md:justify-between lg:px-8"> - <div class="mt-8 md:order-1 md:mt-0"> - <p class="text-center text-xs leading-5 text-gray-500"> - Copyright © 2014—2023 Taler Systems SA. - {versionText} - </p> - </div> + <footer class="absolute bottom-4"> + <div class="mt-8 md:order-1 md:mt-0"> + <p class="text-xs leading-5 text-gray-300"> + Taler Systems SA. {versionText} + </p> </div> </footer> ); diff --git a/packages/exchange-backoffice-ui/src/pages.ts b/packages/exchange-backoffice-ui/src/pages.ts index b46cf5d51..a78a137a0 100644 --- a/packages/exchange-backoffice-ui/src/pages.ts +++ b/packages/exchange-backoffice-ui/src/pages.ts @@ -1,25 +1,35 @@ import { Home } from "./pages/Home.js"; import { Settings } from "./pages/Settings.js"; -import { ShowForm } from "./pages/ShowForm.js"; +import { AntiMoneyLaunderingForm } from "./pages/AntiMoneyLaunderingForm.js"; import { Welcome } from "./pages/Welcome.js"; import { PageEntry, pageDefinition } from "./route.js"; +import { Officer } from "./pages/Officer.js"; +import { Info } from "./pages/Info.js"; const home: PageEntry = { url: "#/", view: Home, }; +const info: PageEntry = { + url: "#/info", + view: Info, +}; const settings: PageEntry = { url: "#/settings", view: Settings, }; +const officer: PageEntry = { + url: "#/officer", + view: Officer, +}; const welcome: PageEntry<{ asd?: string; name?: string }> = { url: pageDefinition("#/welcome/:name?"), view: Welcome, }; const form: PageEntry<{ number?: string }> = { url: pageDefinition("#/form/:number?"), - view: ShowForm, + view: AntiMoneyLaunderingForm, }; -export const Pages = { home, settings, welcome, form }; +export const Pages = { home, info, officer, settings, welcome, form }; diff --git a/packages/exchange-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx b/packages/exchange-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx new file mode 100644 index 000000000..ee9d1ce30 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx @@ -0,0 +1,71 @@ +import { h } from "preact"; +import { NiceForm } from "../NiceForm.js"; +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"; +import { v1 as form_902_15e_v1 } from "../forms/902_15e.js"; +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 { DocumentDuplicateIcon } from "@heroicons/react/24/solid"; + +export function AntiMoneyLaunderingForm({ 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={() => {}} /> + ); +} + +export const allForms = [ + { + name: "Identification form (902.1e)", + icon: DocumentDuplicateIcon, + impl: form_902_1e_v1, + }, + { + name: "Operational legal entity or partnership (902.11e)", + icon: DocumentDuplicateIcon, + impl: form_902_11e_v1, + }, + { + name: "Foundations (902.12e)", + icon: DocumentDuplicateIcon, + impl: form_902_12e_v1, + }, + { + name: "Declaration for trusts (902.13e)", + icon: DocumentDuplicateIcon, + impl: form_902_13e_v1, + }, + { + name: "Information on life insurance policies (902.15e)", + icon: DocumentDuplicateIcon, + impl: form_902_15e_v1, + }, + { + name: "Declaration of beneficial owner (902.9e)", + icon: DocumentDuplicateIcon, + impl: form_902_9e_v1, + }, + { + name: "Customer profile (902.5e)", + icon: DocumentDuplicateIcon, + impl: form_902_5e_v1, + }, + { + name: "Risk profile (902.4e)", + icon: DocumentDuplicateIcon, + impl: form_902_4e_v1, + }, +]; diff --git a/packages/exchange-backoffice-ui/src/pages/Info.tsx b/packages/exchange-backoffice-ui/src/pages/Info.tsx new file mode 100644 index 000000000..661ab02a7 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/Info.tsx @@ -0,0 +1,5 @@ +import { h } from "preact"; + +export function Info() { + return <div>Show key and wire info</div>; +} diff --git a/packages/exchange-backoffice-ui/src/pages/Officer.tsx b/packages/exchange-backoffice-ui/src/pages/Officer.tsx new file mode 100644 index 000000000..c72ca0720 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/Officer.tsx @@ -0,0 +1,116 @@ +import { useLocalStorage } from "@gnu-taler/web-util/browser"; +import { h } from "preact"; +import { useEffect, useState } from "preact/hooks"; + +const oldKey = + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDPQVq8F0Ce6kTXKQ5Ea2fZRoap6poFYs0FOln8o8+ehGI8rDdMBzNU3pLIlOMKs/vKvhDNMG4m4xxb92wDbvefDxkxaEkbRSZnRiJd4MIbh8Lx8zvFbLp03rkXu9KPN8IprKOXxgN7xbxm0KKcu03rtqLiOvC1gMqja2LMIPCi32nyNneduszHZ57d+CqIKZdVnaqAcXOSMAQsVoEq2joBOeIaSAnIJHg+T8HQ+VcLV8Y722jhX/bH84IyEMup9e7mhgVFnHgINc77c6TONH8H+dHlXCQ+hMPGw9wM+wgpJgIDzrhIN+QSjn283EOXD6z6dpiWBdEYfJRLHwEWk8wNAgMBAAECggEAB/anZrMasQsoXP9qBG1Uvq+r4fXZODFtK5vBNGi+RAWAhCX2iU3SMPB3wbby0wj1DlESR91qBhrTjqG+/TzIzUxLuARyoVZysiTVkjeIzdJVcRgwU5bTbUUs5da6MaA/WNGWMZvoALFUMBEpMQ4uDCC8OSbG8/prDtoZSuWjHrxBhsqSyIoJ3Q0iPQxPT0ShC9d5T56QuhsRQeRIWhQVtFlytXl1lqEbqljhIEOzkvS5QOcXcS3OBo/Nvdit+vi9kkLuiP8z2p6WAiVZCgCXfffNH3EEbQG/BEpIOynkchiDy1L31mFRFk1oYJRs9xD8+oF/N75GhlmYO7IbxeHw0wKBgQDnYZWjGlRM2oHpeiPSII5m9rC7qohO0ImxqifYZPp47vdRMbBWrdbxX68SqdzGfSzXcDPLfBAObG4QR8Xol1LMNJUT9og9pERZHgob+yWkTd68lLSdxfCJEKRJaDmD8dHgSrBYe86ADUeAj+fC4dycYXH//fwed1gt/G8iXtdU9wKBgQDlTp9752+tEh9fMlUdINbZXmGbjHBrZMTnAYJI509iJLIvJvYroU5TvRMsp+rACDc2Zy2nbsaCM5Xzd5wUxRBvF+PiBCFoi7c/EBaLCtb9+vyXtHAIHtzHeYUP/1cq7MOdTwrWvZqzIoW6xm7L9HRX/5i+n+rVUSxnzYIxgTlaGwKBgQC0INgpXbn7CrDQXnG8h/PUXIBB2QS8tsQ7N8hFQndr5j1LTG+HS1ZmGqNk2DAzpgdewM7RvweQ8wDMU9PSutuOdfEI1YhC1LsQ1b3xApfPTX/1N59UpGAZlIcRTr5X5c4J2ptmhxu/vJbJkz5ODR997q6dJ9E6tpZDVp3+F+9zCQKBgQCrp+OzuVjcUoixltgoagDrz7951fQCMPlFhNenA6FlctsAeUYm+yXLgersrvcIsh3C2BJRGJf5t+w0ygFJewwGXff1pensfUq8Jqr5gy/WCSE135lOOuxDVzDI/Pif5YW6KQWQI3e/ScSaQRmIDINbrLcHXGdLMOzw9+LSdE4eqQKBgQDe86MfzwMLPoDH07WC09dCcoIUSYMThYrFwUK3qgEiYaJMZJvdAIwr12szVwVRYIX4wHBObFsQZLTaY5+O/REnze6Q1AQa2H6eH1TalC1r6jBS5/LhIrVWl/0VSdsUIe41tc8xPDWrm9hmLeJLZk+xb5/hAm3REsDM1Iif9O7zzg=="; +export function Officer() { + const storage = useLocalStorage("officer"); + const [keys, setKeys] = useState({ priv: "", pub: "" }); + useEffect(() => { + loadPreviousSession(oldKey).then((keys) => + setKeys(keys ?? { priv: "", pub: "" }), + ); + // generateNewId().then((keys) => setKeys(keys)); + }, []); + + console.log(keys.pub); + console.log(keys.priv); + return ( + <div> + <div>Officer</div> + <h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 "> + Public key + </h1> + <div> + -----BEGIN PUBLIC KEY----- + <p class="mt-6 leading-8 text-gray-700 break-all">{keys.pub}</p> + -----END PUBLIC KEY----- + </div> + <h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 "> + Private key + </h1> + <div> + -----BEGIN PRIVATE KEY----- + <p class="mt-6 leading-8 text-gray-700 break-all">{keys.priv}</p> + -----END PRIVATE KEY----- + </div> + </div> + ); +} + +const rsaAlgorithm: RsaHashedKeyGenParams = { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-256", +}; + +async function generateNewId() { + const key = await crypto.subtle.generateKey(rsaAlgorithm, true, [ + "encrypt", + "decrypt", + ]); + + if (key instanceof CryptoKey) { + throw Error("unexpected key without pair"); + } + const { privateKey, publicKey } = key; + const privRaw = await crypto.subtle.exportKey("pkcs8", privateKey); + + const pubRaw = await crypto.subtle.exportKey("spki", publicKey); + + const priv = btoa(ab2str(privRaw)); + + const pub = btoa(ab2str(pubRaw)); + return { priv, pub }; +} + +async function loadPreviousSession(priv: string) { + const key = str2ab(window.atob(priv)); + const privateKey = await window.crypto.subtle + .importKey("pkcs8", key, rsaAlgorithm, true, ["decrypt"]) + .catch(throwErrorWithStack); + + if (!privateKey) return undefined; + + // export private key to JWK + const jwk = await crypto.subtle + .exportKey("jwk", privateKey) + .catch(throwErrorWithStack); + + // remove private data from JWK + delete jwk.d; + delete jwk.dp; + delete jwk.dq; + delete jwk.q; + delete jwk.qi; + jwk.key_ops = ["encrypt"]; + + const publicKey = await crypto.subtle + .importKey("jwk", jwk, rsaAlgorithm, true, ["encrypt"]) + .catch(throwErrorWithStack); + + const pubRaw = await crypto.subtle + .exportKey("spki", publicKey) + .catch(throwErrorWithStack); + + const pub = btoa(ab2str(pubRaw)); + + return { priv, pub }; +} + +function ab2str(buf: ArrayBuffer) { + return String.fromCharCode.apply(null, Array.from(new Uint8Array(buf))); +} +function str2ab(str: string) { + const buf = new ArrayBuffer(str.length); + const bufView = new Uint8Array(buf); + for (let i = 0, strLen = str.length; i < strLen; i++) { + bufView[i] = str.charCodeAt(i); + } + return buf; +} +function throwErrorWithStack(e: Error): never { + throw new Error(e.message); +} diff --git a/packages/exchange-backoffice-ui/src/pages/ShowForm.tsx b/packages/exchange-backoffice-ui/src/pages/ShowForm.tsx deleted file mode 100644 index 5440ab7e1..000000000 --- a/packages/exchange-backoffice-ui/src/pages/ShowForm.tsx +++ /dev/null @@ -1,20 +0,0 @@ -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={() => {}} /> - ); -} |