fixing issues reported by Christian, wip
This commit is contained in:
parent
535b990215
commit
851b2da39c
@ -363,14 +363,14 @@ export function PaytoWireTransferForm({
|
|||||||
*/
|
*/
|
||||||
export function doAutoFocus(element: HTMLElement | null) {
|
export function doAutoFocus(element: HTMLElement | null) {
|
||||||
if (element) {
|
if (element) {
|
||||||
window.requestIdleCallback(() => {
|
setTimeout(() => {
|
||||||
element.focus()
|
element.focus()
|
||||||
element.scrollIntoView({
|
element.scrollIntoView({
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
block: "center",
|
block: "center",
|
||||||
inline: "center"
|
inline: "center"
|
||||||
})
|
})
|
||||||
})
|
}, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ export function WithdrawalQRCode({
|
|||||||
details={{
|
details={{
|
||||||
account,
|
account,
|
||||||
reserve: data.selected_reserve_pub,
|
reserve: data.selected_reserve_pub,
|
||||||
amount: Amounts.parseOrThrow("usd:10.00")
|
amount: Amounts.parseOrThrow(data.amount)
|
||||||
}}
|
}}
|
||||||
onAborted={() => {
|
onAborted={() => {
|
||||||
notifyInfo(i18n.str`Operation canceled`);
|
notifyInfo(i18n.str`Operation canceled`);
|
||||||
|
@ -60,7 +60,7 @@ export function Application(): VNode {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function ApplicationStatusRoutes(): VNode {
|
function ApplicationStatusRoutes(): VNode {
|
||||||
const { url: backendURL, updateToken, changeBackend } = useBackendContext();
|
const { changeBackend, selected: backendSelected } = useBackendContext();
|
||||||
const result = useBackendConfig();
|
const result = useBackendConfig();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ 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]);
|
||||||
|
|
||||||
if (!backendURL) {
|
if (!backendSelected) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<NotConnectedAppMenu title="Welcome!" />
|
<NotConnectedAppMenu title="Welcome!" />
|
||||||
|
@ -87,9 +87,9 @@ export enum InstancePaths {
|
|||||||
bank_update = "/bank/:bid/update",
|
bank_update = "/bank/:bid/update",
|
||||||
bank_new = "/bank/new",
|
bank_new = "/bank/new",
|
||||||
|
|
||||||
product_list = "/products",
|
inventory_list = "/inventory",
|
||||||
product_update = "/product/:pid/update",
|
inventory_update = "/inventory/:pid/update",
|
||||||
product_new = "/product/new",
|
inventory_new = "/inventory/new",
|
||||||
|
|
||||||
order_list = "/orders",
|
order_list = "/orders",
|
||||||
order_new = "/order/new",
|
order_new = "/order/new",
|
||||||
@ -347,42 +347,42 @@ export function InstanceRoutes({
|
|||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
|
||||||
/>
|
/>
|
||||||
{/**
|
{/**
|
||||||
* Product pages
|
* Inventory pages
|
||||||
*/}
|
*/}
|
||||||
<Route
|
<Route
|
||||||
path={InstancePaths.product_list}
|
path={InstancePaths.inventory_list}
|
||||||
component={ProductListPage}
|
component={ProductListPage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
|
||||||
onCreate={() => {
|
onCreate={() => {
|
||||||
route(InstancePaths.product_new);
|
route(InstancePaths.inventory_new);
|
||||||
}}
|
}}
|
||||||
onSelect={(id: string) => {
|
onSelect={(id: string) => {
|
||||||
route(InstancePaths.product_update.replace(":pid", id));
|
route(InstancePaths.inventory_update.replace(":pid", id));
|
||||||
}}
|
}}
|
||||||
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={InstancePaths.product_update}
|
path={InstancePaths.inventory_update}
|
||||||
component={ProductUpdatePage}
|
component={ProductUpdatePage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.product_list)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.inventory_list)}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
route(InstancePaths.product_list);
|
route(InstancePaths.inventory_list);
|
||||||
}}
|
}}
|
||||||
onBack={() => {
|
onBack={() => {
|
||||||
route(InstancePaths.product_list);
|
route(InstancePaths.inventory_list);
|
||||||
}}
|
}}
|
||||||
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={InstancePaths.product_new}
|
path={InstancePaths.inventory_new}
|
||||||
component={ProductCreatePage}
|
component={ProductCreatePage}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
route(InstancePaths.product_list);
|
route(InstancePaths.inventory_list);
|
||||||
}}
|
}}
|
||||||
onBack={() => {
|
onBack={() => {
|
||||||
route(InstancePaths.product_list);
|
route(InstancePaths.inventory_list);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/**
|
{/**
|
||||||
@ -405,7 +405,7 @@ export function InstanceRoutes({
|
|||||||
path={InstancePaths.bank_update}
|
path={InstancePaths.bank_update}
|
||||||
component={BankAccountUpdatePage}
|
component={BankAccountUpdatePage}
|
||||||
onUnauthorized={LoginPageAccessDenied}
|
onUnauthorized={LoginPageAccessDenied}
|
||||||
onLoadError={ServerErrorRedirectTo(InstancePaths.product_list)}
|
onLoadError={ServerErrorRedirectTo(InstancePaths.inventory_list)}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
route(InstancePaths.bank_list);
|
route(InstancePaths.bank_list);
|
||||||
}}
|
}}
|
||||||
|
@ -212,10 +212,7 @@ export function InputStock<T>({
|
|||||||
withTimestampSupport
|
withTimestampSupport
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InputGroup<Entity>
|
<InputGroup<Entity> name="address" label={i18n.str`Warehouse address`}>
|
||||||
name="address"
|
|
||||||
label={i18n.str`Delivery address`}
|
|
||||||
>
|
|
||||||
<InputLocation name="address" />
|
<InputLocation name="address" />
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
@ -49,7 +49,7 @@ export function Sidebar({
|
|||||||
isPasswordOk
|
isPasswordOk
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
const config = useConfigContext();
|
const config = useConfigContext();
|
||||||
const { url: backendURL } = useBackendContext()
|
const { url: backendURL, resetBackend } = useBackendContext()
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const kycStatus = useInstanceKYCDetails();
|
const kycStatus = useInstanceKYCDetails();
|
||||||
const needKYC = kycStatus.ok && kycStatus.data.type === "redirect";
|
const needKYC = kycStatus.ok && kycStatus.data.type === "redirect";
|
||||||
@ -80,7 +80,7 @@ export function Sidebar({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu is-menu-main">
|
<div class="menu is-menu-main">
|
||||||
{isPasswordOk && instance ? (
|
{instance ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<li>
|
||||||
@ -94,12 +94,12 @@ export function Sidebar({
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={"/products"} class="has-icon">
|
<a href={"/inventory"} class="has-icon">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="mdi mdi-shopping" />
|
<i class="mdi mdi-shopping" />
|
||||||
</span>
|
</span>
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<i18n.Translate>Products</i18n.Translate>
|
<i18n.Translate>Inventory</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -243,7 +243,7 @@ export function Sidebar({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{isPasswordOk && admin && !mimic && (
|
{admin && !mimic && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<p class="menu-label">
|
<p class="menu-label">
|
||||||
<i18n.Translate>Instances</i18n.Translate>
|
<i18n.Translate>Instances</i18n.Translate>
|
||||||
@ -270,7 +270,7 @@ export function Sidebar({
|
|||||||
</li>
|
</li>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
{isPasswordOk &&
|
{isPasswordOk ?
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="has-icon is-state-info is-hoverable"
|
class="has-icon is-state-info is-hoverable"
|
||||||
@ -283,8 +283,21 @@ export function Sidebar({
|
|||||||
<i18n.Translate>Log out</i18n.Translate>
|
<i18n.Translate>Log out</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li> :
|
||||||
}
|
<li>
|
||||||
|
<a
|
||||||
|
class="has-icon is-state-info is-hoverable"
|
||||||
|
onClick={(): void => resetBackend()}
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<i class="mdi mdi-logout default" />
|
||||||
|
</span>
|
||||||
|
<span class="menu-item-label">
|
||||||
|
<i18n.Translate>Change server</i18n.Translate>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
@ -30,11 +30,11 @@ function getInstanceTitle(path: string, id: string): string {
|
|||||||
return `${id}: Orders`;
|
return `${id}: Orders`;
|
||||||
case InstancePaths.order_new:
|
case InstancePaths.order_new:
|
||||||
return `${id}: New order`;
|
return `${id}: New order`;
|
||||||
case InstancePaths.product_list:
|
case InstancePaths.inventory_list:
|
||||||
return `${id}: Products`;
|
return `${id}: Inventory`;
|
||||||
case InstancePaths.product_new:
|
case InstancePaths.inventory_new:
|
||||||
return `${id}: New product`;
|
return `${id}: New product`;
|
||||||
case InstancePaths.product_update:
|
case InstancePaths.inventory_update:
|
||||||
return `${id}: Update product`;
|
return `${id}: Update product`;
|
||||||
case InstancePaths.reserves_new:
|
case InstancePaths.reserves_new:
|
||||||
return `${id}: New reserve`;
|
return `${id}: New reserve`;
|
||||||
|
@ -146,9 +146,9 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
|
|||||||
/>
|
/>
|
||||||
<InputNumber<Entity>
|
<InputNumber<Entity>
|
||||||
name="minimum_age"
|
name="minimum_age"
|
||||||
label={i18n.str`Age restricted`}
|
label={i18n.str`Age restriction`}
|
||||||
tooltip={i18n.str`is this product restricted for customer below certain age?`}
|
tooltip={i18n.str`is this product restricted for customer below certain age?`}
|
||||||
help={i18n.str`can be overridden by the order configuration`}
|
help={i18n.str`minimum age of the buyer`}
|
||||||
/>
|
/>
|
||||||
<Input<Entity>
|
<Input<Entity>
|
||||||
name="unit"
|
name="unit"
|
||||||
@ -165,7 +165,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
|
|||||||
name="stock"
|
name="stock"
|
||||||
label={i18n.str`Stock`}
|
label={i18n.str`Stock`}
|
||||||
alreadyExist={alreadyExist}
|
alreadyExist={alreadyExist}
|
||||||
tooltip={i18n.str`product inventory for products with finite supply (for internal use only)`}
|
tooltip={i18n.str`inventory for products with finite supply (for internal use only)`}
|
||||||
/>
|
/>
|
||||||
<InputTaxes<Entity>
|
<InputTaxes<Entity>
|
||||||
name="taxes"
|
name="taxes"
|
||||||
|
@ -20,35 +20,55 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createContext, h, VNode } from "preact";
|
import { createContext, h, VNode } from "preact";
|
||||||
import { useContext } from "preact/hooks";
|
import { useContext, useState } from "preact/hooks";
|
||||||
import { LoginToken } from "../declaration.js";
|
import { LoginToken } from "../declaration.js";
|
||||||
import { useBackendDefaultToken, useBackendURL } from "../hooks/index.js";
|
import { useBackendDefaultToken, useBackendURL } from "../hooks/index.js";
|
||||||
|
import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
|
||||||
|
import { codecForBoolean } from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
interface BackendContextType {
|
interface BackendContextType {
|
||||||
url: string,
|
url: string,
|
||||||
|
selected: boolean;
|
||||||
token?: LoginToken;
|
token?: LoginToken;
|
||||||
updateToken: (token: LoginToken | undefined) => void;
|
updateToken: (token: LoginToken | undefined) => void;
|
||||||
changeBackend: (url: string) => void;
|
changeBackend: (url: string) => void;
|
||||||
|
resetBackend: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackendContext = createContext<BackendContextType>({
|
const BackendContext = createContext<BackendContextType>({
|
||||||
url: "",
|
url: "",
|
||||||
|
selected: false,
|
||||||
token: undefined,
|
token: undefined,
|
||||||
updateToken: () => null,
|
updateToken: () => null,
|
||||||
changeBackend: () => null,
|
changeBackend: () => null,
|
||||||
|
resetBackend: () => null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const BACKEND_SELECTED = buildStorageKey("backend-selected", codecForBoolean());
|
||||||
|
|
||||||
function useBackendContextState(
|
function useBackendContextState(
|
||||||
defaultUrl?: string,
|
defaultUrl?: string,
|
||||||
): BackendContextType {
|
): BackendContextType {
|
||||||
const [url, changeBackend] = useBackendURL(defaultUrl);
|
const [url, changeBackend2] = useBackendURL(defaultUrl);
|
||||||
const [token, updateToken] = useBackendDefaultToken();
|
const [token, updateToken] = useBackendDefaultToken();
|
||||||
|
const {value, update} = useLocalStorage(BACKEND_SELECTED)
|
||||||
|
|
||||||
|
function changeBackend(s:string) {
|
||||||
|
changeBackend2(s)
|
||||||
|
update(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetBackend() {
|
||||||
|
update(false)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
|
selected: value ?? false,
|
||||||
updateToken,
|
updateToken,
|
||||||
changeBackend
|
changeBackend,
|
||||||
|
resetBackend
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1327,7 +1327,7 @@ export namespace MerchantBackend {
|
|||||||
otp_device_id: string;
|
otp_device_id: string;
|
||||||
|
|
||||||
// Human-readable description for the device.
|
// Human-readable description for the device.
|
||||||
otp_description: string;
|
otp_device_description: string;
|
||||||
|
|
||||||
// A base64-encoded key
|
// A base64-encoded key
|
||||||
otp_key: string;
|
otp_key: string;
|
||||||
@ -1341,7 +1341,7 @@ export namespace MerchantBackend {
|
|||||||
|
|
||||||
interface OtpDevicePatchDetails {
|
interface OtpDevicePatchDetails {
|
||||||
// Human-readable description for the device.
|
// Human-readable description for the device.
|
||||||
otp_description: string;
|
otp_device_description: string;
|
||||||
|
|
||||||
// A base64-encoded key
|
// A base64-encoded key
|
||||||
otp_key: string | undefined;
|
otp_key: string | undefined;
|
||||||
|
@ -31,7 +31,8 @@ const calculateRootPath = () => {
|
|||||||
typeof window !== undefined
|
typeof window !== undefined
|
||||||
? window.location.origin + window.location.pathname
|
? window.location.origin + window.location.pathname
|
||||||
: "/";
|
: "/";
|
||||||
return rootPath;
|
|
||||||
|
return rootPath.replace("webui/","");
|
||||||
};
|
};
|
||||||
|
|
||||||
const loginTokenCodec = buildCodecForObject<LoginToken>()
|
const loginTokenCodec = buildCodecForObject<LoginToken>()
|
||||||
|
@ -30,13 +30,13 @@ const useSWR = _useSWR as unknown as SWRHook;
|
|||||||
|
|
||||||
const MOCKED_DEVICES: Record<string, MerchantBackend.OTP.OtpDeviceAddDetails> = {
|
const MOCKED_DEVICES: Record<string, MerchantBackend.OTP.OtpDeviceAddDetails> = {
|
||||||
"1": {
|
"1": {
|
||||||
otp_description: "first device",
|
otp_device_description: "first device",
|
||||||
otp_algorithm: 1,
|
otp_algorithm: 1,
|
||||||
otp_device_id: "1",
|
otp_device_id: "1",
|
||||||
otp_key: "123",
|
otp_key: "123",
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
otp_description: "second device",
|
otp_device_description: "second device",
|
||||||
otp_algorithm: 0,
|
otp_algorithm: 0,
|
||||||
otp_device_id: "2",
|
otp_device_id: "2",
|
||||||
otp_key: "456",
|
otp_key: "456",
|
||||||
|
@ -26,6 +26,9 @@ import _useSWR, { SWRHook, useSWRConfig } from "swr";
|
|||||||
const useSWR = _useSWR as unknown as SWRHook;
|
const useSWR = _useSWR as unknown as SWRHook;
|
||||||
|
|
||||||
export interface ProductAPI {
|
export interface ProductAPI {
|
||||||
|
getProduct: (
|
||||||
|
id: string,
|
||||||
|
) => Promise<void>;
|
||||||
createProduct: (
|
createProduct: (
|
||||||
data: MerchantBackend.Products.ProductAddDetail,
|
data: MerchantBackend.Products.ProductAddDetail,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
@ -66,7 +69,7 @@ export function useProductAPI(): ProductAPI {
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await mutateAll(/.*"\/private\/products.*/);
|
return await mutateAll(/.*\/private\/products.*/);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteProduct = async (productId: string): Promise<void> => {
|
const deleteProduct = async (productId: string): Promise<void> => {
|
||||||
@ -88,7 +91,17 @@ export function useProductAPI(): ProductAPI {
|
|||||||
return await mutateAll(/.*"\/private\/products.*/);
|
return await mutateAll(/.*"\/private\/products.*/);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { createProduct, updateProduct, deleteProduct, lockProduct };
|
const getProduct = async (
|
||||||
|
productId: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
await request(`/private/products/${productId}`, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
return
|
||||||
|
};
|
||||||
|
|
||||||
|
return { createProduct, updateProduct, deleteProduct, lockProduct, getProduct };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useInstanceProducts(): HttpResponse<
|
export function useInstanceProducts(): HttpResponse<
|
||||||
|
@ -66,7 +66,7 @@ export function CardTable({
|
|||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="mdi mdi-shopping" />
|
<i class="mdi mdi-shopping" />
|
||||||
</span>
|
</span>
|
||||||
<i18n.Translate>Products</i18n.Translate>
|
<i18n.Translate>Inventory</i18n.Translate>
|
||||||
</p>
|
</p>
|
||||||
<div class="card-header-icon" aria-label="more options">
|
<div class="card-header-icon" aria-label="more options">
|
||||||
<span
|
<span
|
||||||
@ -142,7 +142,7 @@ function Table({
|
|||||||
<i18n.Translate>Taxes</i18n.Translate>
|
<i18n.Translate>Taxes</i18n.Translate>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<i18n.Translate>Profit</i18n.Translate>
|
<i18n.Translate>Sales</i18n.Translate>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<i18n.Translate>Stock</i18n.Translate>
|
<i18n.Translate>Stock</i18n.Translate>
|
||||||
@ -190,18 +190,21 @@ function Table({
|
|||||||
src={i.image ? i.image : emptyImage}
|
src={i.image ? i.image : emptyImage}
|
||||||
style={{
|
style={{
|
||||||
border: "solid black 1px",
|
border: "solid black 1px",
|
||||||
width: 100,
|
maxHeight: "2em",
|
||||||
height: 100,
|
width: "auto",
|
||||||
|
height: "auto",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
|
class="has-tooltip-right"
|
||||||
|
data-tooltip={i.description}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
rowSelection !== i.id && rowSelectionHandler(i.id)
|
rowSelection !== i.id && rowSelectionHandler(i.id)
|
||||||
}
|
}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
{i.description}
|
{i.description.length > 30 ? i.description.substring(0, 30) + "..." : i.description}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -53,7 +53,7 @@ export default function ProductList({
|
|||||||
onNotFound,
|
onNotFound,
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
const result = useInstanceProducts();
|
const result = useInstanceProducts();
|
||||||
const { deleteProduct, updateProduct } = useProductAPI();
|
const { deleteProduct, updateProduct, getProduct } = useProductAPI();
|
||||||
const [deleting, setDeleting] =
|
const [deleting, setDeleting] =
|
||||||
useState<MerchantBackend.Products.ProductDetail & WithId | null>(null);
|
useState<MerchantBackend.Products.ProductDetail & WithId | null>(null);
|
||||||
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
||||||
@ -74,11 +74,61 @@ 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">
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control">
|
||||||
|
<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}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
import { HttpError, RequestError, useApiContext, useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { StateUpdater, useEffect, useState } from "preact/hooks";
|
import { StateUpdater, useEffect, useState } from "preact/hooks";
|
||||||
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
|
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
|
||||||
@ -35,6 +35,7 @@ import {
|
|||||||
PAYTO_WIRE_METHOD_LOOKUP,
|
PAYTO_WIRE_METHOD_LOOKUP,
|
||||||
URL_REGEX,
|
URL_REGEX,
|
||||||
} from "../../../../utils/constants.js";
|
} from "../../../../utils/constants.js";
|
||||||
|
import { useBackendBaseRequest } from "../../../../hooks/backend.js";
|
||||||
|
|
||||||
type Entity = MerchantBackend.Rewards.ReserveCreateRequest;
|
type Entity = MerchantBackend.Rewards.ReserveCreateRequest;
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ function ViewStep({
|
|||||||
setReserve,
|
setReserve,
|
||||||
}: ViewProps): VNode {
|
}: ViewProps): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
const {request} = useApiContext()
|
||||||
const [wireMethods, setWireMethods] = useState<Array<string>>([]);
|
const [wireMethods, setWireMethods] = useState<Array<string>>([]);
|
||||||
const [exchangeQueryError, setExchangeQueryError] = useState<
|
const [exchangeQueryError, setExchangeQueryError] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
@ -123,19 +125,26 @@ function ViewStep({
|
|||||||
<AsyncButton
|
<AsyncButton
|
||||||
class="has-tooltip-left"
|
class="has-tooltip-left"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
return fetch(`${reserve.exchange_url}wire`)
|
if (!reserve.exchange_url) {
|
||||||
.then((r) => r.json())
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return request<any>(reserve.exchange_url, "keys") //</div>fetch(`${reserve.exchange_url}wire`)
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
const wireMethods = r.accounts.map((a: any) => {
|
if (r.loading) return;
|
||||||
const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri);
|
if (r.ok) {
|
||||||
return (match && match[1]) || "";
|
const wireMethods = r.data.accounts.map((a: any) => {
|
||||||
});
|
const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri);
|
||||||
|
return (match && match[1]) || "";
|
||||||
|
});
|
||||||
|
}
|
||||||
setWireMethods(wireMethods);
|
setWireMethods(wireMethods);
|
||||||
setCurrentStep(Steps.WIRE_METHOD);
|
setCurrentStep(Steps.WIRE_METHOD);
|
||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
.catch((r: any) => {
|
.catch((r: RequestError<{}>) => {
|
||||||
setExchangeQueryError(r.message);
|
console.log(r.cause)
|
||||||
|
setExchangeQueryError(r.cause.message);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
data-tooltip={
|
data-tooltip={
|
||||||
|
@ -70,8 +70,8 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
: state.otp_key.length !== 32
|
: state.otp_key.length !== 32
|
||||||
? i18n.str`size of the key should be 32`
|
? i18n.str`size of the key should be 32`
|
||||||
: undefined,
|
: undefined,
|
||||||
otp_description: !state.otp_description ? i18n.str`required`
|
otp_device_description: !state.otp_device_description ? i18n.str`required`
|
||||||
: !/[a-zA-Z0-9]*/.test(state.otp_description)
|
: !/[a-zA-Z0-9]*/.test(state.otp_device_description)
|
||||||
? i18n.str`no valid. only characters and numbers`
|
? i18n.str`no valid. only characters and numbers`
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
tooltip={i18n.str`Internal id on the system`}
|
tooltip={i18n.str`Internal id on the system`}
|
||||||
/>
|
/>
|
||||||
<Input<Entity>
|
<Input<Entity>
|
||||||
name="otp_description"
|
name="otp_device_description"
|
||||||
label={i18n.str`Descripiton`}
|
label={i18n.str`Descripiton`}
|
||||||
tooltip={i18n.str`Useful to identify the device physically`}
|
tooltip={i18n.str`Useful to identify the device physically`}
|
||||||
/>
|
/>
|
||||||
|
@ -77,7 +77,7 @@ export function CreatedSuccessfully({
|
|||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
readonly
|
readonly
|
||||||
value={entity.otp_description}
|
value={entity.otp_device_description}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,7 +87,7 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode {
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
>
|
>
|
||||||
<Input<Entity>
|
<Input<Entity>
|
||||||
name="otp_description"
|
name="otp_device_description"
|
||||||
label={i18n.str`Description`}
|
label={i18n.str`Description`}
|
||||||
tooltip={i18n.str`dddd`}
|
tooltip={i18n.str`dddd`}
|
||||||
/>
|
/>
|
||||||
|
@ -80,7 +80,7 @@ export default function UpdateValidator({
|
|||||||
device={{
|
device={{
|
||||||
id: vid,
|
id: vid,
|
||||||
otp_algorithm: result.data.otp_algorithm,
|
otp_algorithm: result.data.otp_algorithm,
|
||||||
otp_description: result.data.device_description,
|
otp_device_description: result.data.device_description,
|
||||||
otp_key: undefined,
|
otp_key: undefined,
|
||||||
otp_ctr: result.data.otp_ctr
|
otp_ctr: result.data.otp_ctr
|
||||||
}}
|
}}
|
||||||
|
@ -52,7 +52,7 @@ function cleanUp(s: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LoginPage({ onConfirm }: Props): VNode {
|
export function LoginPage({ onConfirm }: Props): VNode {
|
||||||
const { url: backendURL, changeBackend } = useBackendContext();
|
const { url: backendURL, changeBackend, resetBackend } = useBackendContext();
|
||||||
const { admin, id } = useInstanceContext();
|
const { admin, id } = useInstanceContext();
|
||||||
const { requestNewLoginToken } = useCredentialsChecker();
|
const { requestNewLoginToken } = useCredentialsChecker();
|
||||||
const [token, setToken] = useState("");
|
const [token, setToken] = useState("");
|
||||||
@ -73,10 +73,9 @@ export function LoginPage({ onConfirm }: Props): VNode {
|
|||||||
}, [backendURL, id, token])
|
}, [backendURL, id, token])
|
||||||
|
|
||||||
async function changeServer() {
|
async function changeServer() {
|
||||||
changeBackend("")
|
resetBackend()
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(admin, id)
|
|
||||||
if (admin && id !== "default") {
|
if (admin && id !== "default") {
|
||||||
//admin trying to access another instance
|
//admin trying to access another instance
|
||||||
return (<div class="columns is-centered" style={{ margin: "auto" }}>
|
return (<div class="columns is-centered" style={{ margin: "auto" }}>
|
||||||
@ -211,10 +210,7 @@ export function LoginPage({ onConfirm }: Props): VNode {
|
|||||||
borderTop: 0,
|
borderTop: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AsyncButton
|
<AsyncButton onClick={changeServer}>
|
||||||
|
|
||||||
onClick={changeServer}
|
|
||||||
>
|
|
||||||
<i18n.Translate>Change server</i18n.Translate>
|
<i18n.Translate>Change server</i18n.Translate>
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
|
|
||||||
@ -304,11 +300,8 @@ export function ConnectionPage({ onConfirm }: { onConfirm: (s: string) => void }
|
|||||||
borderTop: 0,
|
borderTop: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AsyncButton
|
<AsyncButton onClick={doConnect}>
|
||||||
disabled={backendURL === url}
|
<i18n.Translate>Connect</i18n.Translate>
|
||||||
onClick={doConnect}
|
|
||||||
>
|
|
||||||
<i18n.Translate>Try again</i18n.Translate>
|
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import { isAfter, isFuture } from "date-fns";
|
import { isAfter, isFuture } from "date-fns";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { AMOUNT_REGEX, PAYTO_REGEX } from "../utils/constants.js";
|
import { AMOUNT_REGEX, PAYTO_REGEX } from "../utils/constants.js";
|
||||||
|
import { Amounts } from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
yup.setLocale({
|
yup.setLocale({
|
||||||
mixed: {
|
mixed: {
|
||||||
@ -38,7 +39,7 @@ function listOfPayToUrisAreValid(values?: (string | undefined)[]): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function currencyWithAmountIsValid(value?: string): boolean {
|
function currencyWithAmountIsValid(value?: string): boolean {
|
||||||
return !!value && AMOUNT_REGEX.test(value);
|
return !!value && Amounts.parse(value) !== undefined;
|
||||||
}
|
}
|
||||||
function currencyGreaterThan0(value?: string) {
|
function currencyGreaterThan0(value?: string) {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -25,7 +25,7 @@ export const PAYTO_REGEX =
|
|||||||
export const PAYTO_WIRE_METHOD_LOOKUP =
|
export const PAYTO_WIRE_METHOD_LOOKUP =
|
||||||
/payto:\/\/([a-zA-Z][a-zA-Z0-9-.]+)\/.*/;
|
/payto:\/\/([a-zA-Z][a-zA-Z0-9-.]+)\/.*/;
|
||||||
|
|
||||||
export const AMOUNT_REGEX = /^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/;
|
export const AMOUNT_REGEX = /^[a-zA-Z][a-zA-Z]{1,11}:[0-9][0-9,]*\.?[0-9,]*$/;
|
||||||
|
|
||||||
export const INSTANCE_ID_LOOKUP = /\/instances\/([^/]*)\/?$/;
|
export const INSTANCE_ID_LOOKUP = /\/instances\/([^/]*)\/?$/;
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ if (GIT_ROOT === "/") {
|
|||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const GIT_HASH = GIT_ROOT === "/" ? undefined : git_hash();
|
const GIT_HASH = git_hash();
|
||||||
|
|
||||||
const buf = fs.readFileSync(path.join(BASE, "package.json"));
|
const buf = fs.readFileSync(path.join(BASE, "package.json"));
|
||||||
let _package = JSON.parse(buf.toString("utf-8"));
|
let _package = JSON.parse(buf.toString("utf-8"));
|
||||||
|
Loading…
Reference in New Issue
Block a user