Merge branch 'master' into age-withdraw
This commit is contained in:
commit
ffcb40b464
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "@gnu-taler/merchant-backoffice-ui",
|
"name": "@gnu-taler/merchant-backoffice-ui",
|
||||||
"version": "0.1.0",
|
"version": "0.9.3-dev.27",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -64,7 +64,7 @@ function ApplicationStatusRoutes(): VNode {
|
|||||||
const result = useBackendConfig();
|
const result = useBackendConfig();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const { currency, version } = result.ok
|
const { currency, version } = result.ok && result.data
|
||||||
? result.data
|
? result.data
|
||||||
: { currency: "unknown", version: "unknown" };
|
: { currency: "unknown", version: "unknown" };
|
||||||
const ctx = useMemo(() => ({ currency, version }), [currency, version]);
|
const ctx = useMemo(() => ({ currency, version }), [currency, version]);
|
||||||
@ -151,7 +151,7 @@ function ApplicationStatusRoutes(): VNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SUPPORTED_VERSION = "5:0:1"
|
const SUPPORTED_VERSION = "5:0:1"
|
||||||
if (!LibtoolVersion.compare(
|
if (result.data && !LibtoolVersion.compare(
|
||||||
SUPPORTED_VERSION,
|
SUPPORTED_VERSION,
|
||||||
result.data.version,
|
result.data.version,
|
||||||
)?.compatible) {
|
)?.compatible) {
|
||||||
|
@ -80,7 +80,7 @@ import { dateFormatForSettings, useSettings } from "./hooks/useSettings.js";
|
|||||||
|
|
||||||
export enum InstancePaths {
|
export enum InstancePaths {
|
||||||
error = "/error",
|
error = "/error",
|
||||||
server = "/server",
|
settings = "/settings",
|
||||||
token = "/token",
|
token = "/token",
|
||||||
|
|
||||||
bank_list = "/bank",
|
bank_list = "/bank",
|
||||||
@ -118,7 +118,7 @@ export enum InstancePaths {
|
|||||||
validators_update = "/validators/:vid/update",
|
validators_update = "/validators/:vid/update",
|
||||||
validators_new = "/validators/new",
|
validators_new = "/validators/new",
|
||||||
|
|
||||||
settings = "/interface",
|
interface = "/interface",
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
@ -255,7 +255,7 @@ export function InstanceRoutes({
|
|||||||
instance={id}
|
instance={id}
|
||||||
admin={admin}
|
admin={admin}
|
||||||
onShowSettings={() => {
|
onShowSettings={() => {
|
||||||
route(InstancePaths.settings)
|
route(InstancePaths.interface)
|
||||||
}}
|
}}
|
||||||
path={path}
|
path={path}
|
||||||
onLogout={clearTokenAndGoToRoot}
|
onLogout={clearTokenAndGoToRoot}
|
||||||
@ -320,7 +320,7 @@ export function InstanceRoutes({
|
|||||||
* Update instance page
|
* Update instance page
|
||||||
*/}
|
*/}
|
||||||
<Route
|
<Route
|
||||||
path={InstancePaths.server}
|
path={InstancePaths.settings}
|
||||||
component={InstanceUpdatePage}
|
component={InstanceUpdatePage}
|
||||||
onBack={() => {
|
onBack={() => {
|
||||||
route(`/`);
|
route(`/`);
|
||||||
@ -353,7 +353,7 @@ export function InstanceRoutes({
|
|||||||
path={InstancePaths.inventory_list}
|
path={InstancePaths.inventory_list}
|
||||||
component={ProductListPage}
|
component={ProductListPage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
|
||||||
onCreate={() => {
|
onCreate={() => {
|
||||||
route(InstancePaths.inventory_new);
|
route(InstancePaths.inventory_new);
|
||||||
}}
|
}}
|
||||||
@ -392,7 +392,7 @@ export function InstanceRoutes({
|
|||||||
path={InstancePaths.bank_list}
|
path={InstancePaths.bank_list}
|
||||||
component={BankAccountListPage}
|
component={BankAccountListPage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
|
||||||
onCreate={() => {
|
onCreate={() => {
|
||||||
route(InstancePaths.bank_new);
|
route(InstancePaths.bank_new);
|
||||||
}}
|
}}
|
||||||
@ -437,7 +437,7 @@ export function InstanceRoutes({
|
|||||||
route(InstancePaths.order_details.replace(":oid", id));
|
route(InstancePaths.order_details.replace(":oid", id));
|
||||||
}}
|
}}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
|
||||||
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
@ -468,7 +468,7 @@ export function InstanceRoutes({
|
|||||||
component={TransferListPage}
|
component={TransferListPage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
|
||||||
onCreate={() => {
|
onCreate={() => {
|
||||||
route(InstancePaths.transfers_new);
|
route(InstancePaths.transfers_new);
|
||||||
}}
|
}}
|
||||||
@ -491,7 +491,7 @@ export function InstanceRoutes({
|
|||||||
component={WebhookListPage}
|
component={WebhookListPage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
|
||||||
onCreate={() => {
|
onCreate={() => {
|
||||||
route(InstancePaths.webhooks_new);
|
route(InstancePaths.webhooks_new);
|
||||||
}}
|
}}
|
||||||
@ -530,7 +530,7 @@ export function InstanceRoutes({
|
|||||||
component={ValidatorListPage}
|
component={ValidatorListPage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
|
||||||
onCreate={() => {
|
onCreate={() => {
|
||||||
route(InstancePaths.validators_new);
|
route(InstancePaths.validators_new);
|
||||||
}}
|
}}
|
||||||
@ -569,7 +569,7 @@ export function InstanceRoutes({
|
|||||||
component={TemplateListPage}
|
component={TemplateListPage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
|
||||||
onCreate={() => {
|
onCreate={() => {
|
||||||
route(InstancePaths.templates_new);
|
route(InstancePaths.templates_new);
|
||||||
}}
|
}}
|
||||||
@ -638,7 +638,7 @@ export function InstanceRoutes({
|
|||||||
component={ReservesListPage}
|
component={ReservesListPage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.settings)}
|
||||||
onSelect={(id: string) => {
|
onSelect={(id: string) => {
|
||||||
route(InstancePaths.reserves_details.replace(":rid", id));
|
route(InstancePaths.reserves_details.replace(":rid", id));
|
||||||
}}
|
}}
|
||||||
|
@ -107,8 +107,9 @@ export function InputWithAddon<T>({
|
|||||||
{error && <p class="help is-danger">{error}</p>}
|
{error && <p class="help is-danger">{error}</p>}
|
||||||
<span class="has-text-grey">{help}</span>
|
<span class="has-text-grey">{help}</span>
|
||||||
</div>
|
</div>
|
||||||
{side}
|
{expand ? <div>{side}</div> : side}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
import { TranslatedString } from "@gnu-taler/taler-util";
|
||||||
|
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
|
||||||
|
export function JumpToElementById({ testIfExist, onSelect, palceholder, description }: { palceholder: TranslatedString, description: TranslatedString, testIfExist: (id: string) => Promise<any>, onSelect: (id: string) => void }): VNode {
|
||||||
|
const { i18n } = useTranslationContext()
|
||||||
|
|
||||||
|
const [error, setError] = useState<string | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [id, setId] = useState<string>()
|
||||||
|
async function check(currentId: string | undefined): Promise<void> {
|
||||||
|
if (!currentId) {
|
||||||
|
setError(i18n.str`missing id`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await testIfExist(currentId);
|
||||||
|
onSelect(currentId);
|
||||||
|
setError(undefined);
|
||||||
|
} catch {
|
||||||
|
setError(i18n.str`not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
class={error ? "input is-danger" : "input"}
|
||||||
|
type="text"
|
||||||
|
value={id ?? ""}
|
||||||
|
onChange={(e) => setId(e.currentTarget.value)}
|
||||||
|
placeholder={palceholder}
|
||||||
|
/>
|
||||||
|
{error && <p class="help is-danger">{error}</p>}
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="has-tooltip-bottom"
|
||||||
|
data-tooltip={description}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
onClick={(e) => check(id)}
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<i class="mdi mdi-arrow-right" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
@ -177,12 +177,12 @@ export function Sidebar({
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={"/server"} class="has-icon">
|
<a href={"/settings"} class="has-icon">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="mdi mdi-square-edit-outline" />
|
<i class="mdi mdi-square-edit-outline" />
|
||||||
</span>
|
</span>
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<i18n.Translate>Server</i18n.Translate>
|
<i18n.Translate>Settings</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -24,7 +24,7 @@ import { Sidebar } from "./SideBar.js";
|
|||||||
|
|
||||||
function getInstanceTitle(path: string, id: string): string {
|
function getInstanceTitle(path: string, id: string): string {
|
||||||
switch (path) {
|
switch (path) {
|
||||||
case InstancePaths.server:
|
case InstancePaths.settings:
|
||||||
return `${id}: Settings`;
|
return `${id}: Settings`;
|
||||||
case InstancePaths.order_list:
|
case InstancePaths.order_list:
|
||||||
return `${id}: Orders`;
|
return `${id}: Orders`;
|
||||||
@ -64,9 +64,7 @@ function getInstanceTitle(path: string, id: string): string {
|
|||||||
return `${id}: Templates`;
|
return `${id}: Templates`;
|
||||||
case InstancePaths.templates_use:
|
case InstancePaths.templates_use:
|
||||||
return `${id}: Use template`;
|
return `${id}: Use template`;
|
||||||
case InstancePaths.settings:
|
case InstancePaths.interface:
|
||||||
return `${id}: Interface`;
|
|
||||||
case InstancePaths.settings:
|
|
||||||
return `${id}: Interface`;
|
return `${id}: Interface`;
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
|
@ -91,7 +91,7 @@ const CHECK_CONFIG_INTERVAL_OK = 5 * 60 * 1000;
|
|||||||
const CHECK_CONFIG_INTERVAL_FAIL = 2 * 1000;
|
const CHECK_CONFIG_INTERVAL_FAIL = 2 * 1000;
|
||||||
|
|
||||||
export function useBackendConfig(): HttpResponse<
|
export function useBackendConfig(): HttpResponse<
|
||||||
MerchantBackend.VersionResponse,
|
MerchantBackend.VersionResponse | undefined,
|
||||||
RequestError<MerchantBackend.ErrorDetail>
|
RequestError<MerchantBackend.ErrorDetail>
|
||||||
> {
|
> {
|
||||||
const { request } = useBackendBaseRequest();
|
const { request } = useBackendBaseRequest();
|
||||||
@ -340,6 +340,14 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType {
|
|||||||
if (refunded !== undefined) params.refunded = refunded;
|
if (refunded !== undefined) params.refunded = refunded;
|
||||||
if (wired !== undefined) params.wired = wired;
|
if (wired !== undefined) params.wired = wired;
|
||||||
if (date_s !== undefined) params.date_s = date_s;
|
if (date_s !== undefined) params.date_s = date_s;
|
||||||
|
if (delta === 0) {
|
||||||
|
//in this case we can already assume the response
|
||||||
|
//and avoid network
|
||||||
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
data: {orders:[]} as T,
|
||||||
|
})
|
||||||
|
}
|
||||||
return requestHandler<T>(baseUrl, endpoint, { params, token });
|
return requestHandler<T>(baseUrl, endpoint, { params, token });
|
||||||
},
|
},
|
||||||
[baseUrl, token],
|
[baseUrl, token],
|
||||||
@ -385,6 +393,14 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType {
|
|||||||
const params: any = {};
|
const params: any = {};
|
||||||
if (payto_uri !== undefined) params.payto_uri = payto_uri;
|
if (payto_uri !== undefined) params.payto_uri = payto_uri;
|
||||||
if (verified !== undefined) params.verified = verified;
|
if (verified !== undefined) params.verified = verified;
|
||||||
|
if (delta === 0) {
|
||||||
|
//in this case we can already assume the response
|
||||||
|
//and avoid network
|
||||||
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
data: {transfers:[]} as T,
|
||||||
|
})
|
||||||
|
}
|
||||||
if (delta !== undefined) {
|
if (delta !== undefined) {
|
||||||
params.limit = delta;
|
params.limit = delta;
|
||||||
}
|
}
|
||||||
@ -403,6 +419,14 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType {
|
|||||||
): Promise<HttpResponseOk<T>> {
|
): Promise<HttpResponseOk<T>> {
|
||||||
const [endpoint, position, delta] = args
|
const [endpoint, position, delta] = args
|
||||||
const params: any = {};
|
const params: any = {};
|
||||||
|
if (delta === 0) {
|
||||||
|
//in this case we can already assume the response
|
||||||
|
//and avoid network
|
||||||
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
data: {templates:[]} as T,
|
||||||
|
})
|
||||||
|
}
|
||||||
if (delta !== undefined) {
|
if (delta !== undefined) {
|
||||||
params.limit = delta;
|
params.limit = delta;
|
||||||
}
|
}
|
||||||
@ -421,6 +445,14 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType {
|
|||||||
): Promise<HttpResponseOk<T>> {
|
): Promise<HttpResponseOk<T>> {
|
||||||
const [endpoint, position, delta] = args
|
const [endpoint, position, delta] = args
|
||||||
const params: any = {};
|
const params: any = {};
|
||||||
|
if (delta === 0) {
|
||||||
|
//in this case we can already assume the response
|
||||||
|
//and avoid network
|
||||||
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
data: {webhooks:[]} as T,
|
||||||
|
})
|
||||||
|
}
|
||||||
if (delta !== undefined) {
|
if (delta !== undefined) {
|
||||||
params.limit = delta;
|
params.limit = delta;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,13 @@ const calculateRootPath = () => {
|
|||||||
? window.location.origin + window.location.pathname
|
? window.location.origin + window.location.pathname
|
||||||
: "/";
|
: "/";
|
||||||
|
|
||||||
return rootPath.replace("webui/","");
|
/**
|
||||||
|
* By default, merchant backend serves the html content
|
||||||
|
* from the /webui root. This should cover most of the
|
||||||
|
* cases and the rootPath will be the merchant backend
|
||||||
|
* URL where the instances are
|
||||||
|
*/
|
||||||
|
return rootPath.replace("/webui/", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const loginTokenCodec = buildCodecForObject<LoginToken>()
|
const loginTokenCodec = buildCodecForObject<LoginToken>()
|
||||||
|
@ -82,10 +82,19 @@ export function useTemplateAPI(): TemplateAPI {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const testTemplateExist = async (
|
||||||
|
templateId: string,
|
||||||
|
): Promise<HttpResponseOk<void>> => {
|
||||||
|
const res = await request<void>(`/private/templates/${templateId}`, { method: "GET", });
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createTemplate,
|
createTemplate,
|
||||||
updateTemplate,
|
updateTemplate,
|
||||||
deleteTemplate,
|
deleteTemplate,
|
||||||
|
testTemplateExist,
|
||||||
createOrderFromTemplate,
|
createOrderFromTemplate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -98,6 +107,9 @@ export interface TemplateAPI {
|
|||||||
id: string,
|
id: string,
|
||||||
data: MerchantBackend.Template.TemplatePatchDetails,
|
data: MerchantBackend.Template.TemplatePatchDetails,
|
||||||
) => Promise<HttpResponseOk<void>>;
|
) => Promise<HttpResponseOk<void>>;
|
||||||
|
testTemplateExist: (
|
||||||
|
id: string
|
||||||
|
) => Promise<HttpResponseOk<void>>;
|
||||||
deleteTemplate: (id: string) => Promise<HttpResponseOk<void>>;
|
deleteTemplate: (id: string) => Promise<HttpResponseOk<void>>;
|
||||||
createOrderFromTemplate: (
|
createOrderFromTemplate: (
|
||||||
id: string,
|
id: string,
|
||||||
@ -119,11 +131,11 @@ export function useInstanceTemplates(
|
|||||||
> {
|
> {
|
||||||
const { templateFetcher } = useBackendInstanceRequest();
|
const { templateFetcher } = useBackendInstanceRequest();
|
||||||
|
|
||||||
// const [pageBefore, setPageBefore] = useState(1);
|
const [pageBefore, setPageBefore] = useState(1);
|
||||||
const [pageAfter, setPageAfter] = useState(1);
|
const [pageAfter, setPageAfter] = useState(1);
|
||||||
|
|
||||||
const totalAfter = pageAfter * PAGE_SIZE;
|
const totalAfter = pageAfter * PAGE_SIZE;
|
||||||
// const totalBefore = args?.position !== undefined ? pageBefore * PAGE_SIZE : 0;
|
const totalBefore = args?.position ? pageBefore * PAGE_SIZE : 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME: this can be cleaned up a little
|
* FIXME: this can be cleaned up a little
|
||||||
@ -131,20 +143,20 @@ export function useInstanceTemplates(
|
|||||||
* the logic of double query should be inside the orderFetch so from the hook perspective and cache
|
* the logic of double query should be inside the orderFetch so from the hook perspective and cache
|
||||||
* is just one query and one error status
|
* is just one query and one error status
|
||||||
*/
|
*/
|
||||||
// const {
|
const {
|
||||||
// data: beforeData,
|
data: beforeData,
|
||||||
// error: beforeError,
|
error: beforeError,
|
||||||
// isValidating: loadingBefore,
|
isValidating: loadingBefore,
|
||||||
// } = useSWR<HttpResponseOk<MerchantBackend.Template.TemplateSummaryResponse>, HttpError>(
|
} = useSWR<
|
||||||
// [
|
HttpResponseOk<MerchantBackend.Template.TemplateSummaryResponse>,
|
||||||
// `/private/templates`,
|
RequestError<MerchantBackend.ErrorDetail>>(
|
||||||
// token,
|
[
|
||||||
// url,
|
`/private/templates`,
|
||||||
// args?.position,
|
args?.position,
|
||||||
// totalBefore,
|
totalBefore,
|
||||||
// ],
|
],
|
||||||
// templateFetcher,
|
templateFetcher,
|
||||||
// );
|
);
|
||||||
const {
|
const {
|
||||||
data: afterData,
|
data: afterData,
|
||||||
error: afterError,
|
error: afterError,
|
||||||
@ -155,9 +167,13 @@ export function useInstanceTemplates(
|
|||||||
>([`/private/templates`, args?.position, -totalAfter], templateFetcher);
|
>([`/private/templates`, args?.position, -totalAfter], templateFetcher);
|
||||||
|
|
||||||
//this will save last result
|
//this will save last result
|
||||||
// const [lastBefore, setLastBefore] = useState<
|
const [lastBefore, setLastBefore] = useState<
|
||||||
// HttpResponse<MerchantBackend.Template.TemplateSummaryResponse, MerchantBackend.ErrorDetail>
|
HttpResponse<
|
||||||
// >({ loading: true });
|
MerchantBackend.Template.TemplateSummaryResponse,
|
||||||
|
MerchantBackend.ErrorDetail
|
||||||
|
>
|
||||||
|
>({ loading: true });
|
||||||
|
|
||||||
const [lastAfter, setLastAfter] = useState<
|
const [lastAfter, setLastAfter] = useState<
|
||||||
HttpResponse<
|
HttpResponse<
|
||||||
MerchantBackend.Template.TemplateSummaryResponse,
|
MerchantBackend.Template.TemplateSummaryResponse,
|
||||||
@ -166,19 +182,18 @@ export function useInstanceTemplates(
|
|||||||
>({ loading: true });
|
>({ loading: true });
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (afterData) setLastAfter(afterData);
|
if (afterData) setLastAfter(afterData);
|
||||||
// if (beforeData) setLastBefore(beforeData);
|
if (beforeData) setLastBefore(beforeData);
|
||||||
}, [afterData /*, beforeData*/]);
|
}, [afterData, beforeData]);
|
||||||
|
|
||||||
// if (beforeError) return beforeError;
|
if (beforeError) return beforeError.cause;
|
||||||
if (afterError) return afterError.cause;
|
if (afterError) return afterError.cause;
|
||||||
|
|
||||||
// if the query returns less that we ask, then we have reach the end or beginning
|
// if the query returns less that we ask, then we have reach the end or beginning
|
||||||
const isReachingEnd =
|
const isReachingEnd =
|
||||||
afterData && afterData.data.templates.length < totalAfter;
|
afterData && afterData.data.templates.length < totalAfter;
|
||||||
const isReachingStart = false;
|
const isReachingStart = args?.position === undefined
|
||||||
// args?.position === undefined
|
||
|
||||||
// ||
|
(beforeData && beforeData.data.templates.length < totalBefore);
|
||||||
// (beforeData && beforeData.data.templates.length < totalBefore);
|
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
isReachingEnd,
|
isReachingEnd,
|
||||||
@ -188,37 +203,36 @@ export function useInstanceTemplates(
|
|||||||
if (afterData.data.templates.length < MAX_RESULT_SIZE) {
|
if (afterData.data.templates.length < MAX_RESULT_SIZE) {
|
||||||
setPageAfter(pageAfter + 1);
|
setPageAfter(pageAfter + 1);
|
||||||
} else {
|
} else {
|
||||||
const from = `${
|
const from = `${afterData.data.templates[afterData.data.templates.length - 1]
|
||||||
afterData.data.templates[afterData.data.templates.length - 1]
|
|
||||||
.template_id
|
.template_id
|
||||||
}`;
|
}`;
|
||||||
if (from && updatePosition) updatePosition(from);
|
if (from && updatePosition) updatePosition(from);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loadMorePrev: () => {
|
loadMorePrev: () => {
|
||||||
// if (!beforeData || isReachingStart) return;
|
if (!beforeData || isReachingStart) return;
|
||||||
// if (beforeData.data.templates.length < MAX_RESULT_SIZE) {
|
if (beforeData.data.templates.length < MAX_RESULT_SIZE) {
|
||||||
// setPageBefore(pageBefore + 1);
|
setPageBefore(pageBefore + 1);
|
||||||
// } else if (beforeData) {
|
} else if (beforeData) {
|
||||||
// const from = `${beforeData.data.templates[beforeData.data.templates.length - 1]
|
const from = `${beforeData.data.templates[beforeData.data.templates.length - 1]
|
||||||
// .template_id
|
.template_id
|
||||||
// }`;
|
}`;
|
||||||
// if (from && updatePosition) updatePosition(from);
|
if (from && updatePosition) updatePosition(from);
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const templates = !afterData ? [] : (afterData || lastAfter).data.templates;
|
// const templates = !afterData ? [] : (afterData || lastAfter).data.templates;
|
||||||
// const templates =
|
const templates =
|
||||||
// !beforeData || !afterData
|
!beforeData || !afterData
|
||||||
// ? []
|
? []
|
||||||
// : (beforeData || lastBefore).data.templates
|
: (beforeData || lastBefore).data.templates
|
||||||
// .slice()
|
.slice()
|
||||||
// .reverse()
|
.reverse()
|
||||||
// .concat((afterData || lastAfter).data.templates);
|
.concat((afterData || lastAfter).data.templates);
|
||||||
if (loadingAfter /* || loadingBefore */)
|
if (loadingAfter || loadingBefore)
|
||||||
return { loading: true, data: { templates } };
|
return { loading: true, data: { templates } };
|
||||||
if (/*beforeData &&*/ afterData) {
|
if (beforeData && afterData) {
|
||||||
return { ok: true, data: { templates }, ...pagination };
|
return { ok: true, data: { templates }, ...pagination };
|
||||||
}
|
}
|
||||||
return { loading: true };
|
return { loading: true };
|
||||||
|
@ -93,9 +93,11 @@ export function useInstanceWebhooks(
|
|||||||
> {
|
> {
|
||||||
const { webhookFetcher } = useBackendInstanceRequest();
|
const { webhookFetcher } = useBackendInstanceRequest();
|
||||||
|
|
||||||
|
const [pageBefore, setPageBefore] = useState(1);
|
||||||
const [pageAfter, setPageAfter] = useState(1);
|
const [pageAfter, setPageAfter] = useState(1);
|
||||||
|
|
||||||
const totalAfter = pageAfter * PAGE_SIZE;
|
const totalAfter = pageAfter * PAGE_SIZE;
|
||||||
|
const totalBefore = args?.position ? pageBefore * PAGE_SIZE : 0;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: afterData,
|
data: afterData,
|
||||||
@ -120,7 +122,7 @@ export function useInstanceWebhooks(
|
|||||||
|
|
||||||
const isReachingEnd =
|
const isReachingEnd =
|
||||||
afterData && afterData.data.webhooks.length < totalAfter;
|
afterData && afterData.data.webhooks.length < totalAfter;
|
||||||
const isReachingStart = false;
|
const isReachingStart = true;
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
isReachingEnd,
|
isReachingEnd,
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode, Fragment } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { DatePicker } from "../../../../components/picker/DatePicker.js";
|
import { DatePicker } from "../../../../components/picker/DatePicker.js";
|
||||||
import { MerchantBackend, WithId } from "../../../../declaration.js";
|
import { MerchantBackend, WithId } from "../../../../declaration.js";
|
||||||
@ -29,8 +29,6 @@ import { CardTable } from "./Table.js";
|
|||||||
import { dateFormatForSettings, useSettings } from "../../../../hooks/useSettings.js";
|
import { dateFormatForSettings, useSettings } from "../../../../hooks/useSettings.js";
|
||||||
|
|
||||||
export interface ListPageProps {
|
export interface ListPageProps {
|
||||||
errorOrderId: string | undefined;
|
|
||||||
|
|
||||||
onShowAll: () => void;
|
onShowAll: () => void;
|
||||||
onShowNotPaid: () => void;
|
onShowNotPaid: () => void;
|
||||||
onShowPaid: () => void;
|
onShowPaid: () => void;
|
||||||
@ -56,17 +54,18 @@ export interface ListPageProps {
|
|||||||
|
|
||||||
onSelectOrder: (o: MerchantBackend.Orders.OrderHistoryEntry & WithId) => void;
|
onSelectOrder: (o: MerchantBackend.Orders.OrderHistoryEntry & WithId) => void;
|
||||||
onRefundOrder: (o: MerchantBackend.Orders.OrderHistoryEntry & WithId) => void;
|
onRefundOrder: (o: MerchantBackend.Orders.OrderHistoryEntry & WithId) => void;
|
||||||
onSearchOrderById: (id: string) => void;
|
|
||||||
onCreate: () => void;
|
onCreate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListPage({
|
export function ListPage({
|
||||||
|
hasMoreAfter,
|
||||||
|
hasMoreBefore,
|
||||||
|
onLoadMoreAfter,
|
||||||
|
onLoadMoreBefore,
|
||||||
orders,
|
orders,
|
||||||
errorOrderId,
|
|
||||||
isAllActive,
|
isAllActive,
|
||||||
onSelectOrder,
|
onSelectOrder,
|
||||||
onRefundOrder,
|
onRefundOrder,
|
||||||
onSearchOrderById,
|
|
||||||
jumpToDate,
|
jumpToDate,
|
||||||
onCopyURL,
|
onCopyURL,
|
||||||
onShowAll,
|
onShowAll,
|
||||||
@ -86,42 +85,10 @@ export function ListPage({
|
|||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const dateTooltip = i18n.str`select date to show nearby orders`;
|
const dateTooltip = i18n.str`select date to show nearby orders`;
|
||||||
const [pickDate, setPickDate] = useState(false);
|
const [pickDate, setPickDate] = useState(false);
|
||||||
const [orderId, setOrderId] = useState<string>("");
|
|
||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section class="section is-main-section">
|
<Fragment>
|
||||||
<div class="level">
|
|
||||||
<div class="level-left">
|
|
||||||
<div class="level-item">
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control">
|
|
||||||
<input
|
|
||||||
class={errorOrderId ? "input is-danger" : "input"}
|
|
||||||
type="text"
|
|
||||||
value={orderId}
|
|
||||||
onChange={(e) => setOrderId(e.currentTarget.value)}
|
|
||||||
placeholder={i18n.str`order id`}
|
|
||||||
/>
|
|
||||||
{errorOrderId && <p class="help is-danger">{errorOrderId}</p>}
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="has-tooltip-bottom"
|
|
||||||
data-tooltip={i18n.str`jump to order with the given order ID`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="button"
|
|
||||||
onClick={(e) => onSearchOrderById(orderId)}
|
|
||||||
>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="mdi mdi-arrow-right" />
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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" }}>
|
||||||
@ -249,7 +216,11 @@ export function ListPage({
|
|||||||
onCopyURL={onCopyURL}
|
onCopyURL={onCopyURL}
|
||||||
onSelect={onSelectOrder}
|
onSelect={onSelectOrder}
|
||||||
onRefund={onRefundOrder}
|
onRefund={onRefundOrder}
|
||||||
|
hasMoreAfter={hasMoreAfter}
|
||||||
|
hasMoreBefore={hasMoreBefore}
|
||||||
|
onLoadMoreAfter={onLoadMoreAfter}
|
||||||
|
onLoadMoreBefore={onLoadMoreBefore}
|
||||||
/>
|
/>
|
||||||
</section>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -140,10 +140,9 @@ function Table({
|
|||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
return (
|
return (
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
{onLoadMoreBefore && (
|
{hasMoreBefore && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
disabled={!hasMoreBefore}
|
|
||||||
onClick={onLoadMoreBefore}
|
onClick={onLoadMoreBefore}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load newer orders</i18n.Translate>
|
<i18n.Translate>load newer orders</i18n.Translate>
|
||||||
@ -218,10 +217,9 @@ function Table({
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{onLoadMoreAfter && (
|
{hasMoreAfter && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
disabled={!hasMoreAfter}
|
|
||||||
onClick={onLoadMoreAfter}
|
onClick={onLoadMoreAfter}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load older orders</i18n.Translate>
|
<i18n.Translate>load older orders</i18n.Translate>
|
||||||
|
@ -39,6 +39,7 @@ import { Notification } from "../../../../utils/types.js";
|
|||||||
import { ListPage } from "./ListPage.js";
|
import { ListPage } from "./ListPage.js";
|
||||||
import { RefundModal } from "./Table.js";
|
import { RefundModal } from "./Table.js";
|
||||||
import { HttpStatusCode } from "@gnu-taler/taler-util";
|
import { HttpStatusCode } from "@gnu-taler/taler-util";
|
||||||
|
import { JumpToElementById } from "../../../../components/form/JumpToElementById.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onUnauthorized: () => VNode;
|
onUnauthorized: () => VNode;
|
||||||
@ -69,9 +70,6 @@ export default function OrderList({
|
|||||||
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
||||||
|
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const [errorOrderId, setErrorOrderId] = useState<string | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.loading) return <Loading />;
|
if (result.loading) return <Loading />;
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
@ -100,24 +98,17 @@ export default function OrderList({
|
|||||||
? "is-active"
|
? "is-active"
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
async function testIfOrderExistAndSelect(orderId: string): Promise<void> {
|
|
||||||
if (!orderId) {
|
|
||||||
setErrorOrderId(i18n.str`Enter an order id`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await getPaymentURL(orderId);
|
|
||||||
onSelect(orderId);
|
|
||||||
setErrorOrderId(undefined);
|
|
||||||
} catch {
|
|
||||||
setErrorOrderId(i18n.str`order not found`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<section class="section is-main-section">
|
||||||
<NotificationCard notification={notif} />
|
<NotificationCard notification={notif} />
|
||||||
|
|
||||||
|
<JumpToElementById
|
||||||
|
testIfExist={getPaymentURL}
|
||||||
|
onSelect={onSelect}
|
||||||
|
description={i18n.str`jump to order with the given product ID`}
|
||||||
|
palceholder={i18n.str`order id`}
|
||||||
|
/>
|
||||||
|
|
||||||
<ListPage
|
<ListPage
|
||||||
orders={result.data.orders.map((o) => ({ ...o, id: o.order_id }))}
|
orders={result.data.orders.map((o) => ({ ...o, id: o.order_id }))}
|
||||||
onLoadMoreBefore={result.loadMorePrev}
|
onLoadMoreBefore={result.loadMorePrev}
|
||||||
@ -126,7 +117,6 @@ export default function OrderList({
|
|||||||
hasMoreAfter={!result.isReachingEnd}
|
hasMoreAfter={!result.isReachingEnd}
|
||||||
onSelectOrder={(order) => onSelect(order.id)}
|
onSelectOrder={(order) => onSelect(order.id)}
|
||||||
onRefundOrder={(value) => setOrderToBeRefunded(value)}
|
onRefundOrder={(value) => setOrderToBeRefunded(value)}
|
||||||
errorOrderId={errorOrderId}
|
|
||||||
isAllActive={isAllActive}
|
isAllActive={isAllActive}
|
||||||
isNotWiredActive={isNotWiredActive}
|
isNotWiredActive={isNotWiredActive}
|
||||||
isWiredActive={isWiredActive}
|
isWiredActive={isWiredActive}
|
||||||
@ -138,7 +128,6 @@ export default function OrderList({
|
|||||||
getPaymentURL(id).then((resp) => copyToClipboard(resp.data))
|
getPaymentURL(id).then((resp) => copyToClipboard(resp.data))
|
||||||
}
|
}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
onSearchOrderById={testIfOrderExistAndSelect}
|
|
||||||
onSelectDate={setNewDate}
|
onSelectDate={setNewDate}
|
||||||
onShowAll={() => setFilter({})}
|
onShowAll={() => setFilter({})}
|
||||||
onShowNotPaid={() => setFilter({ paid: "no" })}
|
onShowNotPaid={() => setFilter({ paid: "no" })}
|
||||||
@ -190,7 +179,7 @@ export default function OrderList({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +244,10 @@ function Table({
|
|||||||
}
|
}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
|
<span style={{"whiteSpace":"nowrap"}}>
|
||||||
|
|
||||||
{i.total_sold} {i.unit}
|
{i.total_sold} {i.unit}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="is-actions-cell right-sticky">
|
<td class="is-actions-cell right-sticky">
|
||||||
<div class="buttons is-right">
|
<div class="buttons is-right">
|
||||||
@ -341,7 +344,7 @@ function FastProductWithInfiniteStockUpdateForm({
|
|||||||
|
|
||||||
<div class="buttons mt-5">
|
<div class="buttons mt-5">
|
||||||
|
|
||||||
<button class="button " onClick={onCancel}>
|
<button class="button mt-5" onClick={onCancel}>
|
||||||
<i18n.Translate>Clone</i18n.Translate>
|
<i18n.Translate>Clone</i18n.Translate>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,6 +37,7 @@ import { Notification } from "../../../../utils/types.js";
|
|||||||
import { CardTable } from "./Table.js";
|
import { CardTable } from "./Table.js";
|
||||||
import { HttpStatusCode } from "@gnu-taler/taler-util";
|
import { HttpStatusCode } from "@gnu-taler/taler-util";
|
||||||
import { ConfirmModal, DeleteModal } from "../../../../components/modal/index.js";
|
import { ConfirmModal, DeleteModal } from "../../../../components/modal/index.js";
|
||||||
|
import { JumpToElementById } from "../../../../components/form/JumpToElementById.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onUnauthorized: () => VNode;
|
onUnauthorized: () => VNode;
|
||||||
@ -74,60 +75,17 @@ export default function ProductList({
|
|||||||
return onNotFound();
|
return onNotFound();
|
||||||
return onLoadError(result);
|
return onLoadError(result);
|
||||||
}
|
}
|
||||||
const [errorId, setErrorId] = useState<string | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [productId, setProductId] = useState<string>()
|
|
||||||
async function testIfProductExistAndSelect(orderId: string | undefined): Promise<void> {
|
|
||||||
if (!orderId) {
|
|
||||||
setErrorId(i18n.str`Enter a product id`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await getProduct(orderId);
|
|
||||||
onSelect(orderId);
|
|
||||||
setErrorId(undefined);
|
|
||||||
} catch {
|
|
||||||
setErrorId(i18n.str`product not found`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section class="section is-main-section">
|
<section class="section is-main-section">
|
||||||
<NotificationCard notification={notif} />
|
<NotificationCard notification={notif} />
|
||||||
|
|
||||||
<div class="level">
|
<JumpToElementById
|
||||||
<div class="level-left">
|
testIfExist={getProduct}
|
||||||
<div class="level-item">
|
onSelect={onSelect}
|
||||||
<div class="field has-addons">
|
description={i18n.str`jump to product with the given product ID`}
|
||||||
<div class="control">
|
palceholder={i18n.str`product id`}
|
||||||
<input
|
|
||||||
class={errorId ? "input is-danger" : "input"}
|
|
||||||
type="text"
|
|
||||||
value={productId ?? ""}
|
|
||||||
onChange={(e) => setProductId(e.currentTarget.value)}
|
|
||||||
placeholder={i18n.str`product id`}
|
|
||||||
/>
|
/>
|
||||||
{errorId && <p class="help is-danger">{errorId}</p>}
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="has-tooltip-bottom"
|
|
||||||
data-tooltip={i18n.str`jump to product with the given product ID`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="button"
|
|
||||||
onClick={(e) => testIfProductExistAndSelect(productId)}
|
|
||||||
>
|
|
||||||
<span class="icon">
|
|
||||||
<i class="mdi mdi-arrow-right" />
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CardTable
|
<CardTable
|
||||||
instances={result.data}
|
instances={result.data}
|
||||||
|
@ -49,7 +49,6 @@ export function ListPage({
|
|||||||
|
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<section class="section is-main-section">
|
|
||||||
<CardTable
|
<CardTable
|
||||||
templates={templates.map((o) => ({
|
templates={templates.map((o) => ({
|
||||||
...o,
|
...o,
|
||||||
@ -65,6 +64,5 @@ export function ListPage({
|
|||||||
onLoadMoreAfter={onLoadMoreAfter}
|
onLoadMoreAfter={onLoadMoreAfter}
|
||||||
hasMoreAfter={!onLoadMoreAfter}
|
hasMoreAfter={!onLoadMoreAfter}
|
||||||
/>
|
/>
|
||||||
</section>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -136,11 +136,10 @@ function Table({
|
|||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
{onLoadMoreBefore && (
|
{hasMoreBefore && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
data-tooltip={i18n.str`load more templates before the first one`}
|
data-tooltip={i18n.str`load more templates before the first one`}
|
||||||
disabled={!hasMoreBefore}
|
|
||||||
onClick={onLoadMoreBefore}
|
onClick={onLoadMoreBefore}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load newer templates</i18n.Translate>
|
<i18n.Translate>load newer templates</i18n.Translate>
|
||||||
@ -188,7 +187,7 @@ function Table({
|
|||||||
data-tooltip={i18n.str`use template to create new order`}
|
data-tooltip={i18n.str`use template to create new order`}
|
||||||
onClick={() => onNewOrder(i)}
|
onClick={() => onNewOrder(i)}
|
||||||
>
|
>
|
||||||
New order
|
Use template
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button is-info is-small has-tooltip-left"
|
class="button is-info is-small has-tooltip-left"
|
||||||
@ -204,11 +203,10 @@ function Table({
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{onLoadMoreAfter && (
|
{hasMoreAfter && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
data-tooltip={i18n.str`load more templates after the last one`}
|
data-tooltip={i18n.str`load more templates after the last one`}
|
||||||
disabled={!hasMoreAfter}
|
|
||||||
onClick={onLoadMoreAfter}
|
onClick={onLoadMoreAfter}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load older templates</i18n.Translate>
|
<i18n.Translate>load older templates</i18n.Translate>
|
||||||
|
@ -35,8 +35,9 @@ import {
|
|||||||
} from "../../../../hooks/templates.js";
|
} from "../../../../hooks/templates.js";
|
||||||
import { Notification } from "../../../../utils/types.js";
|
import { Notification } from "../../../../utils/types.js";
|
||||||
import { ListPage } from "./ListPage.js";
|
import { ListPage } from "./ListPage.js";
|
||||||
import { HttpStatusCode } from "@gnu-taler/taler-util";
|
import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
|
||||||
import { ConfirmModal } from "../../../../components/modal/index.js";
|
import { ConfirmModal } from "../../../../components/modal/index.js";
|
||||||
|
import { JumpToElementById } from "../../../../components/form/JumpToElementById.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onUnauthorized: () => VNode;
|
onUnauthorized: () => VNode;
|
||||||
@ -60,7 +61,7 @@ export default function ListTemplates({
|
|||||||
const [position, setPosition] = useState<string | undefined>(undefined);
|
const [position, setPosition] = useState<string | undefined>(undefined);
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
||||||
const { deleteTemplate } = useTemplateAPI();
|
const { deleteTemplate, testTemplateExist } = useTemplateAPI();
|
||||||
const result = useInstanceTemplates({ position }, (id) => setPosition(id));
|
const result = useInstanceTemplates({ position }, (id) => setPosition(id));
|
||||||
const [deleting, setDeleting] =
|
const [deleting, setDeleting] =
|
||||||
useState<MerchantBackend.Template.TemplateEntry | null>(null);
|
useState<MerchantBackend.Template.TemplateEntry | null>(null);
|
||||||
@ -81,9 +82,16 @@ export default function ListTemplates({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<section class="section is-main-section">
|
||||||
<NotificationCard notification={notif} />
|
<NotificationCard notification={notif} />
|
||||||
|
|
||||||
|
<JumpToElementById
|
||||||
|
testIfExist={testTemplateExist}
|
||||||
|
onSelect={onSelect}
|
||||||
|
description={i18n.str`jump to template with the given template ID`}
|
||||||
|
palceholder={i18n.str`template id`}
|
||||||
|
/>
|
||||||
|
|
||||||
<ListPage
|
<ListPage
|
||||||
templates={result.data.templates}
|
templates={result.data.templates}
|
||||||
onLoadMoreBefore={
|
onLoadMoreBefore={
|
||||||
@ -139,6 +147,6 @@ export default function ListTemplates({
|
|||||||
</p>
|
</p>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ export function ListPage({
|
|||||||
>
|
>
|
||||||
<InputSelector
|
<InputSelector
|
||||||
name="payto_uri"
|
name="payto_uri"
|
||||||
label={i18n.str`Address`}
|
label={i18n.str`Account URI`}
|
||||||
values={accounts}
|
values={accounts}
|
||||||
placeholder={i18n.str`Select one account`}
|
placeholder={i18n.str`Select one account`}
|
||||||
tooltip={i18n.str`filter by account address`}
|
tooltip={i18n.str`filter by account address`}
|
||||||
|
@ -125,11 +125,10 @@ function Table({
|
|||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
return (
|
return (
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
{onLoadMoreBefore && (
|
{hasMoreBefore && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
data-tooltip={i18n.str`load more transfers before the first one`}
|
data-tooltip={i18n.str`load more transfers before the first one`}
|
||||||
disabled={!hasMoreBefore}
|
|
||||||
onClick={onLoadMoreBefore}
|
onClick={onLoadMoreBefore}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load newer transfers</i18n.Translate>
|
<i18n.Translate>load newer transfers</i18n.Translate>
|
||||||
@ -198,11 +197,10 @@ function Table({
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{onLoadMoreAfter && (
|
{hasMoreAfter && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
data-tooltip={i18n.str`load more transfer after the last one`}
|
data-tooltip={i18n.str`load more transfer after the last one`}
|
||||||
disabled={!hasMoreAfter}
|
|
||||||
onClick={onLoadMoreAfter}
|
onClick={onLoadMoreAfter}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load older transfers</i18n.Translate>
|
<i18n.Translate>load older transfers</i18n.Translate>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import { ErrorType, HttpError } from "@gnu-taler/web-util/browser";
|
import { ErrorType, HttpError } from "@gnu-taler/web-util/browser";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { Loading } from "../../../../components/exception/loading.js";
|
import { Loading } from "../../../../components/exception/loading.js";
|
||||||
import { MerchantBackend } from "../../../../declaration.js";
|
import { MerchantBackend } from "../../../../declaration.js";
|
||||||
import { useInstanceDetails } from "../../../../hooks/instance.js";
|
import { useInstanceDetails } from "../../../../hooks/instance.js";
|
||||||
@ -47,7 +47,6 @@ export default function ListTransfer({
|
|||||||
onCreate,
|
onCreate,
|
||||||
onNotFound,
|
onNotFound,
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
const [form, setForm] = useState<Form>({ payto_uri: "" });
|
|
||||||
const setFilter = (s?: "yes" | "no") => setForm({ ...form, verified: s });
|
const setFilter = (s?: "yes" | "no") => setForm({ ...form, verified: s });
|
||||||
|
|
||||||
const [position, setPosition] = useState<string | undefined>(undefined);
|
const [position, setPosition] = useState<string | undefined>(undefined);
|
||||||
@ -56,6 +55,14 @@ export default function ListTransfer({
|
|||||||
const accounts = !instance.ok
|
const accounts = !instance.ok
|
||||||
? []
|
? []
|
||||||
: instance.data.accounts.map((a) => a.payto_uri);
|
: instance.data.accounts.map((a) => a.payto_uri);
|
||||||
|
const [form, setForm] = useState<Form>({ payto_uri: "" });
|
||||||
|
|
||||||
|
const shoulUseDefaultAccount = accounts.length === 1
|
||||||
|
useEffect(() => {
|
||||||
|
if (shoulUseDefaultAccount) {
|
||||||
|
setForm({...form, payto_uri: accounts[0]})
|
||||||
|
}
|
||||||
|
}, [shoulUseDefaultAccount])
|
||||||
|
|
||||||
const isVerifiedTransfers = form.verified === "yes";
|
const isVerifiedTransfers = form.verified === "yes";
|
||||||
const isNonVerifiedTransfers = form.verified === "no";
|
const isNonVerifiedTransfers = form.verified === "no";
|
||||||
|
@ -118,6 +118,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
{state.otp_algorithm && state.otp_algorithm > 0 ? (
|
{state.otp_algorithm && state.otp_algorithm > 0 ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<InputWithAddon<Entity>
|
<InputWithAddon<Entity>
|
||||||
|
expand
|
||||||
name="otp_key"
|
name="otp_key"
|
||||||
label={i18n.str`Device key`}
|
label={i18n.str`Device key`}
|
||||||
inputType={showKey ? "text" : "password"}
|
inputType={showKey ? "text" : "password"}
|
||||||
|
@ -126,11 +126,10 @@ function Table({
|
|||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
{onLoadMoreBefore && (
|
{hasMoreBefore && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
data-tooltip={i18n.str`load more devices before the first one`}
|
data-tooltip={i18n.str`load more devices before the first one`}
|
||||||
disabled={!hasMoreBefore}
|
|
||||||
onClick={onLoadMoreBefore}
|
onClick={onLoadMoreBefore}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load newer devices</i18n.Translate>
|
<i18n.Translate>load newer devices</i18n.Translate>
|
||||||
@ -180,11 +179,10 @@ function Table({
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{onLoadMoreAfter && (
|
{hasMoreAfter && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
data-tooltip={i18n.str`load more devices after the last one`}
|
data-tooltip={i18n.str`load more devices after the last one`}
|
||||||
disabled={!hasMoreAfter}
|
|
||||||
onClick={onLoadMoreAfter}
|
onClick={onLoadMoreAfter}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load older devices</i18n.Translate>
|
<i18n.Translate>load older devices</i18n.Translate>
|
||||||
|
@ -126,11 +126,10 @@ function Table({
|
|||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
{onLoadMoreBefore && (
|
{hasMoreBefore && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
data-tooltip={i18n.str`load more webhooks before the first one`}
|
data-tooltip={i18n.str`load more webhooks before the first one`}
|
||||||
disabled={!hasMoreBefore}
|
|
||||||
onClick={onLoadMoreBefore}
|
onClick={onLoadMoreBefore}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load newer webhooks</i18n.Translate>
|
<i18n.Translate>load newer webhooks</i18n.Translate>
|
||||||
@ -187,11 +186,10 @@ function Table({
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{onLoadMoreAfter && (
|
{hasMoreAfter && (
|
||||||
<button
|
<button
|
||||||
class="button is-fullwidth"
|
class="button is-fullwidth"
|
||||||
data-tooltip={i18n.str`load more webhooks after the last one`}
|
data-tooltip={i18n.str`load more webhooks after the last one`}
|
||||||
disabled={!hasMoreAfter}
|
|
||||||
onClick={onLoadMoreAfter}
|
onClick={onLoadMoreAfter}
|
||||||
>
|
>
|
||||||
<i18n.Translate>load older webhooks</i18n.Translate>
|
<i18n.Translate>load older webhooks</i18n.Translate>
|
||||||
|
@ -26,31 +26,15 @@ import { useBackendContext } from "../../context/backend.js";
|
|||||||
import { useInstanceContext } from "../../context/instance.js";
|
import { useInstanceContext } from "../../context/instance.js";
|
||||||
import { AccessToken, LoginToken } from "../../declaration.js";
|
import { AccessToken, LoginToken } from "../../declaration.js";
|
||||||
import { useCredentialsChecker } from "../../hooks/backend.js";
|
import { useCredentialsChecker } from "../../hooks/backend.js";
|
||||||
import { useBackendURL } from "../../hooks/index.js";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onConfirm: (token: LoginToken | undefined) => void;
|
onConfirm: (token: LoginToken | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTokenValuePart(t: string): string {
|
|
||||||
if (!t) return t;
|
|
||||||
const match = /secret-token:(.*)/.exec(t);
|
|
||||||
if (!match || !match[1]) return "";
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeToken(r: string): AccessToken {
|
function normalizeToken(r: string): AccessToken {
|
||||||
return `secret-token:${r}` as AccessToken;
|
return `secret-token:${r}` as AccessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanUp(s: string): string {
|
|
||||||
let result = s;
|
|
||||||
if (result.indexOf("webui/") !== -1) {
|
|
||||||
result = result.substring(0, result.indexOf("webui/"));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LoginPage({ onConfirm }: Props): VNode {
|
export function LoginPage({ onConfirm }: Props): VNode {
|
||||||
const { url: backendURL, changeBackend, resetBackend } = useBackendContext();
|
const { url: backendURL, changeBackend, resetBackend } = useBackendContext();
|
||||||
const { admin, id } = useInstanceContext();
|
const { admin, id } = useInstanceContext();
|
||||||
@ -245,11 +229,14 @@ function AsyncButton({ onClick, disabled, type = "", children }: { type?: string
|
|||||||
export function ConnectionPage({ onConfirm }: { onConfirm: (s: string) => void }): VNode {
|
export function ConnectionPage({ onConfirm }: { onConfirm: (s: string) => void }): VNode {
|
||||||
const { url: backendURL } = useBackendContext()
|
const { url: backendURL } = useBackendContext()
|
||||||
|
|
||||||
const [url, setURL] = useState(cleanUp(backendURL));
|
const [error, setError] = useState<string>();
|
||||||
|
const [url, setURL] = useState(backendURL ?? "");
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
async function doConnect() {
|
async function doConnect() {
|
||||||
onConfirm(url)
|
const withHttp = url.startsWith("http") ? url : "https://" + url
|
||||||
|
|
||||||
|
onConfirm(withHttp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Loading…
Reference in New Issue
Block a user