ui settings view

This commit is contained in:
Sebastian 2023-08-07 06:51:10 -03:00
parent 8eb0183c78
commit 7d1621767c
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
10 changed files with 118 additions and 59 deletions

View File

@ -26,7 +26,7 @@ import {
} from "@gnu-taler/web-util/browser"; } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { route } from "preact-router"; import { route } from "preact-router";
import { useMemo } from "preact/hooks"; import { useMemo, useState } from "preact/hooks";
import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js"; import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js";
import { Loading } from "./components/exception/loading.js"; import { Loading } from "./components/exception/loading.js";
import { import {
@ -42,6 +42,7 @@ import { useBackendConfig } from "./hooks/backend.js";
import { strings } from "./i18n/strings.js"; import { strings } from "./i18n/strings.js";
import LoginPage from "./paths/login/index.js"; import LoginPage from "./paths/login/index.js";
import { HttpStatusCode } from "@gnu-taler/taler-util"; import { HttpStatusCode } from "@gnu-taler/taler-util";
import { Settings } from "./paths/settings/index.js";
export function Application(): VNode { export function Application(): VNode {
return ( return (
@ -70,10 +71,19 @@ function ApplicationStatusRoutes(): VNode {
: { currency: "unknown", version: "unknown" }; : { currency: "unknown", version: "unknown" };
const ctx = useMemo(() => ({ currency, version }), [currency, version]); const ctx = useMemo(() => ({ currency, version }), [currency, version]);
const [showSettings, setShowSettings] = useState(false)
if (showSettings) {
return <Fragment>
<NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="UI Settings" />
<Settings />
</Fragment>
}
if (!triedToLog) { if (!triedToLog) {
return ( return (
<Fragment> <Fragment>
<NotYetReadyAppMenu title="Welcome!" /> <NotYetReadyAppMenu title="Welcome!" onShowSettings={() => setShowSettings(true)} />
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} /> <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
</Fragment> </Fragment>
); );
@ -87,7 +97,7 @@ function ApplicationStatusRoutes(): VNode {
) { ) {
return ( return (
<Fragment> <Fragment>
<NotYetReadyAppMenu title="Login" /> <NotYetReadyAppMenu title="Login" onShowSettings={() => setShowSettings(true)} />
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} /> <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
</Fragment> </Fragment>
); );
@ -98,7 +108,7 @@ function ApplicationStatusRoutes(): VNode {
) { ) {
return ( return (
<Fragment> <Fragment>
<NotYetReadyAppMenu title="Error" /> <NotYetReadyAppMenu title="Error" onShowSettings={() => setShowSettings(true)} />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n.str`Server not found`, message: i18n.str`Server not found`,
@ -112,7 +122,7 @@ function ApplicationStatusRoutes(): VNode {
} }
if (result.type === ErrorType.SERVER) { if (result.type === ErrorType.SERVER) {
<Fragment> <Fragment>
<NotYetReadyAppMenu title="Error" /> <NotYetReadyAppMenu title="Error" onShowSettings={() => setShowSettings(true)} />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n.str`Server response with an error code`, message: i18n.str`Server response with an error code`,
@ -125,7 +135,7 @@ function ApplicationStatusRoutes(): VNode {
} }
if (result.type === ErrorType.UNREADABLE) { if (result.type === ErrorType.UNREADABLE) {
<Fragment> <Fragment>
<NotYetReadyAppMenu title="Error" /> <NotYetReadyAppMenu title="Error" onShowSettings={() => setShowSettings(true)} />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n.str`Response from server is unreadable, http status: ${result.status}`, message: i18n.str`Response from server is unreadable, http status: ${result.status}`,
@ -138,7 +148,7 @@ function ApplicationStatusRoutes(): VNode {
} }
return ( return (
<Fragment> <Fragment>
<NotYetReadyAppMenu title="Error" /> <NotYetReadyAppMenu title="Error" onShowSettings={() => setShowSettings(true)} />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n.str`Unexpected Error`, message: i18n.str`Unexpected Error`,

View File

@ -33,6 +33,7 @@ import { InstanceRoutes } from "./InstanceRoutes.js";
import LoginPage from "./paths/login/index.js"; import LoginPage from "./paths/login/index.js";
import { INSTANCE_ID_LOOKUP } from "./utils/constants.js"; import { INSTANCE_ID_LOOKUP } from "./utils/constants.js";
import { HttpStatusCode } from "@gnu-taler/taler-util"; import { HttpStatusCode } from "@gnu-taler/taler-util";
import { Settings } from "./paths/settings/index.js";
export function ApplicationReadyRoutes(): VNode { export function ApplicationReadyRoutes(): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -48,8 +49,15 @@ export function ApplicationReadyRoutes(): VNode {
clearAllTokens(); clearAllTokens();
route("/"); route("/");
}; };
const [showSettings, setShowSettings] = useState(false)
if (result.loading) return <NotYetReadyAppMenu title="Loading..." />; if (showSettings) {
return <Fragment>
<NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="UI Settings" onLogout={clearTokenAndGoToRoot} />
<Settings/>
</Fragment>
}
if (result.loading) return <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Loading..." />;
let admin = true; let admin = true;
let instanceNameByBackendURL; let instanceNameByBackendURL;
@ -61,7 +69,7 @@ export function ApplicationReadyRoutes(): VNode {
) { ) {
return ( return (
<Fragment> <Fragment>
<NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} /> <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Login" onLogout={clearTokenAndGoToRoot} />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n.str`Access denied`, message: i18n.str`Access denied`,
@ -81,7 +89,7 @@ export function ApplicationReadyRoutes(): VNode {
// does not match our pattern // does not match our pattern
return ( return (
<Fragment> <Fragment>
<NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} /> <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Error" onLogout={clearTokenAndGoToRoot} />
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n.str`Couldn't access the server.`, message: i18n.str`Couldn't access the server.`,

View File

@ -68,6 +68,7 @@ import LoginPage from "./paths/login/index.js";
import NotFoundPage from "./paths/notfound/index.js"; import NotFoundPage from "./paths/notfound/index.js";
import { Notification } from "./utils/types.js"; import { Notification } from "./utils/types.js";
import { MerchantBackend } from "./declaration.js"; import { MerchantBackend } from "./declaration.js";
import { Settings } from "./paths/settings/index.js";
export enum InstancePaths { export enum InstancePaths {
// details = '/', // details = '/',
@ -100,6 +101,8 @@ export enum InstancePaths {
webhooks_list = "/webhooks", webhooks_list = "/webhooks",
webhooks_update = "/webhooks/:tid/update", webhooks_update = "/webhooks/:tid/update",
webhooks_new = "/webhooks/new", webhooks_new = "/webhooks/new",
settings = "/settings",
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
@ -240,6 +243,9 @@ export function InstanceRoutes({
<Menu <Menu
instance={id} instance={id}
admin={admin} admin={admin}
onShowSettings={() => {
route("/settings")
}}
path={path} path={path}
onLogout={clearTokenAndGoToRoot} onLogout={clearTokenAndGoToRoot}
setInstanceName={setInstanceName} setInstanceName={setInstanceName}
@ -558,6 +564,7 @@ export function InstanceRoutes({
}} }}
/> />
<Route path={InstancePaths.kyc} component={ListKYCPage} /> <Route path={InstancePaths.kyc} component={ListKYCPage} />
<Route path={InstancePaths.settings} component={Settings} />
{/** {/**
* Example pages * Example pages
*/} */}

View File

@ -229,7 +229,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
); );
} }
function AsyncButton({onClick, children}:{onClick: () => Promise<void>, children: ComponentChildren}):VNode { function AsyncButton({ onClick, children }: { onClick: () => Promise<void>, children: ComponentChildren }): VNode {
const [running, setRunning] = useState(false) const [running, setRunning] = useState(false)
return <button class="button is-info" disabled={running} onClick={() => { return <button class="button is-info" disabled={running} onClick={() => {
setRunning(true) setRunning(true)

View File

@ -20,7 +20,6 @@
*/ */
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { LangSelector } from "./LangSelector.js";
import logo from "../../assets/logo-2021.svg"; import logo from "../../assets/logo-2021.svg";
interface Props { interface Props {
@ -65,7 +64,6 @@ export function NavigationBar({ onMobileMenu, title }: Props): VNode {
</a> </a>
<div class="navbar-end"> <div class="navbar-end">
<div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}> <div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
<LangSelector />
</div> </div>
</div> </div>
</div> </div>

View File

@ -31,6 +31,7 @@ const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
interface Props { interface Props {
onLogout: () => void; onLogout: () => void;
onShowSettings: () => void;
mobile?: boolean; mobile?: boolean;
instance: string; instance: string;
admin?: boolean; admin?: boolean;
@ -40,6 +41,7 @@ interface Props {
export function Sidebar({ export function Sidebar({
mobile, mobile,
instance, instance,
onShowSettings,
onLogout, onLogout,
admin, admin,
mimic, mimic,
@ -78,21 +80,8 @@ export function Sidebar({
<div class="menu is-menu-main"> <div class="menu is-menu-main">
{instance ? ( {instance ? (
<Fragment> <Fragment>
<p class="menu-label">
<i18n.Translate>Instance</i18n.Translate>
</p>
<ul class="menu-list"> <ul class="menu-list">
<li> <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>
<a href={"/orders"} class="has-icon"> <a href={"/orders"} class="has-icon">
<span class="icon"> <span class="icon">
<i class="mdi mdi-cash-register" /> <i class="mdi mdi-cash-register" />
@ -132,6 +121,31 @@ export function Sidebar({
</span> </span>
</a> </a>
</li> </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> <li>
<a href={"/reserves"} class="has-icon"> <a href={"/reserves"} class="has-icon">
<span class="icon"> <span class="icon">
@ -150,16 +164,6 @@ export function Sidebar({
</span> </span>
</a> </a>
</li> </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> </ul>
</Fragment> </Fragment>
) : undefined} ) : undefined}
@ -167,6 +171,18 @@ export function Sidebar({
<i18n.Translate>Connection</i18n.Translate> <i18n.Translate>Connection</i18n.Translate>
</p> </p>
<ul class="menu-list"> <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> <li>
<div> <div>
<span style={{ width: "3rem" }} class="icon"> <span style={{ width: "3rem" }} class="icon">

View File

@ -75,6 +75,7 @@ interface MenuProps {
instance: string; instance: string;
admin?: boolean; admin?: boolean;
onLogout?: () => void; onLogout?: () => void;
onShowSettings: () => void;
setInstanceName: (s: string) => void; setInstanceName: (s: string) => void;
} }
@ -93,6 +94,7 @@ function WithTitle({
export function Menu({ export function Menu({
onLogout, onLogout,
onShowSettings,
title, title,
instance, instance,
path, path,
@ -121,6 +123,7 @@ export function Menu({
{onLogout && ( {onLogout && (
<Sidebar <Sidebar
onShowSettings={onShowSettings}
onLogout={onLogout} onLogout={onLogout}
admin={admin} admin={admin}
mimic={mimic} mimic={mimic}
@ -159,6 +162,7 @@ export function Menu({
interface NotYetReadyAppMenuProps { interface NotYetReadyAppMenuProps {
title: string; title: string;
onLogout?: () => void; onLogout?: () => void;
onShowSettings: () => void;
} }
interface NotifProps { interface NotifProps {
@ -199,6 +203,7 @@ export function NotificationCard({
export function NotYetReadyAppMenu({ export function NotYetReadyAppMenu({
onLogout, onLogout,
onShowSettings,
title, title,
}: NotYetReadyAppMenuProps): VNode { }: NotYetReadyAppMenuProps): VNode {
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
@ -217,7 +222,7 @@ export function NotYetReadyAppMenu({
title={title} title={title}
/> />
{onLogout && ( {onLogout && (
<Sidebar onLogout={onLogout} instance="" mobile={mobileOpen} /> <Sidebar onShowSettings={onShowSettings} onLogout={onLogout} instance="" mobile={mobileOpen} />
)} )}
</div> </div>
); );

View File

@ -164,7 +164,7 @@ export function ListPage({
<div class="field has-addons"> <div class="field has-addons">
{jumpToDate && ( {jumpToDate && (
<div class="control"> <div class="control">
<a class="button" onClick={() => onSelectDate(undefined)}> <a class="button is-fullwidth" onClick={() => onSelectDate(undefined)}>
<span <span
class="icon" class="icon"
data-tooltip={i18n.str`clear date filter`} data-tooltip={i18n.str`clear date filter`}
@ -191,7 +191,7 @@ export function ListPage({
<div class="control"> <div class="control">
<span class="has-tooltip-left" data-tooltip={dateTooltip}> <span class="has-tooltip-left" data-tooltip={dateTooltip}>
<a <a
class="button" class="button is-fullwidth"
onClick={() => { onClick={() => {
setPickDate(true); setPickDate(true);
}} }}

View File

@ -85,34 +85,34 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
template_contract: !state.template_contract template_contract: !state.template_contract
? undefined ? undefined
: undefinedIfEmpty({ : undefinedIfEmpty({
amount: !state.template_contract?.amount amount: !state.template_contract?.amount
? undefined ? undefined
: !parsedPrice : !parsedPrice
? i18n.str`not valid` ? i18n.str`not valid`
: Amounts.isZero(parsedPrice) : Amounts.isZero(parsedPrice)
? i18n.str`must be greater than 0` ? i18n.str`must be greater than 0`
: undefined,
minimum_age:
state.template_contract.minimum_age < 0
? i18n.str`should be greater that 0`
: undefined, : undefined,
pay_duration: !state.template_contract.pay_duration minimum_age:
? i18n.str`can't be empty` state.template_contract.minimum_age < 0
: state.template_contract.pay_duration.d_us === "forever" ? i18n.str`should be greater that 0`
: undefined,
pay_duration: !state.template_contract.pay_duration
? i18n.str`can't be empty`
: state.template_contract.pay_duration.d_us === "forever"
? undefined ? undefined
: state.template_contract.pay_duration.d_us < 1000 * 1000 //less than one second : state.template_contract.pay_duration.d_us < 1000 * 1000 //less than one second
? i18n.str`to short` ? i18n.str`to short`
: undefined, : undefined,
} as Partial<MerchantTemplateContractDetails>), } as Partial<MerchantTemplateContractDetails>),
pos_key: !state.pos_key pos_key: !state.pos_key
? !state.pos_algorithm ? !state.pos_algorithm
? undefined ? undefined
: i18n.str`required` : i18n.str`required`
: !isBase32RFC3548Charset(state.pos_key) : !isBase32RFC3548Charset(state.pos_key)
? i18n.str`just letters and numbers from 2 to 7` ? i18n.str`just letters and numbers from 2 to 7`
: state.pos_key.length !== 32 : state.pos_key.length !== 32
? i18n.str`size of the key should be 32` ? i18n.str`size of the key should be 32`
: undefined, : undefined,
}; };
const hasErrors = Object.keys(errors).some( const hasErrors = Object.keys(errors).some(
@ -139,7 +139,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
> >
<InputWithAddon<Entity> <InputWithAddon<Entity>
name="template_id" name="template_id"
addonBefore={`${backend.url}/instances/templates/`} help={`${backend.url}/instances/templates/${state.template_id ?? ""}`}
label={i18n.str`Identifier`} label={i18n.str`Identifier`}
tooltip={i18n.str`Name of the template in URLs.`} tooltip={i18n.str`Name of the template in URLs.`}
/> />

View File

@ -0,0 +1,15 @@
import { VNode, h } from "preact";
export function Settings(): VNode {
return <div>
<section class="section is-main-section">
<div class="columns">
<div class="column" />
<div class="column is-four-fifths">
settings view
</div>
<div class="column" />
</div>
</section>
</div>
}