fixing issues reported by Christian, wip

This commit is contained in:
Sebastian 2023-10-04 14:36:03 -03:00
parent 535b990215
commit 851b2da39c
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
24 changed files with 185 additions and 85 deletions

View File

@ -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)
} }
} }

View File

@ -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`);

View File

@ -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!" />

View File

@ -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);
}} }}

View File

@ -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>

View File

@ -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,6 +283,19 @@ export function Sidebar({
<i18n.Translate>Log out</i18n.Translate> <i18n.Translate>Log out</i18n.Translate>
</span> </span>
</a> </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> </li>
} }
</ul> </ul>

View File

@ -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`;

View File

@ -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"

View File

@ -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
}; };
} }

View File

@ -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;

View File

@ -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>()

View File

@ -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",

View File

@ -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<

View File

@ -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={() =>

View File

@ -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}

View File

@ -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;
if (r.ok) {
const wireMethods = r.data.accounts.map((a: any) => {
const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri); const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri);
return (match && match[1]) || ""; 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={

View File

@ -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`}
/> />

View File

@ -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>

View File

@ -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`}
/> />

View File

@ -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
}} }}

View File

@ -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>

View File

@ -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) {

View File

@ -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\/([^/]*)\/?$/;

View File

@ -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"));