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