diff options
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>    );  | 
