gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant-backoffice] branch master updated (eecd261 -> dd2bfd6)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (eecd261 -> dd2bfd6)
Date: Fri, 19 Feb 2021 21:12:24 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository merchant-backoffice.

    from eecd261  downgrade requirement
     new 6aba8b5  better readme, updated makefile with more commands, add 
config context, switch to nodejs 12
     new dd2bfd6  updated changelog

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGELOG.md                                       | 28 ++++++++++++-
 README.md                                          | 47 ++++++++++++++--------
 build-system/Makefile                              | 28 ++++++++++---
 package.json                                       |  2 +
 packages/frontend/.storybook/preview.js            | 11 ++++-
 packages/frontend/src/components/auth/index.tsx    |  2 +-
 packages/frontend/src/components/yup/YupField.tsx  | 47 ++++++++++++++--------
 packages/frontend/src/context/backend.ts           | 11 +++++
 .../frontend/src/{components => }/hooks/backend.ts |  2 +-
 .../frontend/src/{components => }/hooks/index.ts   |  0
 packages/frontend/src/index.tsx                    | 44 +++++++++++---------
 .../src/routes/instances/Create.stories.tsx        | 20 ++++-----
 .../frontend/src/routes/instances/CreatePage.tsx   |  7 +++-
 .../frontend/src/routes/instances/UpdatePage.tsx   |  8 +++-
 packages/frontend/src/routes/instances/index.tsx   |  2 +-
 15 files changed, 183 insertions(+), 76 deletions(-)
 create mode 100644 packages/frontend/src/context/backend.ts
 rename packages/frontend/src/{components => }/hooks/backend.ts (98%)
 rename packages/frontend/src/{components => }/hooks/index.ts (100%)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68f8acd..e093a1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,31 @@ All notable changes to this project will be documented in this 
file.
 The format is based on [Keep a 
Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [Unreleased]
+## [Future work]
+ - implement correct weblate feature
+ - add order section
+ - add product section
+ - add tips section
+ - show the connection state (url, currency, version) in the sidebar
+ - what happend if cannot access the config
+ - date format (error handling)
+ - bug: set text int the intpu date (seconds)
+ - feature: input as date format
+ - connection errors
+ - add backend url without slash
+ - edit button should be a pen
+ - all button to the right
+ - check the url from the backend when login
+ - reorder the fields from the address/juriction section (take example)
+ - allow point separator for amounts
+ - prevent letters to be input in numbers
+ - red color when input is invalid (onchange)
+ - auth token config as popup with 3 actions (clear (sure?), cancel, set token)
+ - remove checkbox from auth token, use button (manage auth)
+ - prepend payto:// to account
+ - validate on change everything
+ - remove footer
+
  - implement proper error handling
  - PATCH payto uri not working as expeced: re-enable, creating with multiple 
uris
  - replace Yup and type definition with a taler-library for the purpose (first 
wait Florian to refactor wallet core)
@@ -15,6 +39,8 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - configure eslint
  - configure prettier
  - prune scss styles to reduce size
+
+## [Unreleased]
 ## [0.0.1] - 2021-02-18
 ### Changed
  - button of the form to the right
diff --git a/README.md b/README.md
index eb23b20..4fb7a7e 100644
--- a/README.md
+++ b/README.md
@@ -4,38 +4,53 @@ Merchant Admin Frontend is a Single Page Application that 
connect with a running
 
 ## System requirements
 
-- Node version: 14.15.0
-- Yarn version: 1.21.1
+- Node: v12.18.4
+- pnpm: 5.17.2
+- make
+- python>=3.8
 
-## Environment
+## Compiling from source
 
-This applications use some configuration from the environment, like the 
location of the Merchant Backend.
-Be sure to copy the file `template.env` into `.env` and override with your 
custom values.
-## Installation
+Check the requirements and run `./bootstrap` and `./configure`
 
-First run the command `yarn install` to get all the dependencies.
-The running `yarn dev` should set the server up and running.
-Use the browser to navigate into `http://localhost:8080`
+```shell
+./bootstrap 
+./configure
+```
+
+Then run `make` to install all the nodejs dependencies
+
+## Running develop
+
+To run a development server run 
+
+```shell
+make dev
+```
+
+This should start a watch process that will reload the server every time that 
a file is saved.
+
+## Building for deploy
 
 ## CLI Commands
 
-*   `yarn install`: Installs dependencies
+*   `make compile`: Installs dependencies and compile with typescript
 
-*   `yarn dev`: Run a development, HMR server. The application will 
automatically refresh 
+*   `make dev`: Run a development, HMR server. The application will 
automatically refresh 
      every time a file is edited
 
-*   `yarn build`: Production-ready build into the `./build` directory. Print 
bundle size 
+*   `make build`: Production-ready build into the `./build` directory. Print 
bundle size 
      information and compare its with previous build using the 
`size-plugin.json` file.
 
-*   `yarn serve`: Serves the content into `./build` directory, usefull to test 
the result.
+*   `make serve`: build and serves the content, usefull to test the result.
 
-*   `yarn lint`: Pass TypeScript files using ESLint
+*   `make lint`: Pass TypeScript files using ESLint
 
-*   `yarn test`: Run Jest and Enzyme with
+*   `make check`: Run Jest and Enzyme with
     
[`enzyme-adapter-preact-pure`](https://github.com/preactjs/enzyme-adapter-preact-pure)
 for
     your tests
 
-*   `yarn storybook`: Run visual components explorer. Usefull for components 
design and development
+*   `make dev-views`: Run visual components explorer. Usefull for components 
design and development
     without the need of setting up the whole system.
 
 
diff --git a/build-system/Makefile b/build-system/Makefile
index 36279d5..c819c48 100644
--- a/build-system/Makefile
+++ b/build-system/Makefile
@@ -15,13 +15,13 @@ compile:
        pnpm i -r
        pnpm run compile
 
-.PHONY: dist
-dist:
-       $(git-archive-all) --include ./configure taler-wallet-$(shell git 
describe --tags).tar.gz
+# .PHONY: dist
+# dist:
+#      $(git-archive-all) --include ./configure taler-wallet-$(shell git 
describe --tags).tar.gz
 
-.PHONY: publish
-publish: compile
-       pnpm publish -r --no-git-checks
+# .PHONY: publish
+# publish: compile
+#      pnpm publish -r --no-git-checks
 
 # make documentation from docstrings
 .PHONY: typedoc
@@ -32,6 +32,10 @@ typedoc:
 clean:
        pnpm run clean
 
+.PHONY: build
+build: 
+       pnpm run build
+
 .PHONY: submodules-update
 submodules-update:
        git submodule update --recursive --remote
@@ -40,6 +44,18 @@ submodules-update:
 check: compile
        pnpm run check
 
+.PHONY: dev
+dev: compile
+       pnpm run --filter merchant-backoffice dev
+
+.PHONY: serve
+serve: build
+       pnpm run --filter merchant-backoffice serve
+
+.PHONY: dev-view
+dev-view: compile
+       pnpm run --filter merchant-backoffice storybook
+
 .PHONY: lint
 lint:
        pnpm run lint
diff --git a/package.json b/package.json
index 12ef94d..99a785d 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,8 @@
   "scripts": {
     "clean": "pnpm run --filter '{packages}' clean",
     "compile": "pnpm run --filter '{packages}' compile",
+    "build": "pnpm run --filter '{packages}' build",
+    "serve": "pnpm run --filter '{packages}' serve",
     "lint": "pnpm run --filter '{packages}' lint",
     "typedoc": "pnpm run --filter '{packages}' typedoc",
     "pretty": "pnpm run --filter '{packages}' pretty",
diff --git a/packages/frontend/.storybook/preview.js 
b/packages/frontend/.storybook/preview.js
index edb0805..2c732ec 100644
--- a/packages/frontend/.storybook/preview.js
+++ b/packages/frontend/.storybook/preview.js
@@ -1,7 +1,13 @@
 import "../src/scss/main.scss"
 import { IntlProvider } from 'preact-i18n';
 import { h } from "preact";
-import lang from '../src/i18n'
+import { translations } from '../src/i18n'
+import { ConfigContext } from '../src/context/backend'
+
+const mockConfig = {
+  backendURL: 'http://demo.taler.net',
+  currency: 'KUDOS'
+}
 
 export const parameters = {
   controls: { expanded: true },
@@ -25,8 +31,9 @@ export const globalTypes = {
 
 export const decorators = [
   (Story, { globals }) => {
-    return <IntlProvider definition={lang[globals.locale]} mark>
+    return <IntlProvider definition={translations[globals.locale]} mark>
       <Story />
     </IntlProvider>
   },
+  (Story) => <ConfigContext.Provider value={mockConfig}> <Story /> 
</ConfigContext.Provider>
 ];
diff --git a/packages/frontend/src/components/auth/index.tsx 
b/packages/frontend/src/components/auth/index.tsx
index b6541a4..6ec69c0 100644
--- a/packages/frontend/src/components/auth/index.tsx
+++ b/packages/frontend/src/components/auth/index.tsx
@@ -21,7 +21,7 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { useBackend } from "../hooks";
+import { useBackend } from "../../hooks";
 
 interface Props {
   onConfirm?: () => void;
diff --git a/packages/frontend/src/components/yup/YupField.tsx 
b/packages/frontend/src/components/yup/YupField.tsx
index 7c08cda..5bbcf33 100644
--- a/packages/frontend/src/components/yup/YupField.tsx
+++ b/packages/frontend/src/components/yup/YupField.tsx
@@ -21,9 +21,7 @@
 
 import { h, VNode } from "preact";
 import { Text, useText } from "preact-i18n";
-import { useState } from "preact/hooks";
-import { useBackendConfig } from "../hooks/backend";
-import { useBackend } from "../hooks";
+import { StateUpdater, useContext, useState } from "preact/hooks";
 import { intervalToDuration, formatDuration } from 'date-fns'
 
 function readableDuration(duration: number): string {
@@ -33,7 +31,7 @@ function readableDuration(duration: number): string {
 // customFormatDuration({ start: 0, end: 10800 * 1000}) // 3 hours
 // customFormatDuration({ start: 0, end: 108000 * 1000}) // 1 day 6 hours
 
-interface Props {
+interface PropsInputInternal {
   name: string;
   value: string;
   readonly?: boolean;
@@ -41,7 +39,7 @@ interface Props {
   onChange: any;
 }
 
-interface Props2 {
+interface PropsObject {
   name: string;
   info: any;
   value: any;
@@ -49,8 +47,17 @@ interface Props2 {
   onChange: any;
 }
 
+interface Props {
+  name: string;
+  field: string;
+  errors: any;
+  object: any;
+  valueHandler: StateUpdater<any>;
+  info: any;
+}
+import { ConfigContext } from '../../context/backend';
 
-export function YupField(name: string, field: string, errors: any, object: 
any, valueHandler: any, info: any): VNode {
+export function YupField({ name, field, errors, object, valueHandler, info }: 
Props): VNode {
   const updateField = (f: string) => (v: string): void => valueHandler((prev: 
any) => ({ ...prev, [f]: v }))
   const values = {
     name, errors,
@@ -58,9 +65,7 @@ export function YupField(name: string, field: string, errors: 
any, object: any,
     value: object && object[field],
     onChange: updateField(field)
   }
-  const [backend] = useBackend()
-  const { data } = useBackendConfig()
-  const currency = data?.currency || ''
+  const config = useContext(ConfigContext)
 
   switch (info.meta?.type) {
     case 'group': return <YupObjectInput name={name}
@@ -69,8 +74,13 @@ export function YupField(name: string, field: string, 
errors: any, object: any,
       onChange={(updater: any): void => valueHandler((prev: any) => ({ 
...prev, [field]: updater(prev[field]) }))}
     />
     case 'array': return <YupInputArray {...values} />;
-    case 'amount': return <YupInputWithAddon {...values} addon={currency} 
onChange={(v: string): void => values.onChange(`${currency}:${v}`)} 
value={values.value?.split(':')[1]} />;
-    case 'url': return <YupInputWithAddon {...values} 
addon={`${backend.url}/private/instances/`} />;
+    case 'amount': {
+      if (config.currency) {
+        return <YupInputWithAddon {...values} addon={config.currency} 
onChange={(v: string): void => values.onChange(`${config.currency}:${v}`)} 
value={values.value?.split(':')[1]} />
+      } 
+      return <YupInput {...values} />;      
+    }
+    case 'url': return <YupInputWithAddon {...values} 
addon={`${config.backendURL}/private/instances/`} />;
     case 'secured': return <YupInputSecured {...values} />;
     case 'duration': return <YupInputWithAddon 
addon={readableDuration(values.value?.d_ms)} atTheEnd {...values} 
value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void => 
values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />;
     default: return <YupInput {...values} />;
@@ -78,7 +88,7 @@ export function YupField(name: string, field: string, errors: 
any, object: any,
   }
 }
 
-function YupObjectInput({ name, info, value, errors, onChange }: Props2): 
VNode {
+function YupObjectInput({ name, info, value, errors, onChange }: PropsObject): 
VNode {
   const [active, setActive] = useState(false)
   return <div class="card">
     <header class="card-header">
@@ -95,13 +105,16 @@ function YupObjectInput({ name, info, value, errors, 
onChange }: Props2): VNode
     </header>
     <div class={active ? "card-content" : "is-hidden"}>
       <div class="content">
-        {Object.keys(info.fields).map(f => YupField(`${name}.${f}`, f, errors, 
value, onChange, info.fields[f]))}
+        {Object.keys(info.fields).map(f => <YupField name={`${name}.${f}`}
+          field={f} errors={errors} object={value}
+          valueHandler={onChange} info={info.fields[f]}
+        />)}
       </div>
     </div>
   </div>
 }
 
-function YupInput({ name, readonly, value, errors, onChange }: Props): VNode {
+function YupInput({ name, readonly, value, errors, onChange }: 
PropsInputInternal): VNode {
   const dict = useText({
     placeholder: `fields.instance.${name}.placeholder`,
     tooltip: `fields.instance.${name}.tooltip`,
@@ -133,7 +146,7 @@ function YupInput({ name, readonly, value, errors, onChange 
}: Props): VNode {
   </div>
 }
 
-function YupInputArray({ name, readonly, value, errors, onChange }: Props): 
VNode {
+function YupInputArray({ name, readonly, value, errors, onChange }: 
PropsInputInternal): VNode {
   const dict = useText({
     placeholder: `fields.instance.${name}.placeholder`,
     tooltip: `fields.instance.${name}.tooltip`,
@@ -184,7 +197,7 @@ function YupInputArray({ name, readonly, value, errors, 
onChange }: Props): VNod
   </div>
 }
 
-function YupInputWithAddon({ name, readonly, value, errors, onChange, addon, 
atTheEnd }: Props & { addon: string; atTheEnd?: boolean }): VNode {
+function YupInputWithAddon({ name, readonly, value, errors, onChange, addon, 
atTheEnd }: PropsInputInternal & { addon: string; atTheEnd?: boolean }): VNode {
   const dict = useText({
     placeholder: `fields.instance.${name}.placeholder`,
     tooltip: `fields.instance.${name}.tooltip`,
@@ -222,7 +235,7 @@ function YupInputWithAddon({ name, readonly, value, errors, 
onChange, addon, atT
   </div>
 }
 
-function YupInputSecured({ name, readonly, value, errors, onChange }: Props): 
VNode {
+function YupInputSecured({ name, readonly, value, errors, onChange }: 
PropsInputInternal): VNode {
   const dict = useText({
     placeholder: `fields.instance.${name}.placeholder`,
     tooltip: `fields.instance.${name}.tooltip`,
diff --git a/packages/frontend/src/context/backend.ts 
b/packages/frontend/src/context/backend.ts
new file mode 100644
index 0000000..beb6a1b
--- /dev/null
+++ b/packages/frontend/src/context/backend.ts
@@ -0,0 +1,11 @@
+import { createContext } from 'preact'
+
+interface GlobalContext {
+  backendURL: string;
+  currency?: string;
+}
+
+export const ConfigContext = createContext<GlobalContext>({
+  backendURL: '',
+  currency: '',
+})
diff --git a/packages/frontend/src/components/hooks/backend.ts 
b/packages/frontend/src/hooks/backend.ts
similarity index 98%
rename from packages/frontend/src/components/hooks/backend.ts
rename to packages/frontend/src/hooks/backend.ts
index 360f87e..0804c7b 100644
--- a/packages/frontend/src/components/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -21,7 +21,7 @@
 
 import useSWR, { mutate } from 'swr';
 import axios from 'axios'
-import { MerchantBackend } from '../../declaration';
+import { MerchantBackend } from '../declaration';
 
 type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError<T>;
 
diff --git a/packages/frontend/src/components/hooks/index.ts 
b/packages/frontend/src/hooks/index.ts
similarity index 100%
rename from packages/frontend/src/components/hooks/index.ts
rename to packages/frontend/src/hooks/index.ts
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index 8d7e3ab..c613514 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -14,10 +14,10 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
 
 import "./scss/main.scss"
 
@@ -29,28 +29,36 @@ import { Footer } from './components/footer';
 import { Sidebar } from './components/sidebar';
 import { NavigationBar } from './components/navbar';
 import { Notifications } from './components/notifications';
-import { useNotifications } from './hooks/notifications';
 import { translations } from './i18n';
-import { useLang } from './components/hooks';
+import { useBackend, useLang } from './hooks';
 
 import NotFoundPage from './routes/notfound';
 import Instances from './routes/instances';
+import { useNotifications } from "./hooks/notifications";
+import { ConfigContext } from './context/backend';
+import { useBackendConfig } from "./hooks/backend";
 
 export default function App(): VNode {
   const { notifications, pushNotification } = useNotifications()
   const [lang, setLang] = useLang()
+  const [{url: backendURL}] = useBackend();
+  const { data } = useBackendConfig();
+
   return (
-    <IntlProvider definition={(translations as any)[lang] || translations.en}>
-      <div id="app">
-        <NavigationBar lang={lang} setLang={setLang} />
-        <Sidebar />
-        <Notifications notifications={notifications} />
-        <Router>
-          <Route path="/" component={Instances} 
pushNotification={pushNotification} />
-          <Route default component={NotFoundPage} />
-        </Router>
-        <Footer />
-      </div>
-    </IntlProvider >
+    <ConfigContext.Provider value={{backendURL, currency: data && 
data.currency}}>
+
+      <IntlProvider definition={(translations as any)[lang] || 
translations.en}>
+        <div id="app">
+          <NavigationBar lang={lang} setLang={setLang} />
+          <Sidebar />
+          <Notifications notifications={notifications} />
+          <Router>
+            <Route path="/" component={Instances} 
pushNotification={pushNotification} />
+            <Route default component={NotFoundPage} />
+          </Router>
+          <Footer />
+        </div>
+      </IntlProvider >
+    </ConfigContext.Provider>
   );
 }
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/Create.stories.tsx 
b/packages/frontend/src/routes/instances/Create.stories.tsx
index fb8e113..28ab52a 100644
--- a/packages/frontend/src/routes/instances/Create.stories.tsx
+++ b/packages/frontend/src/routes/instances/Create.stories.tsx
@@ -14,23 +14,25 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
 
 import { h, VNode } from 'preact';
-import {CreatePage} from './CreatePage'
+import { CreatePage } from './CreatePage'
 
 
 export default {
-  title: 'Instances/CreateModal',
+  title: 'Instances/Create',
   component: CreatePage,
   argTypes: {
-    element: { control: 'object' },
-    onCancel: { action: 'onCancel' },
-    onConfirm: { action: 'onConfirm' },
+    onCreate: { action: 'onCreate' },
+    goBack: { action: 'goBack' },
   }
 };
 
 export const Example = (a: any): VNode => <CreatePage {...a} />;
+Example.args = {
+  isLoading: false
+}
diff --git a/packages/frontend/src/routes/instances/CreatePage.tsx 
b/packages/frontend/src/routes/instances/CreatePage.tsx
index ba32100..7f16c2d 100644
--- a/packages/frontend/src/routes/instances/CreatePage.tsx
+++ b/packages/frontend/src/routes/instances/CreatePage.tsx
@@ -60,7 +60,6 @@ export function CreatePage({ onCreate, isLoading, goBack }: 
Props): VNode {
       setErrors(pathMessages)
     }
   }
-
   return <div>
     <section class="section is-title-bar">
 
@@ -97,7 +96,11 @@ export function CreatePage({ onCreate, isLoading, goBack }: 
Props): VNode {
       <div class="columns">
         <div class="column" />
         <div class="column is-two-thirds">
-          {Object.keys(schema.fields).map(f => YupField(f, f, errors, value, 
valueHandler, schema.fields[f].describe()))}
+          {Object.keys(schema.fields)
+            .map(f => <YupField name={f}
+              field={f} errors={errors} object={value}
+              valueHandler={valueHandler} info={schema.fields[f].describe()}
+            />)}
           <div class="buttons is-right">
             <button class="button" onClick={goBack} ><Text id="cancel" 
/></button>
             <button class="button is-success" onClick={submit} ><Text 
id="confirm" /></button>
diff --git a/packages/frontend/src/routes/instances/UpdatePage.tsx 
b/packages/frontend/src/routes/instances/UpdatePage.tsx
index fc62337..feed125 100644
--- a/packages/frontend/src/routes/instances/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/UpdatePage.tsx
@@ -101,8 +101,12 @@ export function UpdatePage({ onUpdate, isLoading, 
selected, goBack }: Props): VN
       <div class="columns">
         <div class="column" />
         <div class="column is-two-thirds">
-          {Object.keys(schema.fields).map(f => YupField(f, f, errors, value, 
valueHandler, schema.fields[f].describe()))}
-          <div class="buttons is-right">
+          {Object.keys(schema.fields)
+            .map(f => <YupField name={f}
+              field={f} errors={errors} object={value}
+              valueHandler={valueHandler} info={schema.fields[f].describe()}
+            />)}
+         <div class="buttons is-right">
             <button class="button" onClick={goBack} ><Text id="cancel" 
/></button>
             <button class="button is-success" onClick={submit} ><Text 
id="confirm" /></button>
           </div>
diff --git a/packages/frontend/src/routes/instances/index.tsx 
b/packages/frontend/src/routes/instances/index.tsx
index 4715424..e44b087 100644
--- a/packages/frontend/src/routes/instances/index.tsx
+++ b/packages/frontend/src/routes/instances/index.tsx
@@ -22,7 +22,7 @@
 import { h, VNode } from 'preact';
 import { View } from './View';
 import { LoginPage } from '../../components/auth';
-import { useBackendInstance, useBackendInstances } from 
'../../components/hooks/backend';
+import { useBackendInstance, useBackendInstances } from '../../hooks/backend';
 import { useEffect, useState } from 'preact/hooks';
 import { Notification } from '../../declaration';
 import { CreatePage } from './CreatePage';

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]