remove webui from login url, ad qr for template, fix navbar size,
This commit is contained in:
parent
2291d460e8
commit
8ddc551cc8
@ -25,6 +25,7 @@ serve({
|
|||||||
folder: './dist',
|
folder: './dist',
|
||||||
port: 8080,
|
port: 8080,
|
||||||
source: './src',
|
source: './src',
|
||||||
|
insecure: true,
|
||||||
development: true,
|
development: true,
|
||||||
onUpdate: async () => esbuild.build(buildConfig)
|
onUpdate: async () => esbuild.build(buildConfig)
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
TranslationProvider,
|
TranslationProvider,
|
||||||
useTranslationContext,
|
useTranslationContext,
|
||||||
} from "@gnu-taler/web-util/lib/index.browser";
|
} from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { 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 } from "preact/hooks";
|
||||||
import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js";
|
import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js";
|
||||||
@ -70,24 +70,24 @@ function ApplicationStatusRoutes(): VNode {
|
|||||||
|
|
||||||
if (!triedToLog) {
|
if (!triedToLog) {
|
||||||
return (
|
return (
|
||||||
<div id="app">
|
<Fragment>
|
||||||
<NotYetReadyAppMenu title="Welcome!" />
|
<NotYetReadyAppMenu title="Welcome!" />
|
||||||
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.clientError && result.isUnauthorized)
|
if (result.clientError && result.isUnauthorized)
|
||||||
return (
|
return (
|
||||||
<div id="app">
|
<Fragment>
|
||||||
<NotYetReadyAppMenu title="Login" />
|
<NotYetReadyAppMenu title="Login" />
|
||||||
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.clientError && result.isNotfound)
|
if (result.clientError && result.isNotfound)
|
||||||
return (
|
return (
|
||||||
<div id="app">
|
<Fragment>
|
||||||
<NotYetReadyAppMenu title="Error" />
|
<NotYetReadyAppMenu title="Error" />
|
||||||
<NotificationCard
|
<NotificationCard
|
||||||
notification={{
|
notification={{
|
||||||
@ -97,12 +97,12 @@ function ApplicationStatusRoutes(): VNode {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.serverError)
|
if (result.serverError)
|
||||||
return (
|
return (
|
||||||
<div id="app">
|
<Fragment>
|
||||||
<NotYetReadyAppMenu title="Error" />
|
<NotYetReadyAppMenu title="Error" />
|
||||||
<NotificationCard
|
<NotificationCard
|
||||||
notification={{
|
notification={{
|
||||||
@ -112,14 +112,14 @@ function ApplicationStatusRoutes(): VNode {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.loading) return <Loading />;
|
if (result.loading) return <Loading />;
|
||||||
|
|
||||||
if (!result.ok)
|
if (!result.ok)
|
||||||
return (
|
return (
|
||||||
<div id="app">
|
<Fragment>
|
||||||
<NotYetReadyAppMenu title="Error" />
|
<NotYetReadyAppMenu title="Error" />
|
||||||
<NotificationCard
|
<NotificationCard
|
||||||
notification={{
|
notification={{
|
||||||
@ -129,7 +129,7 @@ function ApplicationStatusRoutes(): VNode {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
<LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -52,6 +52,7 @@ import ReservesDetailsPage from "./paths/instance/reserves/details/index.js";
|
|||||||
import ReservesListPage from "./paths/instance/reserves/list/index.js";
|
import ReservesListPage from "./paths/instance/reserves/list/index.js";
|
||||||
import TemplateCreatePage from "./paths/instance/templates/create/index.js";
|
import TemplateCreatePage from "./paths/instance/templates/create/index.js";
|
||||||
import TemplateUsePage from "./paths/instance/templates/use/index.js";
|
import TemplateUsePage from "./paths/instance/templates/use/index.js";
|
||||||
|
import TemplateQrPage from "./paths/instance/templates/qr/index.js";
|
||||||
import TemplateListPage from "./paths/instance/templates/list/index.js";
|
import TemplateListPage from "./paths/instance/templates/list/index.js";
|
||||||
import TemplateUpdatePage from "./paths/instance/templates/update/index.js";
|
import TemplateUpdatePage from "./paths/instance/templates/update/index.js";
|
||||||
import WebhookCreatePage from "./paths/instance/webhooks/create/index.js";
|
import WebhookCreatePage from "./paths/instance/webhooks/create/index.js";
|
||||||
@ -94,6 +95,7 @@ export enum InstancePaths {
|
|||||||
templates_update = "/templates/:tid/update",
|
templates_update = "/templates/:tid/update",
|
||||||
templates_new = "/templates/new",
|
templates_new = "/templates/new",
|
||||||
templates_use = "/templates/:tid/use",
|
templates_use = "/templates/:tid/use",
|
||||||
|
templates_qr = "/templates/:tid/qr",
|
||||||
|
|
||||||
webhooks_list = "/webhooks",
|
webhooks_list = "/webhooks",
|
||||||
webhooks_update = "/webhooks/:tid/update",
|
webhooks_update = "/webhooks/:tid/update",
|
||||||
@ -465,6 +467,9 @@ export function InstanceRoutes({
|
|||||||
onNewOrder={(id: string) => {
|
onNewOrder={(id: string) => {
|
||||||
route(InstancePaths.templates_use.replace(":tid", id));
|
route(InstancePaths.templates_use.replace(":tid", id));
|
||||||
}}
|
}}
|
||||||
|
onQR={(id: string) => {
|
||||||
|
route(InstancePaths.templates_qr.replace(":tid", id));
|
||||||
|
}}
|
||||||
onSelect={(id: string) => {
|
onSelect={(id: string) => {
|
||||||
route(InstancePaths.templates_update.replace(":tid", id));
|
route(InstancePaths.templates_update.replace(":tid", id));
|
||||||
}}
|
}}
|
||||||
@ -505,6 +510,16 @@ export function InstanceRoutes({
|
|||||||
route(InstancePaths.templates_list);
|
route(InstancePaths.templates_list);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path={InstancePaths.templates_qr}
|
||||||
|
component={TemplateQrPage}
|
||||||
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
|
onLoadError={ServerErrorRedirectTo(InstancePaths.templates_list)}
|
||||||
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
|
onBack={() => {
|
||||||
|
route(InstancePaths.templates_list);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/**
|
{/**
|
||||||
* reserves pages
|
* reserves pages
|
||||||
|
@ -42,6 +42,14 @@ function normalizeToken(r: string | undefined): string | undefined {
|
|||||||
return r ? `secret-token:${encodeURIComponent(r)}` : undefined;
|
return r ? `secret-token:${encodeURIComponent(r)}` : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanUp(s: string): string {
|
||||||
|
let result = s;
|
||||||
|
if (result.indexOf("webui/") !== -1) {
|
||||||
|
result = result.substring(0, result.indexOf("webui/"));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function LoginModal({ onConfirm, withMessage }: Props): VNode {
|
export function LoginModal({ onConfirm, withMessage }: Props): VNode {
|
||||||
const { url: backendUrl, token: baseToken } = useBackendContext();
|
const { url: backendUrl, token: baseToken } = useBackendContext();
|
||||||
const { admin, token: instanceToken } = useInstanceContext();
|
const { admin, token: instanceToken } = useInstanceContext();
|
||||||
@ -50,11 +58,11 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode {
|
|||||||
);
|
);
|
||||||
const [token, setToken] = useState(currentToken);
|
const [token, setToken] = useState(currentToken);
|
||||||
|
|
||||||
const [url, setURL] = useState(backendUrl);
|
const [url, setURL] = useState(cleanUp(backendUrl));
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="columns is-centered">
|
<div class="columns is-centered" style={{ margin: "auto" }}>
|
||||||
<div class="column is-two-thirds ">
|
<div class="column is-two-thirds ">
|
||||||
<div class="modal-card" style={{ width: "100%", margin: 0 }}>
|
<div class="modal-card" style={{ width: "100%", margin: 0 }}>
|
||||||
<header
|
<header
|
||||||
|
@ -61,7 +61,7 @@ export function NavigationBar({ onMobileMenu, title }: Props): VNode {
|
|||||||
class="navbar-start is-justify-content-center is-flex-grow-1"
|
class="navbar-start is-justify-content-center is-flex-grow-1"
|
||||||
href="https://taler.net"
|
href="https://taler.net"
|
||||||
>
|
>
|
||||||
<img src={logo} style={{ height: 50, margin: 10 }} />
|
<img src={logo} style={{ height: 35, margin: 10 }} />
|
||||||
</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 }}>
|
||||||
|
@ -57,56 +57,54 @@ export function View({
|
|||||||
: instances;
|
: instances;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="app">
|
<section class="section is-main-section">
|
||||||
<section class="section is-main-section">
|
<div class="columns">
|
||||||
<div class="columns">
|
<div class="column is-two-thirds">
|
||||||
<div class="column is-two-thirds">
|
<div class="tabs" style={{ overflow: "inherit" }}>
|
||||||
<div class="tabs" style={{ overflow: "inherit" }}>
|
<ul>
|
||||||
<ul>
|
<li class={showIsActive}>
|
||||||
<li class={showIsActive}>
|
<div
|
||||||
<div
|
class="has-tooltip-right"
|
||||||
class="has-tooltip-right"
|
data-tooltip={i18n.str`Only show active instances`}
|
||||||
data-tooltip={i18n.str`Only show active instances`}
|
>
|
||||||
>
|
<a onClick={() => setShow("active")}>
|
||||||
<a onClick={() => setShow("active")}>
|
<i18n.Translate>Active</i18n.Translate>
|
||||||
<i18n.Translate>Active</i18n.Translate>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
<li class={showIsDeleted}>
|
||||||
<li class={showIsDeleted}>
|
<div
|
||||||
<div
|
class="has-tooltip-right"
|
||||||
class="has-tooltip-right"
|
data-tooltip={i18n.str`Only show deleted instances`}
|
||||||
data-tooltip={i18n.str`Only show deleted instances`}
|
>
|
||||||
>
|
<a onClick={() => setShow("deleted")}>
|
||||||
<a onClick={() => setShow("deleted")}>
|
<i18n.Translate>Deleted</i18n.Translate>
|
||||||
<i18n.Translate>Deleted</i18n.Translate>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
<li class={showAll}>
|
||||||
<li class={showAll}>
|
<div
|
||||||
<div
|
class="has-tooltip-right"
|
||||||
class="has-tooltip-right"
|
data-tooltip={i18n.str`Show all instances`}
|
||||||
data-tooltip={i18n.str`Show all instances`}
|
>
|
||||||
>
|
<a onClick={() => setShow(null)}>
|
||||||
<a onClick={() => setShow(null)}>
|
<i18n.Translate>All</i18n.Translate>
|
||||||
<i18n.Translate>All</i18n.Translate>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CardTableActive
|
</div>
|
||||||
instances={showingInstances}
|
<CardTableActive
|
||||||
onDelete={onDelete}
|
instances={showingInstances}
|
||||||
onPurge={onPurge}
|
onDelete={onDelete}
|
||||||
setInstanceName={setInstanceName}
|
onPurge={onPurge}
|
||||||
onUpdate={onUpdate}
|
setInstanceName={setInstanceName}
|
||||||
selected={selected}
|
onUpdate={onUpdate}
|
||||||
onCreate={onCreate}
|
selected={selected}
|
||||||
/>
|
onCreate={onCreate}
|
||||||
</section>
|
/>
|
||||||
</div>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -114,13 +114,13 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
<Input
|
<Input
|
||||||
name="template_contract.summary"
|
name="template_contract.summary"
|
||||||
inputType="multiline"
|
inputType="multiline"
|
||||||
label={i18n.str`Order summary`}
|
label={i18n.str`Fixed summary`}
|
||||||
tooltip={i18n.str`Title of the order to be shown to the customer`}
|
tooltip={i18n.str`If specified, this template will create order with the same summary`}
|
||||||
/>
|
/>
|
||||||
<InputCurrency
|
<InputCurrency
|
||||||
name="template_contract.amount"
|
name="template_contract.amount"
|
||||||
label={i18n.str`Order price`}
|
label={i18n.str`Fixed price`}
|
||||||
tooltip={i18n.str`Order price`}
|
tooltip={i18n.str`If specified, this template will create order with the same price`}
|
||||||
/>
|
/>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
name="template_contract.minimum_age"
|
name="template_contract.minimum_age"
|
||||||
|
@ -32,6 +32,7 @@ export interface Props {
|
|||||||
onDelete: (e: MerchantBackend.Template.TemplateEntry) => void;
|
onDelete: (e: MerchantBackend.Template.TemplateEntry) => void;
|
||||||
onSelect: (e: MerchantBackend.Template.TemplateEntry) => void;
|
onSelect: (e: MerchantBackend.Template.TemplateEntry) => void;
|
||||||
onNewOrder: (e: MerchantBackend.Template.TemplateEntry) => void;
|
onNewOrder: (e: MerchantBackend.Template.TemplateEntry) => void;
|
||||||
|
onQR: (e: MerchantBackend.Template.TemplateEntry) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListPage({
|
export function ListPage({
|
||||||
@ -40,6 +41,7 @@ export function ListPage({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onSelect,
|
onSelect,
|
||||||
onNewOrder,
|
onNewOrder,
|
||||||
|
onQR,
|
||||||
onLoadMoreBefore,
|
onLoadMoreBefore,
|
||||||
onLoadMoreAfter,
|
onLoadMoreAfter,
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
@ -53,6 +55,7 @@ export function ListPage({
|
|||||||
...o,
|
...o,
|
||||||
id: String(o.template_id),
|
id: String(o.template_id),
|
||||||
}))}
|
}))}
|
||||||
|
onQR={onQR}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
@ -31,6 +31,7 @@ interface Props {
|
|||||||
onDelete: (e: Entity) => void;
|
onDelete: (e: Entity) => void;
|
||||||
onSelect: (e: Entity) => void;
|
onSelect: (e: Entity) => void;
|
||||||
onNewOrder: (e: Entity) => void;
|
onNewOrder: (e: Entity) => void;
|
||||||
|
onQR: (e: Entity) => void;
|
||||||
onCreate: () => void;
|
onCreate: () => void;
|
||||||
onLoadMoreBefore?: () => void;
|
onLoadMoreBefore?: () => void;
|
||||||
hasMoreBefore?: boolean;
|
hasMoreBefore?: boolean;
|
||||||
@ -43,6 +44,7 @@ export function CardTable({
|
|||||||
onCreate,
|
onCreate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
onQR,
|
||||||
onNewOrder,
|
onNewOrder,
|
||||||
onLoadMoreAfter,
|
onLoadMoreAfter,
|
||||||
onLoadMoreBefore,
|
onLoadMoreBefore,
|
||||||
@ -84,6 +86,7 @@ export function CardTable({
|
|||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
onNewOrder={onNewOrder}
|
onNewOrder={onNewOrder}
|
||||||
|
onQR={onQR}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
rowSelectionHandler={rowSelectionHandler}
|
rowSelectionHandler={rowSelectionHandler}
|
||||||
onLoadMoreAfter={onLoadMoreAfter}
|
onLoadMoreAfter={onLoadMoreAfter}
|
||||||
@ -105,6 +108,7 @@ interface TableProps {
|
|||||||
instances: Entity[];
|
instances: Entity[];
|
||||||
onDelete: (e: Entity) => void;
|
onDelete: (e: Entity) => void;
|
||||||
onNewOrder: (e: Entity) => void;
|
onNewOrder: (e: Entity) => void;
|
||||||
|
onQR: (e: Entity) => void;
|
||||||
onSelect: (e: Entity) => void;
|
onSelect: (e: Entity) => void;
|
||||||
rowSelectionHandler: StateUpdater<string[]>;
|
rowSelectionHandler: StateUpdater<string[]>;
|
||||||
onLoadMoreBefore?: () => void;
|
onLoadMoreBefore?: () => void;
|
||||||
@ -123,6 +127,7 @@ function Table({
|
|||||||
onLoadMoreAfter,
|
onLoadMoreAfter,
|
||||||
onDelete,
|
onDelete,
|
||||||
onNewOrder,
|
onNewOrder,
|
||||||
|
onQR,
|
||||||
onSelect,
|
onSelect,
|
||||||
onLoadMoreBefore,
|
onLoadMoreBefore,
|
||||||
hasMoreAfter,
|
hasMoreAfter,
|
||||||
@ -185,6 +190,13 @@ function Table({
|
|||||||
>
|
>
|
||||||
New order
|
New order
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="button is-info is-small has-tooltip-left"
|
||||||
|
data-tooltip={i18n.str`create qr code for the template`}
|
||||||
|
onClick={() => onQR(i)}
|
||||||
|
>
|
||||||
|
QR
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -42,12 +42,14 @@ interface Props {
|
|||||||
onCreate: () => void;
|
onCreate: () => void;
|
||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
onNewOrder: (id: string) => void;
|
onNewOrder: (id: string) => void;
|
||||||
|
onQR: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ListTemplates({
|
export default function ListTemplates({
|
||||||
onUnauthorized,
|
onUnauthorized,
|
||||||
onLoadError,
|
onLoadError,
|
||||||
onCreate,
|
onCreate,
|
||||||
|
onQR,
|
||||||
onSelect,
|
onSelect,
|
||||||
onNewOrder,
|
onNewOrder,
|
||||||
onNotFound,
|
onNotFound,
|
||||||
@ -80,6 +82,9 @@ export default function ListTemplates({
|
|||||||
onNewOrder={(e) => {
|
onNewOrder={(e) => {
|
||||||
onNewOrder(e.template_id);
|
onNewOrder(e.template_id);
|
||||||
}}
|
}}
|
||||||
|
onQR={(e) => {
|
||||||
|
onQR(e.template_id);
|
||||||
|
}}
|
||||||
onDelete={(e: MerchantBackend.Template.TemplateEntry) =>
|
onDelete={(e: MerchantBackend.Template.TemplateEntry) =>
|
||||||
deleteTemplate(e.template_id)
|
deleteTemplate(e.template_id)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
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 { QrPage as TestedComponent } from "./QrPage.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Templates/QR",
|
||||||
|
component: TestedComponent,
|
||||||
|
};
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
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 { buildPayto, classifyTalerUri } from "@gnu-taler/taler-util";
|
||||||
|
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
|
||||||
|
import { QR } from "../../../../components/exception/QR.js";
|
||||||
|
import {
|
||||||
|
FormErrors,
|
||||||
|
FormProvider,
|
||||||
|
} from "../../../../components/form/FormProvider.js";
|
||||||
|
import { Input } from "../../../../components/form/Input.js";
|
||||||
|
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
|
||||||
|
import { useBackendContext } from "../../../../context/backend.js";
|
||||||
|
import { useConfigContext } from "../../../../context/config.js";
|
||||||
|
import { MerchantBackend } from "../../../../declaration.js";
|
||||||
|
|
||||||
|
type Entity = MerchantBackend.Template.UsingTemplateDetails;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
template: MerchantBackend.Template.TemplateDetails;
|
||||||
|
id: string;
|
||||||
|
onBack?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QrPage({ template, id: templateId, onBack }: Props): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
const { url: backendUrl } = useBackendContext();
|
||||||
|
const config = useConfigContext();
|
||||||
|
|
||||||
|
const [state, setState] = useState<Partial<Entity>>({
|
||||||
|
amount: template.template_contract.amount,
|
||||||
|
summary: template.template_contract.summary,
|
||||||
|
});
|
||||||
|
|
||||||
|
const errors: FormErrors<Entity> = {};
|
||||||
|
|
||||||
|
const hasErrors = Object.keys(errors).some(
|
||||||
|
(k) => (errors as any)[k] !== undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fixedAmount = !!template.template_contract.amount;
|
||||||
|
const fixedSummary = !!template.template_contract.summary;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (!fixedAmount) {
|
||||||
|
if (state.amount) {
|
||||||
|
params.append("amount", state.amount);
|
||||||
|
} else {
|
||||||
|
params.append("amount", config.currency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fixedSummary) {
|
||||||
|
params.append("summary", state.summary ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsStr = fixedAmount && fixedSummary ? "" : "?" + params.toString();
|
||||||
|
const merchantURL = new URL(backendUrl);
|
||||||
|
|
||||||
|
const talerProto =
|
||||||
|
merchantURL.protocol === "http:" ? "taler+http:" : "taler:";
|
||||||
|
|
||||||
|
const payTemplateUri = `${talerProto}//pay-template/${merchantURL.hostname}/${templateId}${paramsStr}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<section class="section is-main-section">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column" />
|
||||||
|
<div class="column is-four-fifths">
|
||||||
|
<FormProvider
|
||||||
|
object={state}
|
||||||
|
valueHandler={setState}
|
||||||
|
errors={errors}
|
||||||
|
>
|
||||||
|
<InputCurrency<Entity>
|
||||||
|
name="amount"
|
||||||
|
label={i18n.str`Amount`}
|
||||||
|
readonly={fixedAmount}
|
||||||
|
tooltip={i18n.str`Amount of the order`}
|
||||||
|
/>
|
||||||
|
<Input<Entity>
|
||||||
|
name="summary"
|
||||||
|
inputType="multiline"
|
||||||
|
readonly={fixedSummary}
|
||||||
|
label={i18n.str`Order summary`}
|
||||||
|
tooltip={i18n.str`Title of the order to be shown to the customer`}
|
||||||
|
/>
|
||||||
|
</FormProvider>
|
||||||
|
|
||||||
|
<div class="buttons is-right mt-5">
|
||||||
|
{onBack && (
|
||||||
|
<button class="button" onClick={onBack}>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button class="button is-info" onClick={onBack}>
|
||||||
|
<i18n.Translate>Print</i18n.Translate>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<pre>
|
||||||
|
<a href={payTemplateUri}>{payTemplateUri}</a>
|
||||||
|
</pre>
|
||||||
|
<QR text={payTemplateUri} />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
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 {
|
||||||
|
HttpError,
|
||||||
|
useTranslationContext,
|
||||||
|
} from "@gnu-taler/web-util/lib/index.browser";
|
||||||
|
import { Fragment, h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { Loading } from "../../../../components/exception/loading.js";
|
||||||
|
import { NotificationCard } from "../../../../components/menu/index.js";
|
||||||
|
import { MerchantBackend } from "../../../../declaration.js";
|
||||||
|
import {
|
||||||
|
useTemplateAPI,
|
||||||
|
useTemplateDetails,
|
||||||
|
} from "../../../../hooks/templates.js";
|
||||||
|
import { Notification } from "../../../../utils/types.js";
|
||||||
|
import { QrPage } from "./QrPage.js";
|
||||||
|
|
||||||
|
export type Entity = MerchantBackend.Transfers.TransferInformation;
|
||||||
|
interface Props {
|
||||||
|
onBack?: () => void;
|
||||||
|
onUnauthorized: () => VNode;
|
||||||
|
onNotFound: () => VNode;
|
||||||
|
onLoadError: (e: HttpError<MerchantBackend.ErrorDetail>) => VNode;
|
||||||
|
tid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TemplateQrPage({
|
||||||
|
tid,
|
||||||
|
onBack,
|
||||||
|
onLoadError,
|
||||||
|
onNotFound,
|
||||||
|
onUnauthorized,
|
||||||
|
}: Props): VNode {
|
||||||
|
const { createOrderFromTemplate } = useTemplateAPI();
|
||||||
|
const result = useTemplateDetails(tid);
|
||||||
|
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
if (result.clientError && result.isUnauthorized) return onUnauthorized();
|
||||||
|
if (result.clientError && result.isNotfound) return onNotFound();
|
||||||
|
if (result.loading) return <Loading />;
|
||||||
|
if (!result.ok) return onLoadError(result);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NotificationCard notification={notif} />
|
||||||
|
<QrPage template={result.data} id={tid} onBack={onBack} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -123,13 +123,13 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
|||||||
<Input
|
<Input
|
||||||
name="template_contract.summary"
|
name="template_contract.summary"
|
||||||
inputType="multiline"
|
inputType="multiline"
|
||||||
label={i18n.str`Order summary`}
|
label={i18n.str`Fixed summary`}
|
||||||
tooltip={i18n.str`Title of the order to be shown to the customer`}
|
tooltip={i18n.str`If specified, this template will create order with the same summary`}
|
||||||
/>
|
/>
|
||||||
<InputCurrency
|
<InputCurrency
|
||||||
name="template_contract.amount"
|
name="template_contract.amount"
|
||||||
label={i18n.str`Order price`}
|
label={i18n.str`Fixed price`}
|
||||||
tooltip={i18n.str`total product price added up`}
|
tooltip={i18n.str`If specified, this template will create order with the same price`}
|
||||||
/>
|
/>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
name="template_contract.minimum_age"
|
name="template_contract.minimum_age"
|
||||||
|
Loading…
Reference in New Issue
Block a user