commit 53ed96d624fdcbf182594e47acfd484a9bf73dbf
parent f70673688d204df006f23b4574f7a5cafba5d826
Author: Sebastian <sebasjm@taler-systems.com>
Date: Mon, 12 Jan 2026 14:46:46 -0300
fix #10805
Diffstat:
3 files changed, 80 insertions(+), 303 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputSecured.stories.tsx b/packages/merchant-backoffice-ui/src/components/form/InputSecured.stories.tsx
@@ -1,61 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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 { useState } from "preact/hooks";
-import { FormProvider } from "./FormProvider.js";
-import { InputSecured } from "./InputSecured.js";
-
-export default {
- title: "Components/Form/InputSecured",
- component: InputSecured,
-};
-
-type T = { auth_token: string | null };
-
-export const InitialValueEmpty = (): VNode => {
- const [state, setState] = useState<Partial<T>>({ auth_token: "" });
- return (
- <FormProvider<T> object={state} errors={{}} valueHandler={setState}>
- Initial value: ''
- <InputSecured<T> name="auth_token" label="Password" />
- </FormProvider>
- );
-};
-
-export const InitialValueToken = (): VNode => {
- const [state, setState] = useState<Partial<T>>({ auth_token: "token" });
- return (
- <FormProvider<T> object={state} errors={{}} valueHandler={setState}>
- <InputSecured<T> name="auth_token" label="Password" />
- </FormProvider>
- );
-};
-
-export const InitialValueNull = (): VNode => {
- const [state, setState] = useState<Partial<T>>({ auth_token: null });
- return (
- <FormProvider<T> object={state} errors={{}} valueHandler={setState}>
- Initial value: ''
- <InputSecured<T> name="auth_token" label="Password" />
- </FormProvider>
- );
-};
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputSecured.tsx b/packages/merchant-backoffice-ui/src/components/form/InputSecured.tsx
@@ -1,188 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 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 { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { InputProps, useField } from "./useField.js";
-
-const TALER_SCREEN_ID = 13;
-
-export type Props<T> = InputProps<T>;
-
-const TokenStatus = ({ prev, post }: any) => {
- const { i18n } = useTranslationContext();
- if (
- (prev === undefined || prev === null) &&
- (post === undefined || post === null)
- )
- return null;
- return prev === post ? null : post === null ? (
- <span class="tag is-danger is-align-self-center ml-2">
- <i18n.Translate>Deleting</i18n.Translate>
- </span>
- ) : (
- <span class="tag is-warning is-align-self-center ml-2">
- <i18n.Translate>Changing</i18n.Translate>
- </span>
- );
-};
-
-export function InputSecured<T>({
- name,
- readonly,
- placeholder,
- tooltip,
- label,
- help,
-}: Props<keyof T>): VNode {
- const { error, value, initial, onChange, toStr, fromStr } = useField<T>(name);
-
- const [active, setActive] = useState(false);
- const [newValue, setNuewValue] = useState("");
-
- const { i18n } = useTranslationContext();
-
- return (
- <Fragment>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">
- {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-3">
- {!active ? (
- <Fragment>
- <div class="field has-addons">
- <button type="button" class="button"
- onClick={(): void => {
- setActive(!active);
- }}
- >
- <div class="icon is-left">
- <i class="mdi mdi-lock-reset" />
- </div>
- <span>
- <i18n.Translate>Manage password</i18n.Translate>
- </span>
- </button>
- <TokenStatus prev={initial} post={value} />
- </div>
- </Fragment>
- ) : (
- <Fragment>
- <div class="field has-addons">
- <div class="control">
- <a class="button is-static">secret-token:</a>
- </div>
- <div class="control is-expanded">
- <input
- class="input"
- type="text"
- placeholder={placeholder}
- readonly={readonly || !active}
- disabled={readonly || !active}
- name={String(name)}
- value={newValue}
- onInput={(e): void => {
- setNuewValue(e.currentTarget.value);
- }}
- />
- {help}
- </div>
- <div class="control">
- <button type="button" class="button is-info"
- disabled={fromStr(newValue) === value}
- onClick={(): void => {
- onChange(fromStr(newValue));
- setActive(!active);
- setNuewValue("");
- }}
- >
- <div class="icon is-left">
- <i class="mdi mdi-lock-outline" />
- </div>
- <span>
- <i18n.Translate>Update</i18n.Translate>
- </span>
- </button>
- </div>
- </div>
- </Fragment>
- )}
- {error ? (
- <p class="help is-danger" style={{ fontSize: 16 }}>
- {error}
- </p>
- ) : null}
- </div>
- </div>
- {active && (
- <div class="field is-horizontal">
- <div class="field-body is-flex-grow-3">
- <div class="level" style={{ width: "100%" }}>
- <div class="level-right is-flex-grow-1">
- <div class="level-item">
- <button type="button" class="button is-danger"
- disabled={null === value || undefined === value}
- onClick={(): void => {
- onChange(null!);
- setActive(!active);
- setNuewValue("");
- }}
- >
- <div class="icon is-left">
- <i class="mdi mdi-lock-open-variant" />
- </div>
- <span>
- <i18n.Translate>Remove</i18n.Translate>
- </span>
- </button>
- </div>
- <div class="level-item">
- <button type="button" class="button "
- onClick={(): void => {
- onChange(initial!);
- setActive(!active);
- setNuewValue("");
- }}
- >
- <div class="icon is-left">
- <i class="mdi mdi-lock-open-variant" />
- </div>
- <span>
- <i18n.Translate>Cancel</i18n.Translate>
- </span>
- </button>
- </div>
- </div>
- </div>
- </div>
- </div>
- )}
- </Fragment>
- );
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx
@@ -69,6 +69,7 @@ export function LoginPage({ showCreateAccount }: Props): VNode {
);
const { i18n } = useTranslationContext();
+ const [hidePassword, setHidePassword] = useState(true);
const [notification, safeFunctionHandler] = useLocalNotificationBetter();
const mfa = useChallengeHandler();
@@ -135,64 +136,89 @@ export function LoginPage({ showCreateAccount }: Props): VNode {
class="modal-card-body"
style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }}
>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">
- <i18n.Translate>Username</i18n.Translate>
- </label>
- <span class="icon has-tooltip-right" data-tooltip={i18n.str`Instance name.`}>
- <i class="mdi mdi-information" />
- </span>
- </div>
- <div class="field-body">
- <div class="field">
- <p class="control is-expanded">
- <input
- class="input"
- type="text"
- // placeholder={i18n.str`instance name`}
- name="username"
- onKeyPress={(e) =>
- e.keyCode === 13 ? login.call() : null
- }
- value={username}
- onInput={(e): void =>
- setUsername(e?.currentTarget.value)
- }
- />
- </p>
+ <form>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ <i18n.Translate>Username</i18n.Translate>
+ <span
+ class="icon has-tooltip-right"
+ data-tooltip={i18n.str`Instance name.`}
+ >
+ <i class="mdi mdi-information" />
+ </span>
+ </label>
+ </div>
+ <div class="field-body">
+ <div class="field">
+ <p class="control is-expanded">
+ <input
+ class="input"
+ type="text"
+ // placeholder={i18n.str`instance name`}
+ name="username"
+ onKeyPress={(e) =>
+ e.keyCode === 13 ? login.call() : null
+ }
+ value={username}
+ onInput={(e): void =>
+ setUsername(e?.currentTarget.value)
+ }
+ />
+ </p>
+ </div>
</div>
</div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">
- <i18n.Translate>Password</i18n.Translate>
- </label>
- <span class="icon has-tooltip-right" data-tooltip={i18n.str`Instance password.`}>
- <i class="mdi mdi-information" />
- </span>
- </div>
- <div class="field-body">
- <div class="field">
- <p class="control is-expanded">
- <input
- class="input"
- type="password"
- // placeholder={i18n.str`current password`}
- name="token"
- onKeyPress={(e) =>
- e.keyCode === 13 ? login.call() : null
- }
- value={password}
- onInput={(e): void =>
- setPassword(e?.currentTarget.value)
- }
- />
- </p>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ <i18n.Translate>Password</i18n.Translate>
+ <span
+ class="icon has-tooltip-right"
+ data-tooltip={i18n.str`Instance password.`}
+ >
+ <i class="mdi mdi-information" />
+ </span>
+ </label>
+ </div>
+ <div class="field-body">
+ <div class="field has-addons">
+ <p class="control is-expanded">
+ <input
+ class="input"
+ type={hidePassword ? "password" : "text"}
+ // placeholder={i18n.str`current password`}
+ name="token"
+ onKeyPress={(e) =>
+ e.keyCode === 13 ? login.call() : null
+ }
+ value={password}
+ onInput={(e): void =>
+ setPassword(e?.currentTarget.value)
+ }
+ />
+ </p>
+ <div
+ class="control"
+ style={{ cursor: "pointer" }}
+ onClick={() => {
+ setHidePassword((h) => !h);
+ }}
+ >
+ <a class="button is-static point">
+ <span class="icon">
+ {hidePassword ? (
+ <i class="mdi mdi-eye-off" />
+ ) : (
+ <i class="mdi mdi-eye" />
+ )}
+ </span>
+ </a>
+ </div>
+ </div>
</div>
</div>
- </div>
+ </form>
</section>
<footer
class="modal-card-foot "