aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/aml-backoffice-ui/src/App.tsx15
-rw-r--r--packages/aml-backoffice-ui/src/Dashboard.tsx564
-rw-r--r--packages/aml-backoffice-ui/src/assets/home.svg3
-rw-r--r--packages/aml-backoffice-ui/src/assets/people.svg3
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useCases.ts11
-rw-r--r--packages/aml-backoffice-ui/src/hooks/useSettings.ts70
-rw-r--r--packages/aml-backoffice-ui/src/pages.ts52
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.tsx12
-rw-r--r--packages/aml-backoffice-ui/src/pages/CreateAccount.tsx36
-rw-r--r--packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx3
-rw-r--r--packages/aml-backoffice-ui/src/pages/Home.tsx5
-rw-r--r--packages/aml-backoffice-ui/src/pages/Officer.tsx10
-rw-r--r--packages/aml-backoffice-ui/src/pages/Settings.tsx5
-rw-r--r--packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx19
-rw-r--r--packages/aml-backoffice-ui/src/pages/Welcome.tsx9
-rw-r--r--packages/aml-backoffice-ui/src/route.ts17
-rw-r--r--packages/aml-backoffice-ui/src/settings.ts2
-rw-r--r--packages/aml-backoffice-ui/src/utils/Loading.tsx43
-rw-r--r--packages/aml-backoffice-ui/tailwind.config.js8
19 files changed, 306 insertions, 581 deletions
diff --git a/packages/aml-backoffice-ui/src/App.tsx b/packages/aml-backoffice-ui/src/App.tsx
index 0e29279ff..52c86c273 100644
--- a/packages/aml-backoffice-ui/src/App.tsx
+++ b/packages/aml-backoffice-ui/src/App.tsx
@@ -1,9 +1,14 @@
import { TranslationProvider } from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
-import { ExchangeAmlFrame, Main } from "./Dashboard.js";
+import { ExchangeAmlFrame } from "./Dashboard.js";
import "./scss/main.css";
import { ExchangeApiProvider } from "./context/config.js";
import { getInitialBackendBaseURL } from "./hooks/useBackend.js";
+import { Router } from "./route.js";
+import { Pages } from "./pages.js";
+
+const pageList = Object.values(Pages);
+
export function App(): VNode {
const baseUrl = getInitialBackendBaseURL();
@@ -11,7 +16,13 @@ export function App(): VNode {
<TranslationProvider source={{}}>
<ExchangeApiProvider baseUrl={baseUrl} frameOnError={ExchangeAmlFrame}>
<ExchangeAmlFrame>
- <Main />
+ <Router
+ pageList={pageList}
+ onNotFound={() => {
+ window.location.href = Pages.cases.url
+ return <div>not found</div>;
+ }}
+ />
</ExchangeAmlFrame>
</ExchangeApiProvider>
</TranslationProvider>
diff --git a/packages/aml-backoffice-ui/src/Dashboard.tsx b/packages/aml-backoffice-ui/src/Dashboard.tsx
index bd8a48c45..5d86836d4 100644
--- a/packages/aml-backoffice-ui/src/Dashboard.tsx
+++ b/packages/aml-backoffice-ui/src/Dashboard.tsx
@@ -1,13 +1,17 @@
-import { useNotifications } from "@gnu-taler/web-util/browser";
+import { Footer, GlobalNotificationsBanner, Header, LangSelector, notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Dialog, Transition } from "@headlessui/react";
import { UserIcon, XCircleIcon } from "@heroicons/react/20/solid";
import { CheckCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { InformationCircleIcon } from "@heroicons/react/24/solid";
import { ComponentChildren, Fragment, VNode, h } from "preact";
-import { useState } from "preact/hooks";
+import { useEffect, useErrorBoundary, useState } from "preact/hooks";
import logo from "./assets/logo-2021.svg";
import { Pages } from "./pages.js";
-import { Router, useCurrentLocation } from "./route.js";
+import { PageEntry, Router, useCurrentLocation } from "./route.js";
+import { uiSettings } from "./settings.js";
+import { TranslatedString } from "@gnu-taler/taler-util";
+import { useOfficer } from "./hooks/useOfficer.js";
+import { getAllBooleanSettings, getLabelForSetting, useSettings } from "./hooks/useSettings.js";
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(" ");
@@ -78,6 +82,7 @@ const versionText = VERSION
* writing text with the correct format
*/
+const pageList = Object.values(Pages);
function LeftMenu() {
const currentLocation = useCurrentLocation(pageList);
@@ -88,9 +93,9 @@ function LeftMenu() {
<ul role="list" class="-mx-2 space-y-1">
<li>
<a
- href={Pages.info.url}
+ href={Pages.cases.url}
class={classNames(
- Pages.info.url === currentLocation?.path
+ Pages.cases.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",
@@ -98,7 +103,7 @@ function LeftMenu() {
>
<InformationCircleIcon
class={classNames(
- Pages.info.url === currentLocation?.path
+ Pages.cases.url === currentLocation?.path
? "text-white"
: "text-indigo-200 group-hover:text-white",
"h-6 w-6 shrink-0",
@@ -132,472 +137,143 @@ function LeftMenu() {
</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 ExchangeAmlFrame({
children,
}: {
children?: ComponentChildren;
}): VNode {
- const [sidebarOpen, setSidebarOpen] = useState(false);
+ const { i18n } = useTranslationContext();
- return (
- <Fragment>
- <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">
- <header class="flex items-center justify-between border-b border-white/5 ">
- <h1 class="text-base font-semibold leading-7 text-white">
- Exchange AML Backoffice
- </h1>
- </header>
- </div>
- <LeftMenu />
- <Footer />
- </div>
- </NavigationBar>
- <div class="lg:pl-72">
- <TopBar
- onOpenSidebar={() => {
- setSidebarOpen(true);
- }}
- />
- <Notifications />
- {children}
- </div>
- </Fragment>
- );
-}
+ const [error, resetError] = useErrorBoundary();
+
+ useEffect(() => {
+ if (error) {
+ if (error instanceof Error) {
+ notifyException(i18n.str`Internal error, please report.`, error)
+ } else {
+ notifyError(i18n.str`Internal error, please report.`, String(error) as TranslatedString)
+ }
+ resetError()
+ }
+ }, [error])
-export function Main(): VNode {
- return <main class="py-10 px-4 sm:px-6 lg:px-8">
- <div class="mx-auto max-w-3xl">
- <Router
- pageList={pageList}
- onNotFound={() => {
- return <div>not found</div>;
+ const officer = useOfficer();
+ const [settings, updateSettings] = useSettings();
+
+ return (<div class="min-h-full flex flex-col m-0 bg-slate-200" style="min-height: 100vh;">
+ <div class="bg-indigo-600 pb-32">
+ <Header
+ title="Exchange"
+ iconLinkURL={uiSettings.backendBaseURL ?? "#"}
+ onLogout={officer.state !== "ready" ? undefined : () => {
+ officer.lock()
}}
- />
+ sites={[]}
+ supportedLangs={["en", "es", "de"]}
+ >
+ <li>
+ <div class="text-xs font-semibold leading-6 text-gray-400">
+ <i18n.Translate>Preferences</i18n.Translate>
+ </div>
+ <ul role="list" class="space-y-1">
+ {getAllBooleanSettings().map(set => {
+ const isOn: boolean = !!settings[set]
+ return <li class="mt-2 pl-2">
+ <div class="flex items-center justify-between">
+ <span class="flex flex-grow flex-col">
+ <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+ {getLabelForSetting(set, i18n)}
+ </span>
+ </span>
+ <button type="button" data-enabled={isOn} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
+
+ onClick={() => { updateSettings(set, !isOn); }}>
+ <span aria-hidden="true" data-enabled={isOn} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+ </button>
+ </div>
+ </li>
+ })}
+ </ul>
+ </li>
+ </Header>
</div>
- </main>
-}
-const pageList = Object.values(Pages);
+ <GlobalNotificationsBanner />
-function NavigationBar({
- isOpen,
- setOpen,
- children,
-}: {
- isOpen: boolean;
- setOpen: (v: boolean) => void;
- children: ComponentChildren;
-}) {
- return (
- <Fragment>
- <Transition.Root show={isOpen} as={Fragment}>
- <Dialog
- as="div"
- /* @ts-ignore */
- class="relative z-50 lg:hidden"
- onClose={setOpen}
- >
- <Transition.Child
- as={Fragment}
- enter="transition-opacity ease-linear duration-300"
- enterFrom="opacity-0"
- enterTo="opacity-100"
- leave="transition-opacity ease-linear duration-300"
- leaveFrom="opacity-100"
- leaveTo="opacity-0"
- >
- <div class="fixed inset-0 bg-gray-900/80" />
- </Transition.Child>
+ <main class="-mt-32 flex grow ">
+ {officer.state !== "ready" ? undefined :
+ <Navigation />
+ }
+ <div class="flex mx-auto my-4">
+ <div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
+ {children}
+ </div>
+ </div>
- <div class="fixed inset-0 flex">
- <Transition.Child
- as={Fragment}
- enter="transition ease-in-out duration-300 transform"
- enterFrom="-translate-x-full"
- enterTo="translate-x-0"
- leave="transition ease-in-out duration-300 transform"
- leaveFrom="translate-x-0"
- leaveTo="-translate-x-full"
- >
- <Dialog.Panel class="relative mr-16 flex w-full max-w-xs flex-1">
- <Transition.Child
- as={Fragment}
- enter="ease-in-out duration-300"
- enterFrom="opacity-0"
- enterTo="opacity-100"
- leave="ease-in-out duration-300"
- leaveFrom="opacity-100"
- leaveTo="opacity-0"
- >
- <div class="absolute left-full top-0 flex w-16 justify-center pt-5">
- <button
- type="button"
- class="-m-2.5 p-2.5"
- onClick={() => setOpen(false)}
- >
- <span class="sr-only">Close sidebar</span>
- <XMarkIcon
- class="h-6 w-6 text-white"
- aria-hidden="true"
- />
- </button>
- </div>
- </Transition.Child>
- {children}
- </Dialog.Panel>
- </Transition.Child>
- </div>
- </Dialog>
- </Transition.Root>
+ </main>
- <div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
- {children}
- </div>
- </Fragment>
+ <Footer
+ testingUrl={localStorage.getItem("exchange-base-url") ?? undefined}
+ GIT_HASH={GIT_HASH}
+ VERSION={VERSION}
+ />
+ </div>
);
}
-function TopBar({ onOpenSidebar }: { onOpenSidebar: () => void }) {
+function Navigation(): VNode {
+ const { i18n } = useTranslationContext()
+ const pageList: Array<PageEntry> = [
+ Pages.officer,
+ Pages.cases
+ ]
return (
- <div class="relative flex h-16 justify-between">
- <div class="relative z-10 flex p-2 lg:hidden">
- <button
- type="button"
- onClick={() => {
- onOpenSidebar();
- }}
- class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-gray-900"
- aria-controls="mobile-menu"
- aria-expanded="false"
- >
- <span class="sr-only">Open menu</span>
- <svg
- class="block h-6 w-6"
- fill="none"
- viewBox="0 0 24 24"
- stroke-width="1.5"
- stroke="currentColor"
- aria-hidden="true"
- >
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
- />
- </svg>
- <svg
- class="hidden h-6 w-6"
- fill="none"
- viewBox="0 0 24 24"
- stroke-width="1.5"
- stroke="currentColor"
- aria-hidden="true"
- >
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M6 18L18 6M6 6l12 12"
- />
- </svg>
- </button>
- </div>
- <div class="relative z-0 flex flex-1 items-center justify-center px-2 sm:absolute sm:inset-0">
- <div class="w-full sm:max-w-xs flex flex-1 items-center justify-center">
- <img
- class="h-8 w-auto"
- src={logo}
- alt="Taler"
- style={{ height: 35, margin: 10 }}
- />
- </div>
- </div>
- {/* <div class="relative z-10 flex items-center lg:hidden">dd</div> */}
- </div>
- );
-}
+ <div class="flex gap-y-5 w-48 bg-indigo-600 divide-y rounded-r-lg divide-cyan-800 overflow-y-auto overflow-x-clip">
-// 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">
-// <button
-// type="button"
-// class="-m-2.5 p-2.5 text-gray-700 lg:hidden"
-// onClick={onOpenSidebar}
-// >
-// <span class="sr-only">Open sidebar</span>
-// <Bars3Icon class="h-6 w-6" aria-hidden="true" />
-// </button>
+ <nav class="flex flex-1 flex-col mx-4 mt-4 mb-2">
+ <ul role="list" class="flex flex-1 flex-col gap-y-7">
+ <li>
+ <ul role="list" class="-mx-2 space-y-1">
+ {pageList.map(p => {
+ return <li>
+ {/* <!-- Current: "bg-indigo-700 text-white", Default: "text-indigo-200 hover:text-white hover:bg-indigo-700" --> */}
+ <a href="#" class="bg-indigo-700 text-white group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
+ <img src={p.icon} />
+ {p.name}
+ </a>
+ </li>
-// {/* Separator */}
-// <div class="h-6 w-px bg-gray-900/10 lg:hidden" aria-hidden="true" />
+ })}
+ {/* <li>
+ <a href="#" class="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">
-// <div class="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
-// <div class="relative flex flex-1" />
-// {/* <form class="relative flex flex-1" action="#" method="GET">
-// <label htmlFor="search-field" class="sr-only">
-// Search
-// </label>
-// <MagnifyingGlassIcon
-// class="pointer-events-none absolute inset-y-0 left-0 h-full w-5 text-gray-400"
-// aria-hidden="true"
-// />
-// <input
-// id="search-field"
-// class="block h-full w-full border-0 py-0 pl-8 pr-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm"
-// placeholder="Search..."
-// type="search"
-// name="search"
-// />
-// </form> */}
-// <div class="flex items-center gap-x-4 lg:gap-x-6">
-// {/* <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> */}
+ <i18n.Translate>Officer</i18n.Translate>
+ </a>
+ </li> */}
+ </ul>
+ </li>
-// {/* Separator */}
-// <div
-// class="hidden lg:block lg:h-6 lg:w-px lg:bg-gray-900/10"
-// aria-hidden="true"
-// />
+ {/* <li class="mt-auto ">
+ <a href="#" 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">
+ <svg class="h-6 w-6 shrink-0 text-indigo-200 group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
+ </svg>
+ Settings
+ </a>
+ </li> */}
-// {/* {officerName === undefined ? (
-// <div />
-// ) : (
-// <Menu
-// as="div"
-// class="relative"
-// >
-// <Menu.Button class="-m-1.5 flex items-center p-1.5">
-// <span class="sr-only">Open user menu</span>
-// <img
-// class="h-8 w-8 rounded-full bg-gray-50"
-// src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
-// alt=""
-// />
-// <span class="hidden lg:flex lg:items-center">
-// <span
-// class="ml-4 text-sm font-semibold leading-6 text-gray-900"
-// aria-hidden="true"
-// >
-// {officerName}
-// </span>
-// <ChevronDownIcon
-// class="ml-2 h-5 w-5 text-gray-400"
-// aria-hidden="true"
-// />
-// </span>
-// </Menu.Button>
-// <Transition
-// as={Fragment}
-// enter="transition ease-out duration-100"
-// enterFrom="transform opacity-0 scale-95"
-// enterTo="transform opacity-100 scale-100"
-// leave="transition ease-in duration-75"
-// leaveFrom="transform opacity-100 scale-100"
-// leaveTo="transform opacity-0 scale-95"
-// >
-// <Menu.Items class="absolute right-0 z-10 mt-2.5 w-48 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none">
-// <Menu.Item>
-// {({ active }: { active: boolean }) => (
-// <a
-// onClick={() => {
-// officer.reset();
-// password.reset();
-// }}
-// class={classNames(
-// active ? "bg-gray-50" : "",
-// "block px-3 py-1 text-sm leading-6 text-gray-900",
-// )}
-// >
-// Forget account
-// </a>
-// )}
-// </Menu.Item>
-// </Menu.Items>
-// </Transition>
-// </Menu>
-// )} */}
-// </div>
-// </div>
-// </div>
-// );
-// }
+ </ul>
+ </nav>
+ </div>
+ )
-function Footer() {
- return (
- <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>
- );
}
-function Notifications() {
- const ns = useNotifications();
-
- // useEffect(() => {
- // if (ns.length) {
- // // remove notifications after some timeout
- // }
- // }, []);
- {
- /* <!-- Global notification live region, render this permanently at the end of the document --> */
- }
- return (
- <div
- aria-live="assertive"
- class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-50"
- >
- <div class="flex w-full flex-col items-center space-y-4 sm:items-end ">
- {/* <!--
- Notification panel, dynamically insert this into the live region when it needs to be displayed
- Entering: "transform ease-out duration-300 transition"
- From: "translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
- To: "translate-y-0 opacity-100 sm:translate-x-0"
- Leaving: "transition ease-in duration-100"
- From: "opacity-100"
- To: "opacity-0"
---> */}
- {ns.map(({ message, remove }) => {
- switch (message.type) {
- case "error": {
- return (
- <div class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 ">
- <div class="p-4 ">
- <div class="flex items-start ">
- <div class="flex-shrink-0">
- <XCircleIcon class="h-6 w-6 text-red-400" />
- </div>
- <div class="ml-3 w-0 flex-1 pt-0.5">
- <p class="text-sm font-medium text-gray-900">
- {message.title}
- </p>
- {message.description && (
- <p class="mt-1 text-sm text-gray-500">
- {message.description}
- </p>
- )}
- </div>
- <div class="ml-4 flex flex-shrink-0">
- <button
- type="button"
- onClick={remove}
- class="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
- >
- <span class="sr-only">Close</span>
- <svg
- class="h-5 w-5"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
- >
- <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
- </svg>
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
- case "info": {
- return (
- <div class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 ">
- <div class="p-4 ">
- <div class="flex items-start ">
- <div class="flex-shrink-0">
- <CheckCircleIcon class="h-6 w-6 text-green-400" />
- </div>
- <div class="ml-3 w-0 flex-1 pt-0.5">
- <p class="text-sm font-medium text-gray-900">
- {message.title}
- </p>
- </div>
- <div class="ml-4 flex flex-shrink-0">
- <button
- type="button"
- onClick={remove}
- class="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
- >
- <span class="sr-only">Close</span>
- <svg
- class="h-5 w-5"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
- >
- <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
- </svg>
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
- }
- })}
- </div>
- </div>
- );
-}
diff --git a/packages/aml-backoffice-ui/src/assets/home.svg b/packages/aml-backoffice-ui/src/assets/home.svg
new file mode 100644
index 000000000..35f340162
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/assets/home.svg
@@ -0,0 +1,3 @@
+<svg class="h-6 w-6 shrink-0 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
+</svg> \ No newline at end of file
diff --git a/packages/aml-backoffice-ui/src/assets/people.svg b/packages/aml-backoffice-ui/src/assets/people.svg
new file mode 100644
index 000000000..1dc878b81
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/assets/people.svg
@@ -0,0 +1,3 @@
+<svg class="h-6 w-6 shrink-0 text-indigo-200 group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
+</svg> \ No newline at end of file
diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts b/packages/aml-backoffice-ui/src/hooks/useCases.ts
index 2a133f46d..c4edd9207 100644
--- a/packages/aml-backoffice-ui/src/hooks/useCases.ts
+++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts
@@ -5,7 +5,7 @@ import {
} from "@gnu-taler/web-util/browser";
import { AmlExchangeBackend } from "../types.js";
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
-import { AmountString, OfficerAccount, TalerExchangeApi, TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util";
+import { AmountString, OfficerAccount, OperationFail, TalerExchangeApi, TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util";
import _useSWR, { SWRHook } from "swr";
import { useExchangeApiContext } from "../context/config.js";
import { useOfficer } from "./useOfficer.js";
@@ -70,6 +70,15 @@ export function useCases(state: AmlExchangeBackend.AmlState) {
};
// const public_accountslist = data?.type !== "ok" ? [] : data.body.public_accounts;
+ if (!session) {
+ return {
+ data: {
+ type: "fail",
+ case: "unauthorized",
+ detail: {}
+ } as OperationFail<never>
+ }
+ }
if (data) {
if (data.type === "fail") {
return { data }
diff --git a/packages/aml-backoffice-ui/src/hooks/useSettings.ts b/packages/aml-backoffice-ui/src/hooks/useSettings.ts
new file mode 100644
index 000000000..52f6f1614
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/hooks/useSettings.ts
@@ -0,0 +1,70 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import {
+ Codec,
+ TranslatedString,
+ buildCodecForObject,
+ codecForBoolean,
+ codecForNumber,
+ codecForString,
+ codecOptional
+} from "@gnu-taler/taler-util";
+import { buildStorageKey, useLocalStorage, useTranslationContext } from "@gnu-taler/web-util/browser";
+
+interface Settings {
+ allowInsecurePassword: boolean;
+}
+
+export function getAllBooleanSettings(): Array<keyof Settings> {
+ return ["allowInsecurePassword"]
+}
+
+export function getLabelForSetting(k: keyof Settings, i18n: ReturnType<typeof useTranslationContext>["i18n"]): TranslatedString {
+ switch (k) {
+ case "allowInsecurePassword": return i18n.str`Allow Insecure password`
+ }
+}
+
+export const codecForSettings = (): Codec<Settings> =>
+ buildCodecForObject<Settings>()
+ .property("allowInsecurePassword", (codecForBoolean()))
+ .build("Settings");
+
+const defaultSettings: Settings = {
+ allowInsecurePassword: false,
+};
+
+const EXCHANGE_SETTINGS_KEY = buildStorageKey(
+ "exchange-settings",
+ codecForSettings(),
+);
+
+export function useSettings(): [
+ Readonly<Settings>,
+ <T extends keyof Settings>(key: T, value: Settings[T]) => void,
+] {
+ const { value, update } = useLocalStorage(
+ EXCHANGE_SETTINGS_KEY,
+ defaultSettings,
+ );
+
+ function updateField<T extends keyof Settings>(k: T, v: Settings[T]) {
+ const newValue = { ...value, [k]: v };
+ update(newValue);
+ }
+ return [value, updateField];
+}
diff --git a/packages/aml-backoffice-ui/src/pages.ts b/packages/aml-backoffice-ui/src/pages.ts
index e4e16f05f..17ede651d 100644
--- a/packages/aml-backoffice-ui/src/pages.ts
+++ b/packages/aml-backoffice-ui/src/pages.ts
@@ -1,55 +1,51 @@
-import { Home } from "./pages/Home.js";
-import { Settings } from "./pages/Settings.js";
+import { TranslatedString } from "@gnu-taler/taler-util";
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 { Cases } from "./pages/Cases.js";
import { CaseDetails } from "./pages/CaseDetails.js";
+import { Cases } from "./pages/Cases.js";
import { NewFormEntry } from "./pages/NewFormEntry.js";
-
-const home: PageEntry = {
- url: "#/",
- view: Home,
-};
+import { Officer } from "./pages/Officer.js";
+import { PageEntry, pageDefinition } from "./route.js";
+import homeLogo from "./assets/home.svg";
+import peopleLogo from "./assets/people.svg";
const cases: PageEntry = {
url: "#/cases",
view: Cases,
+ name: "Cases" as TranslatedString,
+ icon: homeLogo,
+};
+
+const officer: PageEntry = {
+ url: "#/officer",
+ view: Officer,
+ name: "Officer" as TranslatedString,
+ icon: peopleLogo,
};
+
const account: PageEntry<{ account: string }> = {
url: pageDefinition("#/account/:account"),
view: CaseDetails,
+ name: "Account" as TranslatedString,
+ // icon: () => undefined,
};
const newFormEntry: PageEntry<{ account?: string; type?: string }> = {
url: pageDefinition("#/account/:account/new/:type?"),
view: NewFormEntry,
+ name: "New Form" as TranslatedString,
+ // icon: () => undefined,
};
-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: AntiMoneyLaunderingForm,
+ name: "Form" as TranslatedString,
+ // icon: () => undefined,
};
export const Pages = {
- home,
- info: cases,
+ cases,
officer,
- details: account,
- settings,
- welcome,
+ account,
form,
newFormEntry,
};
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx
index 5f79db71e..624f2c985 100644
--- a/packages/aml-backoffice-ui/src/pages/Cases.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -4,17 +4,16 @@ import { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { createNewForm } from "../handlers/forms.js";
import { useCases } from "../hooks/useCases.js";
-import { useOfficer } from "../hooks/useOfficer.js";
import { Pages } from "../pages.js";
import { AmlExchangeBackend } from "../types.js";
import { amlStateConverter } from "./CaseDetails.js";
+import { Officer } from "./Officer.js";
export function Cases() {
const { i18n } = useTranslationContext();
const form = createNewForm<{ state: AmlExchangeBackend.AmlState }>();
-
const initial = AmlExchangeBackend.AmlState.pending;
const [stateFilter, setStateFilter] = useState(initial);
@@ -23,16 +22,15 @@ export function Cases() {
if (!list) {
return <Loading />
}
-
if (list instanceof TalerError) {
return <ErrorLoading error={list} />
}
if (list.data.type === "fail") {
switch (list.data.case) {
- case "unauthorized":
- case "officer-not-found":
- case "officer-disabled": return <div />
+ case "unauthorized": return <Officer />
+ case "officer-not-found": return <Officer />
+ case "officer-disabled": return <Officer />
default: assertUnreachable(list.data)
}
}
@@ -116,7 +114,7 @@ export function Cases() {
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 ">
<div class="text-gray-900">
<a
- href={Pages.details.url({ account: r.h_payto })}
+ href={Pages.account.url({ account: r.h_payto })}
class="text-indigo-600 hover:text-indigo-900"
>
{r.h_payto}
diff --git a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
index 5dcb8b21d..9b8c3c046 100644
--- a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
@@ -5,6 +5,7 @@ import {
} from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
import { createNewForm } from "../handlers/forms.js";
+import { useSettings } from "../hooks/useSettings.js";
export function CreateAccount({
onNewAccount,
@@ -16,12 +17,13 @@ export function CreateAccount({
password: string;
repeat: string;
}>();
+ const [settings] = useSettings()
return (
<div class="flex min-h-full flex-col ">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
- Create account
+ <i18n.Translate>Create account</i18n.Translate>
</h2>
</div>
@@ -33,29 +35,29 @@ export function CreateAccount({
password: {
error: !v.password
? i18n.str`required`
- : v.password.length < 8
- ? i18n.str`should have at least 8 characters`
- : !v.password.match(/[a-z]/) && v.password.match(/[A-Z]/)
- ? i18n.str`should have lowercase and uppercase characters`
- : !v.password.match(/\d/)
- ? i18n.str`should have numbers`
- : !v.password.match(/[^a-zA-Z\d]/)
- ? i18n.str`should have at least one character which is not a number or letter`
- : undefined,
+ : settings.allowInsecurePassword
+ ? undefined
+ : v.password.length < 8
+ ? i18n.str`should have at least 8 characters`
+ : !v.password.match(/[a-z]/) && v.password.match(/[A-Z]/)
+ ? i18n.str`should have lowercase and uppercase characters`
+ : !v.password.match(/\d/)
+ ? i18n.str`should have numbers`
+ : !v.password.match(/[^a-zA-Z\d]/)
+ ? i18n.str`should have at least one character which is not a number or letter`
+ : undefined,
},
repeat: {
error: !v.repeat
? i18n.str`required`
: v.repeat !== v.password
- ? i18n.str`doesn't match`
- : undefined,
+ ? i18n.str`doesn't match`
+ : undefined,
},
};
}}
onSubmit={async (v, s) => {
- console.log(v, s);
const error = s?.password?.error ?? s?.repeat?.error;
- console.log(error);
if (error) {
notifyError(
"Can't create account" as TranslatedString,
@@ -72,7 +74,9 @@ export function CreateAccount({
name="password"
type="password"
help={
- "lower and upper case letters, number and special character" as TranslatedString
+ settings.allowInsecurePassword
+ ? i18n.str`short password are insecure, turn off insecure password in settings`
+ : i18n.str`lower and upper case letters, number and special character`
}
required
/>
@@ -91,7 +95,7 @@ export function CreateAccount({
type="submit"
class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
- Create
+ <i18n.Translate>Create</i18n.Translate>
</button>
</div>
</Form.Provider>
diff --git a/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx b/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx
index 05fd0a019..b3d04d97e 100644
--- a/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx
+++ b/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx
@@ -30,5 +30,8 @@ export function HandleAccountNotReady({
/>
);
}
+ return <div>
+ some
+ </div>
throw Error(`unexpected account state ${(officer as any).state}`);
}
diff --git a/packages/aml-backoffice-ui/src/pages/Home.tsx b/packages/aml-backoffice-ui/src/pages/Home.tsx
deleted file mode 100644
index 838032d63..000000000
--- a/packages/aml-backoffice-ui/src/pages/Home.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { h } from "preact";
-
-export function Home() {
- return <div>Home</div>;
-}
diff --git a/packages/aml-backoffice-ui/src/pages/Officer.tsx b/packages/aml-backoffice-ui/src/pages/Officer.tsx
index 4af34805a..abada3725 100644
--- a/packages/aml-backoffice-ui/src/pages/Officer.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Officer.tsx
@@ -1,9 +1,11 @@
import { Fragment, h } from "preact";
import { useOfficer } from "../hooks/useOfficer.js";
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
export function Officer() {
const officer = useOfficer();
+ const { i18n } = useTranslationContext()
if (officer.state !== "ready") {
return <HandleAccountNotReady officer={officer} />;
}
@@ -11,7 +13,7 @@ export function Officer() {
return (
<div>
<h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 ">
- Public key
+ <i18n.Translate>Public key</i18n.Translate>
</h1>
<div class="max-w-xl text-base leading-7 text-gray-700 lg:max-w-lg">
<p class="mt-6 font-mono break-all">{officer.account.id}</p>
@@ -25,7 +27,7 @@ export function Officer() {
rel="noreferrer"
class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
>
- Request account activation
+ <i18n.Translate>Request account activation</i18n.Translate>
</a>
</p>
<p>
@@ -36,7 +38,7 @@ export function Officer() {
}}
class="m-4 block rounded-md border-0 bg-gray-200 px-3 py-2 text-center text-sm text-black shadow-sm "
>
- Lock account
+ <i18n.Translate>Lock account</i18n.Translate>
</button>
</p>
<p>
@@ -47,7 +49,7 @@ export function Officer() {
}}
class="m-4 block rounded-md bg-red-600 px-3 py-2 text-center text-sm text-white shadow-sm hover:bg-red-500 "
>
- Remove account
+ <i18n.Translate>Forget account</i18n.Translate>
</button>
</p>
</div>
diff --git a/packages/aml-backoffice-ui/src/pages/Settings.tsx b/packages/aml-backoffice-ui/src/pages/Settings.tsx
deleted file mode 100644
index ccff3b210..000000000
--- a/packages/aml-backoffice-ui/src/pages/Settings.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { h } from "preact";
-
-export function Settings() {
- return <div>Settings</div>;
-}
diff --git a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
index 83d8767fb..a6570ffcc 100644
--- a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
+++ b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx
@@ -1,5 +1,5 @@
import { TranslatedString, UnwrapKeyError } from "@gnu-taler/taler-util";
-import { notifyError, notifyInfo } from "@gnu-taler/web-util/browser";
+import { notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
import { createNewForm } from "../handlers/forms.js";
@@ -10,6 +10,7 @@ export function UnlockAccount({
onAccountUnlocked: (password: string) => void;
onRemoveAccount: () => void;
}): VNode {
+ const { i18n } = useTranslationContext()
const Form = createNewForm<{
password: string;
}>();
@@ -17,12 +18,12 @@ export function UnlockAccount({
return (
<div class="flex min-h-full flex-col ">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
- <h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
- Account locked
- </h2>
+ <h1 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
+ <i18n.Translate>Account locked</i18n.Translate>
+ </h1>
<p class="mt-6 text-lg leading-8 text-gray-600">
- Your account is normally locked anytime you reload. To unlock type
- your password again.
+ <i18n.Translate>Your account is normally locked anytime you reload. To unlock type
+ your password again.</i18n.Translate>
</p>
</div>
@@ -30,7 +31,7 @@ export function UnlockAccount({
<div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12">
<Form.Provider
initialValue={{
- password: "welcometo.5146",
+ password: "qwe",
}}
onSubmit={async (v) => {
try {
@@ -63,7 +64,7 @@ export function UnlockAccount({
type="submit"
class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
- Unlock
+ <i18n.Translate>Unlock</i18n.Translate>
</button>
</div>
</Form.Provider>
@@ -75,7 +76,7 @@ export function UnlockAccount({
}}
class="m-4 block rounded-md bg-red-600 px-3 py-2 text-center text-sm text-white shadow-sm hover:bg-red-500 "
>
- Remove account
+ <i18n.Translate>Forget account</i18n.Translate>
</button>
</div>
</div>
diff --git a/packages/aml-backoffice-ui/src/pages/Welcome.tsx b/packages/aml-backoffice-ui/src/pages/Welcome.tsx
deleted file mode 100644
index 433fbcf59..000000000
--- a/packages/aml-backoffice-ui/src/pages/Welcome.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { h } from "preact";
-
-export function Welcome({ name, asd }: { asd?: string; name?: string }) {
- return (
- <div>
- {asd} Hello {name}
- </div>
- );
-}
diff --git a/packages/aml-backoffice-ui/src/route.ts b/packages/aml-backoffice-ui/src/route.ts
index d54f9be83..4c3331668 100644
--- a/packages/aml-backoffice-ui/src/route.ts
+++ b/packages/aml-backoffice-ui/src/route.ts
@@ -1,3 +1,4 @@
+import { TranslatedString } from "@gnu-taler/taler-util";
import { createHashHistory } from "history";
import { h as create, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
@@ -45,14 +46,18 @@ export function pageDefinition<T extends Record<string, string>>(
export type PageEntry<T = unknown> = T extends Record<string, string>
? {
- url: PageDefinition<T>;
- view: (props: T) => VNode;
- }
+ url: PageDefinition<T>;
+ view: (props: T) => VNode;
+ name: TranslatedString,
+ icon?: string,
+ }
: T extends unknown
? {
- url: string;
- view: (props: {}) => VNode;
- }
+ url: string;
+ view: (props: {}) => VNode;
+ name: TranslatedString,
+ icon?: string,
+ }
: never;
export function Router({
diff --git a/packages/aml-backoffice-ui/src/settings.ts b/packages/aml-backoffice-ui/src/settings.ts
index 2897874a2..9c65837c3 100644
--- a/packages/aml-backoffice-ui/src/settings.ts
+++ b/packages/aml-backoffice-ui/src/settings.ts
@@ -24,7 +24,7 @@ export interface UiSettings {
* Global settings for the UI.
*/
const defaultSettings: UiSettings = {
- backendBaseURL: "https://exchange.demo.taler.net/",
+ backendBaseURL: "http://exchange.taler.test:1180/",
allowRegistrations: true,
uiName: "Taler Bank",
};
diff --git a/packages/aml-backoffice-ui/src/utils/Loading.tsx b/packages/aml-backoffice-ui/src/utils/Loading.tsx
deleted file mode 100644
index 7cbdad681..000000000
--- a/packages/aml-backoffice-ui/src/utils/Loading.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { h, VNode } from "preact";
-
-export function Loading(): VNode {
- return (
- <div
- class="columns is-centered is-vcentered"
- style={{
- height: "calc(100% - 3rem)",
- position: "absolute",
- width: "100%",
- }}
- >
- <Spinner />
- </div>
- );
-}
-
-export function Spinner(): VNode {
- return (
- <div class="lds-ring">
- <div />
- <div />
- <div />
- <div />
- </div>
- );
-}
diff --git a/packages/aml-backoffice-ui/tailwind.config.js b/packages/aml-backoffice-ui/tailwind.config.js
index 01f058b2e..ec51dfbb8 100644
--- a/packages/aml-backoffice-ui/tailwind.config.js
+++ b/packages/aml-backoffice-ui/tailwind.config.js
@@ -1,6 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
- content: ["./src/**/*.{html,tsx}"],
+ content: {
+ relative: true,
+ files: [
+ "./src/**/*.{html,tsx}",
+ "./node_modules/@gnu-taler/web-util/src/**/*.{html,tsx}"
+ ],
+ },
theme: {
extend: {},
},