diff options
Diffstat (limited to 'packages/exchange-backoffice-ui/src/pages')
| -rw-r--r-- | packages/exchange-backoffice-ui/src/pages/CaseDetails.tsx (renamed from packages/exchange-backoffice-ui/src/pages/AccountDetails.tsx) | 2 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/pages/Cases.tsx | 8 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/pages/CreateAccount.tsx | 89 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/pages/HandleAccountNotReady.tsx | 31 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/pages/Officer.tsx | 247 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/pages/UnlockAccount.tsx | 70 |
6 files changed, 208 insertions, 239 deletions
diff --git a/packages/exchange-backoffice-ui/src/pages/AccountDetails.tsx b/packages/exchange-backoffice-ui/src/pages/CaseDetails.tsx index b252d2ab0..e5fb8eaba 100644 --- a/packages/exchange-backoffice-ui/src/pages/AccountDetails.tsx +++ b/packages/exchange-backoffice-ui/src/pages/CaseDetails.tsx @@ -141,7 +141,7 @@ function getEventsFromAmlHistory( return ae.concat(ke).sort(selectSooner); } -export function AccountDetails({ account }: { account?: string }) { +export function CaseDetails({ account }: { account?: string }) { const events = getEventsFromAmlHistory( response.aml_history, response.kyc_attributes, diff --git a/packages/exchange-backoffice-ui/src/pages/Cases.tsx b/packages/exchange-backoffice-ui/src/pages/Cases.tsx index 1983769ed..28b9d2a88 100644 --- a/packages/exchange-backoffice-ui/src/pages/Cases.tsx +++ b/packages/exchange-backoffice-ui/src/pages/Cases.tsx @@ -4,8 +4,10 @@ import { AmlRecords, AmlState } from "../types.js"; import { InputChoiceHorizontal } from "../handlers/InputChoiceHorizontal.js"; import { createNewForm } from "../handlers/forms.js"; import { TranslatedString } from "@gnu-taler/taler-util"; -import { amlStateConverter as amlStateConverter } from "./AccountDetails.js"; +import { amlStateConverter as amlStateConverter } from "./CaseDetails.js"; import { useState } from "preact/hooks"; +import { HandleAccountNotReady } from "./HandleAccountNotReady.js"; +import { useOfficer } from "../hooks/useOfficer.js"; const response: AmlRecords = { records: [ @@ -61,6 +63,10 @@ function doFilter( } export function Cases() { + const officer = useOfficer(); + if (officer.state !== "ready") { + return <HandleAccountNotReady officer={officer} />; + } const form = createNewForm<{ state: AmlState; }>(); diff --git a/packages/exchange-backoffice-ui/src/pages/CreateAccount.tsx b/packages/exchange-backoffice-ui/src/pages/CreateAccount.tsx new file mode 100644 index 000000000..41a1d20ff --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/CreateAccount.tsx @@ -0,0 +1,89 @@ +import { TranslatedString } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { VNode, h } from "preact"; +import { createNewForm } from "../handlers/forms.js"; + +export function CreateAccount({ + onNewAccount, +}: { + onNewAccount: (password: string) => void; +}): VNode { + const { i18n } = useTranslationContext(); + const Form = createNewForm<{ + password: string; + repeat: string; + }>(); + + 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 + </h2> + </div> + + <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px] "> + <div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12"> + <Form.Provider + computeFormState={(v) => { + return { + 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, + }, + repeat: { + // error: !v.repeat + // ? i18n.str`required` + // // : v.repeat !== v.password + // // ? i18n.str`doesn't match` + // : undefined, + }, + }; + }} + onSubmit={async (v) => { + onNewAccount(v.password); + }} + > + <div class="mb-4"> + <Form.InputLine + label={"Password" as TranslatedString} + name="password" + type="password" + help={ + "lower and upper case letters, number and special character" as TranslatedString + } + required + /> + </div> + <div class="mb-4"> + <Form.InputLine + label={"Repeat password" as TranslatedString} + name="repeat" + type="password" + required + /> + </div> + + <div class="mt-8"> + <button + 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 + </button> + </div> + </Form.Provider> + </div> + </div> + </div> + ); +} diff --git a/packages/exchange-backoffice-ui/src/pages/HandleAccountNotReady.tsx b/packages/exchange-backoffice-ui/src/pages/HandleAccountNotReady.tsx new file mode 100644 index 000000000..b0c430875 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/HandleAccountNotReady.tsx @@ -0,0 +1,31 @@ +import { VNode, h } from "preact"; +import { OfficerNotReady } from "../hooks/useOfficer.js"; +import { CreateAccount } from "./CreateAccount.js"; +import { UnlockAccount } from "./UnlockAccount.js"; + +export function HandleAccountNotReady({ + officer, +}: { + officer: OfficerNotReady; +}): VNode { + if (officer.state === "not-found") { + return ( + <CreateAccount + onNewAccount={(password) => { + officer.create(password); + }} + /> + ); + } + + if (officer.state === "locked") { + return ( + <UnlockAccount + onAccountUnlocked={(pwd) => { + officer.tryUnlock(pwd); + }} + /> + ); + } + throw Error(`unexpected account state ${(officer as any).state}`); +} diff --git a/packages/exchange-backoffice-ui/src/pages/Officer.tsx b/packages/exchange-backoffice-ui/src/pages/Officer.tsx index 40ec33018..5320369e4 100644 --- a/packages/exchange-backoffice-ui/src/pages/Officer.tsx +++ b/packages/exchange-backoffice-ui/src/pages/Officer.tsx @@ -1,83 +1,11 @@ -import { - AbsoluteTime, - Codec, - TranslatedString, - buildCodecForObject, - codecForAbsoluteTime, - codecForString, -} from "@gnu-taler/taler-util"; -import { - notifyError, - notifyInfo, - useLocalStorage, - useMemoryStorage, - useTranslationContext, -} from "@gnu-taler/web-util/browser"; -import { VNode, h } from "preact"; -import { useEffect, useState } from "preact/hooks"; -import { - Account, - LockedAccount, - UnwrapKeyError, - createNewAccount, - unlockAccount, -} from "../account.js"; -import { createNewForm } from "../handlers/forms.js"; - -export interface Officer { - account: LockedAccount; - when: AbsoluteTime; -} - -const codecForLockedAccount = codecForString() as Codec<LockedAccount>; - -export const codecForOfficer = (): Codec<Officer> => - buildCodecForObject<Officer>() - .property("account", codecForLockedAccount) // FIXME - .property("when", codecForAbsoluteTime) // FIXME - .build("Officer"); +import { Fragment, h } from "preact"; +import { useOfficer } from "../hooks/useOfficer.js"; +import { HandleAccountNotReady } from "./HandleAccountNotReady.js"; export function Officer() { - const password = useMemoryStorage("password"); - const officer = useLocalStorage("officer", { - codec: codecForOfficer(), - }); - const [keys, setKeys] = useState<Account>(); - - useEffect(() => { - if (officer.value === undefined || password.value === undefined) { - return; - } - - unlockAccount(officer.value.account, password.value) - .then((keys) => setKeys(keys ?? { accountId: "", pub: "" })) - .catch((e) => { - if (e instanceof UnwrapKeyError) { - console.log(e); - } - }); - }, [officer.value, password.value]); - - if (officer.value === undefined || !officer.value.account) { - return ( - <CreateAccount - onNewAccount={(account, pwd) => { - password.update(pwd); - officer.update({ account, when: AbsoluteTime.now() }); - }} - /> - ); - } - - if (password.value === undefined) { - return ( - <UnlockAccount - lockedAccount={officer.value.account} - onAccountUnlocked={(pwd) => { - password.update(pwd); - }} - /> - ); + const officer = useOfficer(); + if (officer.state !== "ready") { + return <HandleAccountNotReady officer={officer} />; } return ( @@ -86,12 +14,12 @@ export function Officer() { Public key </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">{keys?.accountId}</p> + <p class="mt-6 font-mono break-all">{officer.account.accountId}</p> </div> <p> <a href={`mailto:aml@exchange.taler.net?body=${encodeURIComponent( - `I want my AML account\n\n\nPubKey: ${keys?.accountId}`, + `I want my AML account\n\n\nPubKey: ${officer.account.accountId}`, )}`} target="_blank" rel="noreferrer" @@ -104,7 +32,7 @@ export function Officer() { <button type="button" onClick={() => { - password.reset(); + officer.lock(); }} class="m-4 block rounded-md border-0 bg-gray-200 px-3 py-2 text-center text-sm text-black shadow-sm " > @@ -115,7 +43,7 @@ export function Officer() { <button type="button" onClick={() => { - officer.reset(); + officer.forget(); }} 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 " > @@ -125,158 +53,3 @@ export function Officer() { </div> ); } - -function CreateAccount({ - onNewAccount, -}: { - onNewAccount: (account: LockedAccount, password: string) => void; -}): VNode { - const { i18n } = useTranslationContext(); - const Form = createNewForm<{ - password: string; - repeat: string; - }>(); - - 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 - </h2> - </div> - - <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px] "> - <div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12"> - <Form.Provider - computeFormState={(v) => { - return { - 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, - }, - repeat: { - // error: !v.repeat - // ? i18n.str`required` - // // : v.repeat !== v.password - // // ? i18n.str`doesn't match` - // : undefined, - }, - }; - }} - onSubmit={async (v) => { - const account = await createNewAccount(v.password); - onNewAccount(account, v.password); - }} - > - <div class="mb-4"> - <Form.InputLine - label={"Password" as TranslatedString} - name="password" - type="password" - help={ - "lower and upper case letters, number and special character" as TranslatedString - } - required - /> - </div> - <div class="mb-4"> - <Form.InputLine - label={"Repeat password" as TranslatedString} - name="repeat" - type="password" - required - /> - </div> - - <div class="mt-8"> - <button - 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 - </button> - </div> - </Form.Provider> - </div> - </div> - </div> - ); -} - -function UnlockAccount({ - lockedAccount, - onAccountUnlocked, -}: { - lockedAccount: LockedAccount; - onAccountUnlocked: (password: string) => void; -}): VNode { - const Form = createNewForm<{ - password: string; - }>(); - - 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> - <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. - </p> - </div> - - <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px] "> - <div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12"> - <Form.Provider - onSubmit={async (v) => { - try { - // test login - await unlockAccount(lockedAccount, v.password); - - onAccountUnlocked(v.password ?? ""); - notifyInfo("Account unlocked" as TranslatedString); - } catch (e) { - if (e instanceof UnwrapKeyError) { - notifyError( - "Could not unlock account" as any, - e.message as any, - ); - } else { - throw e; - } - } - }} - > - <div class="mb-4"> - <Form.InputLine - label={"Password" as TranslatedString} - name="password" - type="password" - required - /> - </div> - - <div class="mt-8"> - <button - 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 - </button> - </div> - </Form.Provider> - </div> - </div> - </div> - ); -} diff --git a/packages/exchange-backoffice-ui/src/pages/UnlockAccount.tsx b/packages/exchange-backoffice-ui/src/pages/UnlockAccount.tsx new file mode 100644 index 000000000..941e28627 --- /dev/null +++ b/packages/exchange-backoffice-ui/src/pages/UnlockAccount.tsx @@ -0,0 +1,70 @@ +import { TranslatedString } from "@gnu-taler/taler-util"; +import { notifyError, notifyInfo } from "@gnu-taler/web-util/browser"; +import { VNode, h } from "preact"; +import { UnwrapKeyError } from "../account.js"; +import { createNewForm } from "../handlers/forms.js"; + +export function UnlockAccount({ + onAccountUnlocked, +}: { + onAccountUnlocked: (password: string) => void; +}): VNode { + const Form = createNewForm<{ + password: string; + }>(); + + 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> + <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. + </p> + </div> + + <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px] "> + <div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12"> + <Form.Provider + onSubmit={async (v) => { + try { + await onAccountUnlocked(v.password); + + notifyInfo("Account unlocked" as TranslatedString); + } catch (e) { + if (e instanceof UnwrapKeyError) { + notifyError( + "Could not unlock account" as any, + e.message as any, + ); + } else { + throw e; + } + } + }} + > + <div class="mb-4"> + <Form.InputLine + label={"Password" as TranslatedString} + name="password" + type="password" + required + /> + </div> + + <div class="mt-8"> + <button + 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 + </button> + </div> + </Form.Provider> + </div> + </div> + </div> + ); +} |
