diff options
| author | Özgür Kesim <oec-taler@kesim.org> | 2023-08-15 13:47:29 +0200 |
|---|---|---|
| committer | Özgür Kesim <oec-taler@kesim.org> | 2023-08-15 13:47:29 +0200 |
| commit | 819949d7f2cfcce8d334cbfdb701255daac64951 (patch) | |
| tree | 592043554f59ec209f9afc1c31c20c4fb585c3ca /packages/merchant-backoffice-ui/src/components | |
| parent | 77ea209ddb6d457db0ce113f91247207cc29fbd8 (diff) | |
| parent | adb0e70f151e63d103f27852312319520f4d0a84 (diff) | |
Merge branch 'master' into age-withdraw
Diffstat (limited to 'packages/merchant-backoffice-ui/src/components')
5 files changed, 234 insertions, 34 deletions
diff --git a/packages/merchant-backoffice-ui/src/components/exception/login.tsx b/packages/merchant-backoffice-ui/src/components/exception/login.tsx index 42c5e89d0..f2f94a7c5 100644 --- a/packages/merchant-backoffice-ui/src/components/exception/login.tsx +++ b/packages/merchant-backoffice-ui/src/components/exception/login.tsx @@ -20,7 +20,7 @@ */ import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; +import { ComponentChildren, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useBackendContext } from "../../context/backend.js"; import { useInstanceContext } from "../../context/instance.js"; @@ -40,7 +40,7 @@ function getTokenValuePart(t: string): string { } function normalizeToken(r: string): string { - return `secret-token:${encodeURIComponent(r)}`; + return `secret-token:${r}`; } function cleanUp(s: string): string { @@ -53,7 +53,7 @@ function cleanUp(s: string): string { export function LoginModal({ onConfirm, withMessage }: Props): VNode { const { url: backendUrl, token: baseToken } = useBackendContext(); - const { admin, token: instanceToken } = useInstanceContext(); + const { admin, token: instanceToken, id } = useInstanceContext(); const testLogin = useCredentialsChecker(); const currentToken = getTokenValuePart( (!admin ? baseToken : instanceToken) ?? "", @@ -63,6 +63,78 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { const [url, setURL] = useState(cleanUp(backendUrl)); const { i18n } = useTranslationContext(); + if (admin && id !== "default") { + //admin trying to access another instance + return (<div class="columns is-centered" style={{ margin: "auto" }}> + <div class="column is-two-thirds "> + <div class="modal-card" style={{ width: "100%", margin: 0 }}> + <header + class="modal-card-head" + style={{ border: "1px solid", borderBottom: 0 }} + > + <p class="modal-card-title">{i18n.str`Login required`}</p> + </header> + <section + class="modal-card-body" + style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} + > + <p> + <i18n.Translate>Need the access token for the instance.</i18n.Translate> + </p> + <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <i18n.Translate>Access Token</i18n.Translate> + </label> + </div> + <div class="field-body"> + <div class="field"> + <p class="control is-expanded"> + <input + class="input" + type="password" + placeholder={"set new access token"} + name="token" + onKeyPress={(e) => + e.keyCode === 13 + ? onConfirm(url, normalizeToken(token)) + : null + } + value={token} + onInput={(e): void => setToken(e?.currentTarget.value)} + /> + </p> + </div> + </div> + </div> + </section> + <footer + class="modal-card-foot " + style={{ + justifyContent: "flex-end", + border: "1px solid", + borderTop: 0, + }} + > + <AsyncButton + onClick={async () => { + const secretToken = normalizeToken(token); + const { valid, cause } = await testLogin(`${url}/instances/${id}`, secretToken); + if (valid) { + onConfirm(url, secretToken); + } else { + onConfirm(url); + } + }} + > + <i18n.Translate>Confirm</i18n.Translate> + </AsyncButton> + </footer> + </div> + </div> + </div>) + } + return ( <div class="columns is-centered" style={{ margin: "auto" }}> <div class="column is-two-thirds "> @@ -137,8 +209,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { borderTop: 0, }} > - <button - class="button is-info" + <AsyncButton onClick={async () => { const secretToken = normalizeToken(token); const { valid, cause } = await testLogin(url, secretToken); @@ -150,10 +221,24 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { }} > <i18n.Translate>Confirm</i18n.Translate> - </button> + </AsyncButton> </footer> </div> </div> </div> ); } + +function AsyncButton({ onClick, children }: { onClick: () => Promise<void>, children: ComponentChildren }): VNode { + const [running, setRunning] = useState(false) + return <button class="button is-info" disabled={running} onClick={() => { + setRunning(true) + onClick().then(() => { + setRunning(false) + }).catch(() => { + setRunning(false) + }) + }}> + {children} + </button> +} diff --git a/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx new file mode 100644 index 000000000..61ddf3c84 --- /dev/null +++ b/packages/merchant-backoffice-ui/src/components/form/InputToggle.tsx @@ -0,0 +1,91 @@ +/* + This file is part of GNU Taler + (C) 2021-2023 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { h, VNode } from "preact"; +import { InputProps, useField } from "./useField.js"; + +interface Props<T> extends InputProps<T> { + name: T; + readonly?: boolean; + expand?: boolean; + threeState?: boolean; + toBoolean?: (v?: any) => boolean | undefined; + fromBoolean?: (s: boolean | undefined) => any; +} + +const defaultToBoolean = (f?: any): boolean | undefined => f || ""; +const defaultFromBoolean = (v: boolean | undefined): any => v as any; + +export function InputToggle<T>({ + name, + readonly, + placeholder, + tooltip, + label, + help, + threeState, + expand, + fromBoolean = defaultFromBoolean, + toBoolean = defaultToBoolean, +}: Props<keyof T>): VNode { + const { error, value, onChange } = useField<T>(name); + + const onCheckboxClick = (): void => { + const c = toBoolean(value); + if (c === false && threeState) return onChange(undefined as any); + return onChange(fromBoolean(!c)); + }; + + return ( + <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label" style={{ width: 200 }}> + {label} + {tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + </div> + <div class="field-body is-flex-grow-1"> + <div class="field"> + <p class={expand ? "control is-expanded" : "control"}> + <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}> + <input + type="checkbox" + class={toBoolean(value) === undefined ? "is-indeterminate" : "toggle-checkbox"} + checked={toBoolean(value)} + placeholder={placeholder} + readonly={readonly} + name={String(name)} + disabled={readonly} + onChange={onCheckboxClick} + /> + <div class="toggle-switch"></div> + </label> + {help} + </p> + {error && <p class="help is-danger">{error}</p>} + </div> + </div> + </div> + ); +} diff --git a/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx index 9624a2c38..9f1b33893 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/NavigationBar.tsx @@ -20,7 +20,6 @@ */ import { h, VNode } from "preact"; -import { LangSelector } from "./LangSelector.js"; import logo from "../../assets/logo-2021.svg"; interface Props { @@ -65,7 +64,6 @@ export function NavigationBar({ onMobileMenu, title }: Props): VNode { </a> <div class="navbar-end"> <div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}> - <LangSelector /> </div> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx index 6fee600eb..f3cf80b92 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -31,6 +31,7 @@ const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; interface Props { onLogout: () => void; + onShowSettings: () => void; mobile?: boolean; instance: string; admin?: boolean; @@ -40,6 +41,7 @@ interface Props { export function Sidebar({ mobile, instance, + onShowSettings, onLogout, admin, mimic, @@ -78,21 +80,8 @@ export function Sidebar({ <div class="menu is-menu-main"> {instance ? ( <Fragment> - <p class="menu-label"> - <i18n.Translate>Instance</i18n.Translate> - </p> <ul class="menu-list"> - <li> - <a href={"/update"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-square-edit-outline" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>Settings</i18n.Translate> - </span> - </a> - </li> - <li> + <li> <a href={"/orders"} class="has-icon"> <span class="icon"> <i class="mdi mdi-cash-register" /> @@ -132,6 +121,31 @@ export function Sidebar({ </span> </a> </li> + {needKYC && ( + <li> + <a href={"/kyc"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-account-check" /> + </span> + <span class="menu-item-label">KYC Status</span> + </a> + </li> + )} + </ul> + <p class="menu-label"> + <i18n.Translate>Configuration</i18n.Translate> + </p> + <ul class="menu-list"> + <li> + <a href={"/update"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-square-edit-outline" /> + </span> + <span class="menu-item-label"> + <i18n.Translate>Account</i18n.Translate> + </span> + </a> + </li> <li> <a href={"/reserves"} class="has-icon"> <span class="icon"> @@ -150,16 +164,6 @@ export function Sidebar({ </span> </a> </li> - {needKYC && ( - <li> - <a href={"/kyc"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-account-check" /> - </span> - <span class="menu-item-label">KYC Status</span> - </a> - </li> - )} </ul> </Fragment> ) : undefined} @@ -168,6 +172,18 @@ export function Sidebar({ </p> <ul class="menu-list"> <li> + <a class="has-icon is-state-info is-hoverable" + onClick={(): void => onShowSettings()} + > + <span class="icon"> + <i class="mdi mdi-newspaper" /> + </span> + <span class="menu-item-label"> + <i18n.Translate>Settings</i18n.Translate> + </span> + </a> + </li> + <li> <div> <span style={{ width: "3rem" }} class="icon"> <i class="mdi mdi-currency-eur" /> diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx index 56573b8ca..cdbae4ae0 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx @@ -75,6 +75,7 @@ interface MenuProps { instance: string; admin?: boolean; onLogout?: () => void; + onShowSettings: () => void; setInstanceName: (s: string) => void; } @@ -93,6 +94,7 @@ function WithTitle({ export function Menu({ onLogout, + onShowSettings, title, instance, path, @@ -121,6 +123,7 @@ export function Menu({ {onLogout && ( <Sidebar + onShowSettings={onShowSettings} onLogout={onLogout} admin={admin} mimic={mimic} @@ -130,7 +133,12 @@ export function Menu({ )} {mimic && ( - <nav class="level"> + <nav class="level" style={{ + zIndex: 100, + position:"fixed", + width:"50%", + marginLeft: "20%" + }}> <div class="level-item has-text-centered has-background-warning"> <p class="is-size-5"> You are viewing the instance <b>"{instance}"</b>.{" "} @@ -154,6 +162,7 @@ export function Menu({ interface NotYetReadyAppMenuProps { title: string; onLogout?: () => void; + onShowSettings: () => void; } interface NotifProps { @@ -194,6 +203,7 @@ export function NotificationCard({ export function NotYetReadyAppMenu({ onLogout, + onShowSettings, title, }: NotYetReadyAppMenuProps): VNode { const [mobileOpen, setMobileOpen] = useState(false); @@ -212,7 +222,7 @@ export function NotYetReadyAppMenu({ title={title} /> {onLogout && ( - <Sidebar onLogout={onLogout} instance="" mobile={mobileOpen} /> + <Sidebar onShowSettings={onShowSettings} onLogout={onLogout} instance="" mobile={mobileOpen} /> )} </div> ); |
