summaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/paths
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths')
-rw-r--r--packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx31
-rw-r--r--packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx48
-rw-r--r--packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx33
-rw-r--r--packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx4
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx6
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx33
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx525
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx13
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx35
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx3
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx17
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx218
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx34
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx14
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx49
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx12
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx4
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx8
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx4
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx6
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx1
-rw-r--r--packages/merchant-backoffice-ui/src/paths/login/index.tsx82
-rw-r--r--packages/merchant-backoffice-ui/src/paths/settings/index.tsx206
23 files changed, 804 insertions, 582 deletions
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx
index ec54dc150..39fdb6bdc 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx
@@ -20,8 +20,8 @@
*/
import { h, VNode, FunctionalComponent } from "preact";
-import { ConfigContextProvider } from "../../../context/config.js";
import { CreatePage as TestedComponent } from "./CreatePage.js";
+import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser";
export default {
title: "Pages/Instance/Create",
@@ -37,19 +37,32 @@ function createExample<Props>(
props: Partial<Props>,
) {
const r = (args: any) => (
- <ConfigContextProvider
+ <MerchantApiProviderTesting
value={{
- currency: "ARS",
- version: "1",
- currencies: {
- currency: "TESTKUDOS"
+ cancelRequest: () => {},
+ config: {
+ currency: "ARS",
+ version: "1",
+ currencies: {
+ "ASD": {
+ name: "testkudos",
+ alt_unit_names: {},
+ num_fractional_input_digits: 1,
+ num_fractional_normal_digits: 1,
+ num_fractional_trailing_zero_digits: 1,
+ }
+ },
+ exchanges: [],
+ name: "taler-merchant"
},
- exchanges: [],
- name: "taler-merchant"
+ hints: [],
+ lib: {} as any,
+ onActivity: (() => {}) as any,
+ url: new URL("asdasd"),
}}
>
<Component {...args} />
- </ConfigContextProvider>
+ </MerchantApiProviderTesting>
);
r.args = props;
return r;
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx
index cbda65bfe..440cd2b07 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx
@@ -17,16 +17,18 @@
*
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
+import {
+ useMerchantApiContext,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { NotificationCard } from "../../../components/menu/index.js";
-import { AccessToken, MerchantBackend } from "../../../declaration.js";
-import { useAdminAPI, useInstanceAPI } from "../../../hooks/instance.js";
+import { MerchantBackend } from "../../../declaration.js";
+import { useAdminAPI } from "../../../hooks/instance.js";
+import { useSessionContext } from "../../../context/session.js";
import { Notification } from "../../../utils/types.js";
import { CreatePage } from "./CreatePage.js";
-import { useCredentialsChecker } from "../../../hooks/backend.js";
-import { useBackendContext } from "../../../context/backend.js";
interface Props {
onBack?: () => void;
@@ -39,8 +41,8 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode {
const { createInstance } = useAdminAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { i18n } = useTranslationContext();
- const { requestNewLoginToken } = useCredentialsChecker()
- const { url: backendURL, updateToken } = useBackendContext()
+ const { lib } = useMerchantApiContext();
+ const { state, logIn } = useSessionContext();
return (
<Fragment>
@@ -53,15 +55,29 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode {
d: MerchantBackend.Instances.InstanceConfigurationMessage,
) => {
try {
- await createInstance(d)
+ await createInstance(d);
if (d.auth.token) {
- const resp = await requestNewLoginToken(backendURL, d.auth.token as AccessToken)
- if (resp.valid) {
- const { token, expiration } = resp
- updateToken({ token, expiration });
- } else {
- updateToken(undefined)
+ const result = await lib.authenticate.createAccessToken(
+ d.auth.token,
+ {
+ scope: "write",
+ duration: {
+ d_us: "forever",
+ },
+ refreshable: true,
+ },
+ );
+ if (result.type === "ok") {
+ const { access_token } = result.body;
+ logIn({ token: access_token });
}
+ // const resp = await requestNewLoginToken(backendURL.href, d.auth.token as AccessToken)
+ // if (resp.valid) {
+ // const { token, expiration } = resp
+ // updateToken({ token, expiration });
+ // } else {
+ // updateToken(undefined)
+ // }
}
onConfirm();
} catch (ex) {
@@ -72,7 +88,7 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode {
description: ex.message,
});
} else {
- console.error(ex)
+ console.error(ex);
}
}
}}
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx
index 9a947c9d5..8166dc739 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx
@@ -19,9 +19,9 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { h, VNode, FunctionalComponent } from "preact";
-import { ConfigContextProvider } from "../../../context/config.js";
+import { FunctionalComponent, h } from "preact";
import { CreatePage as TestedComponent } from "./CreatePage.js";
+import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser";
export default {
title: "Pages/Instance/Create",
@@ -37,19 +37,32 @@ function createExample<Props>(
props: Partial<Props>,
) {
const component = (args: any) => (
- <ConfigContextProvider
+ <MerchantApiProviderTesting
value={{
- currency: "TESTKUDOS",
- version: "1",
- currencies: {
- currency: "TESTKUDOS"
+ cancelRequest: () => {},
+ config: {
+ currency: "ARS",
+ version: "1",
+ currencies: {
+ "ASD": {
+ name: "testkudos",
+ alt_unit_names: {},
+ num_fractional_input_digits: 1,
+ num_fractional_normal_digits: 1,
+ num_fractional_trailing_zero_digits: 1,
+ }
+ },
+ exchanges: [],
+ name: "taler-merchant"
},
- exchanges: [],
- name: "taler-merchant"
+ hints: [],
+ lib: {} as any,
+ onActivity: (() => {}) as any,
+ url: new URL("asdasd"),
}}
>
<Internal {...(props as any)} />
- </ConfigContextProvider>
+ </MerchantApiProviderTesting>
);
return { component, props };
}
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx
index 711a5a4f0..bc18bb352 100644
--- a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx
@@ -23,7 +23,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
import { StateUpdater, useEffect, useState } from "preact/hooks";
import { MerchantBackend } from "../../../declaration.js";
-import { useSessionState } from "../../../hooks/session.js";
+import { useSessionContext } from "../../../context/session.js";
interface Props {
instances: MerchantBackend.Instances.Instance[];
@@ -149,7 +149,7 @@ function Table({
onPurge,
}: TableProps): VNode {
const { i18n } = useTranslationContext();
- const { impersonate } = useSessionState()
+ const { impersonate } = useSessionContext()
return (
<div class="table-container">
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
index 13dd3a2f6..2a37ee588 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx
@@ -18,11 +18,11 @@ import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { Loading } from "../../../components/exception/loading.js";
import { DeleteModal } from "../../../components/modal/index.js";
-import { useInstanceContext } from "../../../context/instance.js";
import { MerchantBackend } from "../../../declaration.js";
import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance.js";
import { DetailPage } from "./DetailPage.js";
import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { useSessionContext } from "../../../context/session.js";
interface Props {
onUnauthorized: () => VNode;
@@ -39,7 +39,7 @@ export default function Detail({
onDelete,
onNotFound,
}: Props): VNode {
- const { id } = useInstanceContext();
+ const { state } = useSessionContext();
const result = useInstanceDetails();
const [deleting, setDeleting] = useState<boolean>(false);
@@ -69,7 +69,7 @@ export default function Detail({
/>
{deleting && (
<DeleteModal
- element={{ name: result.data.name, id }}
+ element={{ name: result.data.name, id: state.instance }}
onCancel={() => setDeleting(false)}
onConfirm={async (): Promise<void> => {
try {
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx
index aabe67e00..94e19bb6e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx
@@ -19,8 +19,8 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { h, VNode, FunctionalComponent } from "preact";
-import { ConfigContextProvider } from "../../../context/config.js";
+import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser";
+import { FunctionalComponent, h } from "preact";
import { DetailPage as TestedComponent } from "./DetailPage.js";
export default {
@@ -37,19 +37,32 @@ function createExample<Props>(
props: Partial<Props>,
) {
const component = (args: any) => (
- <ConfigContextProvider
+ <MerchantApiProviderTesting
value={{
- currency: "TESTKUDOS",
- version: "1",
- currencies: {
- currency: "TESTKUDOS"
+ cancelRequest: () => {},
+ config: {
+ currency: "ARS",
+ version: "1",
+ currencies: {
+ "ASD": {
+ name: "testkudos",
+ alt_unit_names: {},
+ num_fractional_input_digits: 1,
+ num_fractional_normal_digits: 1,
+ num_fractional_trailing_zero_digits: 1,
+ }
+ },
+ exchanges: [],
+ name: "taler-merchant"
},
- exchanges: [],
- name: "taler-merchant"
+ hints: [],
+ lib: {} as any,
+ onActivity: (() => {}) as any,
+ url: new URL("asdasd"),
}}
>
<Internal {...(props as any)} />
- </ConfigContextProvider>
+ </MerchantApiProviderTesting>
);
return { component, props };
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
index 5633d93ab..fca123773 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
@@ -19,10 +19,18 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { AbsoluteTime, Amounts, Duration, TalerProtocolDuration } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+ AbsoluteTime,
+ Amounts,
+ Duration,
+ TalerProtocolDuration,
+} from "@gnu-taler/taler-util";
+import {
+ useMerchantApiContext,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
import { format, isFuture } from "date-fns";
-import { ComponentChildren, Fragment, VNode, h } from "preact";
+import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import {
FormErrors,
@@ -39,10 +47,8 @@ import { InputToggle } from "../../../../components/form/InputToggle.js";
import { InventoryProductForm } from "../../../../components/product/InventoryProductForm.js";
import { NonInventoryProductFrom } from "../../../../components/product/NonInventoryProductForm.js";
import { ProductList } from "../../../../components/product/ProductList.js";
-import { useConfigContext } from "../../../../context/config.js";
import { MerchantBackend, WithId } from "../../../../declaration.js";
import { usePreference } from "../../../../hooks/preference.js";
-import { OrderCreateSchema as schema } from "../../../../schemas/index.js";
import { rate } from "../../../../utils/amount.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
@@ -58,9 +64,16 @@ interface InstanceConfig {
default_wire_transfer_delay: TalerProtocolDuration;
}
-function with_defaults(config: InstanceConfig, currency: string): Partial<Entity> {
- const defaultPayDeadline = Duration.fromTalerProtocolDuration(config.default_pay_delay);
- const defaultWireDeadline = Duration.fromTalerProtocolDuration(config.default_wire_transfer_delay);
+function with_defaults(
+ config: InstanceConfig,
+ _currency: string,
+): Partial<Entity> {
+ const defaultPayDeadline = Duration.fromTalerProtocolDuration(
+ config.default_pay_delay,
+ );
+ const defaultWireDeadline = Duration.fromTalerProtocolDuration(
+ config.default_wire_transfer_delay,
+ );
return {
inventoryProducts: {},
@@ -69,9 +82,9 @@ function with_defaults(config: InstanceConfig, currency: string): Partial<Entity
payments: {
max_fee: undefined,
createToken: true,
- pay_deadline: (defaultPayDeadline),
- refund_deadline: (defaultPayDeadline),
- wire_transfer_deadline: (defaultWireDeadline),
+ pay_deadline: defaultPayDeadline,
+ refund_deadline: defaultPayDeadline,
+ wire_transfer_deadline: defaultWireDeadline,
},
shipping: {},
extra: {},
@@ -114,26 +127,17 @@ interface Entity {
extra: Record<string, string>;
}
-const stringIsValidJSON = (value: string) => {
- try {
- JSON.parse(value.trim());
- return true;
- } catch {
- return false;
- }
-};
-
export function CreatePage({
onCreate,
onBack,
instanceConfig,
instanceInventory,
}: Props): VNode {
- const config = useConfigContext();
- const instance_default = with_defaults(instanceConfig, config.currency)
+ const { config } = useMerchantApiContext();
+ const instance_default = with_defaults(instanceConfig, config.currency);
const [value, valueHandler] = useState(instance_default);
const zero = Amounts.zeroOfCurrency(config.currency);
- const [settings, updateSettings] = usePreference()
+ const [settings, updateSettings] = usePreference();
const inventoryList = Object.values(value.inventoryProducts || {});
const productList = Object.values(value.products || {});
@@ -158,22 +162,25 @@ export function CreatePage({
refund_deadline: !value.payments?.refund_deadline
? undefined
: value.payments.pay_deadline &&
- Duration.cmp(value.payments.refund_deadline, value.payments.pay_deadline) === -1
- ? i18n.str`refund deadline cannot be before pay deadline`
- : value.payments.wire_transfer_deadline &&
Duration.cmp(
- value.payments.wire_transfer_deadline,
value.payments.refund_deadline,
+ value.payments.pay_deadline,
) === -1
+ ? i18n.str`refund deadline cannot be before pay deadline`
+ : value.payments.wire_transfer_deadline &&
+ Duration.cmp(
+ value.payments.wire_transfer_deadline,
+ value.payments.refund_deadline,
+ ) === -1
? i18n.str`wire transfer deadline cannot be before refund deadline`
: undefined,
pay_deadline: !value.payments?.pay_deadline
? i18n.str`required`
: value.payments.wire_transfer_deadline &&
- Duration.cmp(
- value.payments.wire_transfer_deadline,
- value.payments.pay_deadline,
- ) === -1
+ Duration.cmp(
+ value.payments.wire_transfer_deadline,
+ value.payments.pay_deadline,
+ ) === -1
? i18n.str`wire transfer deadline cannot be before pay deadline`
: undefined,
wire_transfer_deadline: !value.payments?.wire_transfer_deadline
@@ -184,12 +191,11 @@ export function CreatePage({
: !value.payments?.refund_deadline
? i18n.str`should have a refund deadline`
: Duration.cmp(
- value.payments.refund_deadline,
- value.payments.auto_refund_deadline,
- ) == -1
+ value.payments.refund_deadline,
+ value.payments.auto_refund_deadline,
+ ) == -1
? i18n.str`auto refund cannot be after refund deadline`
: undefined,
-
}),
shipping: undefinedIfEmpty({
delivery_date: !value.shipping?.delivery_date
@@ -214,18 +220,34 @@ export function CreatePage({
summary: order.pricing.summary,
products: productList,
extra: undefinedIfEmpty(value.extra),
- pay_deadline: !value.payments.pay_deadline ?
- i18n.str`required` :
- AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), value.payments.pay_deadline))
- ,// : undefined,
+ pay_deadline: !value.payments.pay_deadline
+ ? i18n.str`required`
+ : AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ value.payments.pay_deadline,
+ ),
+ ), // : undefined,
wire_transfer_deadline: value.payments.wire_transfer_deadline
- ? AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), value.payments.wire_transfer_deadline))
+ ? AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ value.payments.wire_transfer_deadline,
+ ),
+ )
: undefined,
refund_deadline: value.payments.refund_deadline
- ? AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), value.payments.refund_deadline))
+ ? AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ value.payments.refund_deadline,
+ ),
+ )
: undefined,
auto_refund: value.payments.auto_refund_deadline
- ? Duration.toTalerProtocolDuration(value.payments.auto_refund_deadline)
+ ? Duration.toTalerProtocolDuration(
+ value.payments.auto_refund_deadline,
+ )
: undefined,
max_fee: value.payments.max_fee as string,
@@ -301,7 +323,7 @@ export function CreatePage({
const totalAsString = Amounts.stringify(totalPrice.amount);
const allProducts = productList.concat(inventoryList.map(asProduct));
- const [newField, setNewField] = useState("")
+ const [newField, setNewField] = useState("");
useEffect(() => {
valueHandler((v) => {
@@ -328,37 +350,43 @@ export function CreatePage({
);
// if there is no default pay deadline
- const noDefault_payDeadline = !instance_default.payments || !instance_default.payments.pay_deadline
+ const noDefault_payDeadline =
+ !instance_default.payments || !instance_default.payments.pay_deadline;
// and there is no default wire deadline
- const noDefault_wireDeadline = !instance_default.payments || !instance_default.payments.wire_transfer_deadline
+ const noDefault_wireDeadline =
+ !instance_default.payments ||
+ !instance_default.payments.wire_transfer_deadline;
// user required to set the taler options
- const requiresSomeTalerOptions = noDefault_payDeadline || noDefault_wireDeadline
-
+ const requiresSomeTalerOptions =
+ noDefault_payDeadline || noDefault_wireDeadline;
return (
<div>
-
<section class="section is-main-section">
<div class="tabs is-toggle is-fullwidth is-small">
<ul>
- <li class={!settings.advanceOrderMode ? "is-active" : ""} onClick={() => {
- updateSettings({
- ...settings,
- advanceOrderMode: false
- })
- }}>
- <a >
- <span><i18n.Translate>Simple</i18n.Translate></span>
+ <li
+ class={!settings.advanceOrderMode ? "is-active" : ""}
+ onClick={() => {
+ updateSettings("advanceOrderMode", false);
+ }}
+ >
+ <a>
+ <span>
+ <i18n.Translate>Simple</i18n.Translate>
+ </span>
</a>
</li>
- <li class={settings.advanceOrderMode ? "is-active" : ""} onClick={() => {
- updateSettings({
- ...settings,
- advanceOrderMode: true
- })
- }}>
- <a >
- <span><i18n.Translate>Advanced</i18n.Translate></span>
+ <li
+ class={settings.advanceOrderMode ? "is-active" : ""}
+ onClick={() => {
+ updateSettings("advanceOrderMode", true);
+ }}
+ >
+ <a>
+ <span>
+ <i18n.Translate>Advanced</i18n.Translate>
+ </span>
</a>
</li>
</ul>
@@ -386,7 +414,7 @@ export function CreatePage({
inventory={instanceInventory}
/>
- {settings.advanceOrderMode &&
+ {settings.advanceOrderMode && (
<NonInventoryProductFrom
productToEdit={editingProduct}
onAddProduct={(p) => {
@@ -394,7 +422,7 @@ export function CreatePage({
return addNewProduct(p);
}}
/>
- }
+ )}
{allProducts.length > 0 && (
<ProductList
@@ -437,8 +465,8 @@ export function CreatePage({
discountOrRise > 0 &&
(discountOrRise < 1
? `discount of %${Math.round(
- (1 - discountOrRise) * 100,
- )}`
+ (1 - discountOrRise) * 100,
+ )}`
: `rise of %${Math.round((discountOrRise - 1) * 100)}`)
}
tooltip={i18n.str`Amount to be paid by the customer`}
@@ -459,7 +487,7 @@ export function CreatePage({
tooltip={i18n.str`Title of the order to be shown to the customer`}
/>
- {settings.advanceOrderMode &&
+ {settings.advanceOrderMode && (
<InputGroup
name="shipping"
label={i18n.str`Shipping and Fulfillment`}
@@ -485,146 +513,201 @@ export function CreatePage({
tooltip={i18n.str`URL to which the user will be redirected after successful payment.`}
/>
</InputGroup>
- }
+ )}
- {(settings.advanceOrderMode || requiresSomeTalerOptions) &&
+ {(settings.advanceOrderMode || requiresSomeTalerOptions) && (
<InputGroup
name="payments"
label={i18n.str`Taler payment options`}
tooltip={i18n.str`Override default Taler payment settings for this order`}
>
- {(settings.advanceOrderMode || noDefault_payDeadline) && <InputDuration
- name="payments.pay_deadline"
- label={i18n.str`Payment time`}
- help={<DeadlineHelp duration={value.payments?.pay_deadline} />}
- withForever
- withoutClear
- tooltip={i18n.str`Time for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline. Time start to run after the order is created.`}
- side={
- <span>
- <button class="button" onClick={() => {
- const c = {
- ...value,
- payments: {
- ...(value.payments ?? {}),
- pay_deadline: instance_default.payments?.pay_deadline
- }
- }
- valueHandler(c)
- }}>
- <i18n.Translate>default</i18n.Translate>
- </button>
- </span>
- }
- />}
- {settings.advanceOrderMode && <InputDuration
- name="payments.refund_deadline"
- label={i18n.str`Refund time`}
- help={<DeadlineHelp duration={value.payments?.refund_deadline} />}
- withForever
- withoutClear
- tooltip={i18n.str`Time while the order can be refunded by the merchant. Time starts after the order is created.`}
- side={
- <span>
- <button class="button" onClick={() => {
- valueHandler({
- ...value,
- payments: {
- ...(value.payments ?? {}),
- refund_deadline: instance_default.payments?.refund_deadline
- }
- })
- }}>
- <i18n.Translate>default</i18n.Translate>
- </button>
- </span>
- }
- />}
- {(settings.advanceOrderMode || noDefault_wireDeadline) && <InputDuration
- name="payments.wire_transfer_deadline"
- label={i18n.str`Wire transfer time`}
- help={<DeadlineHelp duration={value.payments?.wire_transfer_deadline} />}
- withoutClear
- withForever
- tooltip={i18n.str`Time for the exchange to make the wire transfer. Time starts after the order is created.`}
- side={
- <span>
- <button class="button" onClick={() => {
- valueHandler({
- ...value,
- payments: {
- ...(value.payments ?? {}),
- wire_transfer_deadline: instance_default.payments?.wire_transfer_deadline
- }
- })
- }}>
- <i18n.Translate>default</i18n.Translate>
- </button>
- </span>
- }
- />}
- {settings.advanceOrderMode && <InputDuration
- name="payments.auto_refund_deadline"
- label={i18n.str`Auto-refund time`}
- help={<DeadlineHelp duration={value.payments?.auto_refund_deadline} />}
- tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`}
- withForever
- />}
-
- {settings.advanceOrderMode && <InputCurrency
- name="payments.max_fee"
- label={i18n.str`Maximum fee`}
- tooltip={i18n.str`Maximum fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`}
- />}
- {settings.advanceOrderMode && <InputToggle
- name="payments.createToken"
- label={i18n.str`Create token`}
- tooltip={i18n.str`If the order ID is easy to guess the token will prevent user to steal orders from others.`}
- />}
- {settings.advanceOrderMode && <InputNumber
- name="payments.minimum_age"
- label={i18n.str`Minimum age required`}
- tooltip={i18n.str`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`}
- help={
- minAgeByProducts > 0
- ? i18n.str`Min age defined by the producs is ${minAgeByProducts}`
- : i18n.str`No product with age restriction in this order`
- }
- />}
+ {(settings.advanceOrderMode || noDefault_payDeadline) && (
+ <InputDuration
+ name="payments.pay_deadline"
+ label={i18n.str`Payment time`}
+ help={
+ <DeadlineHelp duration={value.payments?.pay_deadline} />
+ }
+ withForever
+ withoutClear
+ tooltip={i18n.str`Time for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline. Time start to run after the order is created.`}
+ side={
+ <span>
+ <button
+ class="button"
+ onClick={() => {
+ const c = {
+ ...value,
+ payments: {
+ ...(value.payments ?? {}),
+ pay_deadline:
+ instance_default.payments?.pay_deadline,
+ },
+ };
+ valueHandler(c);
+ }}
+ >
+ <i18n.Translate>default</i18n.Translate>
+ </button>
+ </span>
+ }
+ />
+ )}
+ {settings.advanceOrderMode && (
+ <InputDuration
+ name="payments.refund_deadline"
+ label={i18n.str`Refund time`}
+ help={
+ <DeadlineHelp
+ duration={value.payments?.refund_deadline}
+ />
+ }
+ withForever
+ withoutClear
+ tooltip={i18n.str`Time while the order can be refunded by the merchant. Time starts after the order is created.`}
+ side={
+ <span>
+ <button
+ class="button"
+ onClick={() => {
+ valueHandler({
+ ...value,
+ payments: {
+ ...(value.payments ?? {}),
+ refund_deadline:
+ instance_default.payments?.refund_deadline,
+ },
+ });
+ }}
+ >
+ <i18n.Translate>default</i18n.Translate>
+ </button>
+ </span>
+ }
+ />
+ )}
+ {(settings.advanceOrderMode || noDefault_wireDeadline) && (
+ <InputDuration
+ name="payments.wire_transfer_deadline"
+ label={i18n.str`Wire transfer time`}
+ help={
+ <DeadlineHelp
+ duration={value.payments?.wire_transfer_deadline}
+ />
+ }
+ withoutClear
+ withForever
+ tooltip={i18n.str`Time for the exchange to make the wire transfer. Time starts after the order is created.`}
+ side={
+ <span>
+ <button
+ class="button"
+ onClick={() => {
+ valueHandler({
+ ...value,
+ payments: {
+ ...(value.payments ?? {}),
+ wire_transfer_deadline:
+ instance_default.payments
+ ?.wire_transfer_deadline,
+ },
+ });
+ }}
+ >
+ <i18n.Translate>default</i18n.Translate>
+ </button>
+ </span>
+ }
+ />
+ )}
+ {settings.advanceOrderMode && (
+ <InputDuration
+ name="payments.auto_refund_deadline"
+ label={i18n.str`Auto-refund time`}
+ help={
+ <DeadlineHelp
+ duration={value.payments?.auto_refund_deadline}
+ />
+ }
+ tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`}
+ withForever
+ />
+ )}
+
+ {settings.advanceOrderMode && (
+ <InputCurrency
+ name="payments.max_fee"
+ label={i18n.str`Maximum fee`}
+ tooltip={i18n.str`Maximum fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`}
+ />
+ )}
+ {settings.advanceOrderMode && (
+ <InputToggle
+ name="payments.createToken"
+ label={i18n.str`Create token`}
+ tooltip={i18n.str`If the order ID is easy to guess the token will prevent user to steal orders from others.`}
+ />
+ )}
+ {settings.advanceOrderMode && (
+ <InputNumber
+ name="payments.minimum_age"
+ label={i18n.str`Minimum age required`}
+ tooltip={i18n.str`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`}
+ help={
+ minAgeByProducts > 0
+ ? i18n.str`Min age defined by the producs is ${minAgeByProducts}`
+ : i18n.str`No product with age restriction in this order`
+ }
+ />
+ )}
</InputGroup>
- }
+ )}
- {settings.advanceOrderMode &&
+ {settings.advanceOrderMode && (
<InputGroup
name="extra"
label={i18n.str`Additional information`}
tooltip={i18n.str`Custom information to be included in the contract for this order.`}
>
- {Object.keys(value.extra ?? {}).map((key) => {
-
- return <Input
- name={`extra.${key}`}
- inputType="multiline"
- label={key}
- tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`}
- side={
- <button class="button" onClick={(e) => {
- if (value.extra && value.extra[key] !== undefined) {
- console.log(value.extra)
- delete value.extra[key]
- }
- valueHandler({
- ...value,
- })
- }}>remove</button>
- }
- />
+ {Object.keys(value.extra ?? {}).map((key, idx) => {
+ return (
+ <Input
+ name={`extra.${key}`}
+ key={String(idx)}
+ inputType="multiline"
+ label={key}
+ tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`}
+ side={
+ <button
+ class="button"
+ onClick={(e) => {
+ if (
+ value.extra &&
+ value.extra[key] !== undefined
+ ) {
+ console.log(value.extra);
+ delete value.extra[key];
+ }
+ valueHandler({
+ ...value,
+ });
+ e.preventDefault();
+ }}
+ >
+ remove
+ </button>
+ }
+ />
+ );
})}
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">
<i18n.Translate>Custom field name</i18n.Translate>
- <span class="icon has-tooltip-right" data-tooltip={"new extra field"}>
+ <span
+ class="icon has-tooltip-right"
+ data-tooltip={"new extra field"}
+ >
<i class="mdi mdi-information" />
</span>
</label>
@@ -632,23 +715,33 @@ export function CreatePage({
<div class="field-body is-flex-grow-3">
<div class="field">
<p class="control">
- <input class="input " value={newField} onChange={(e) => setNewField(e.currentTarget.value)} />
+ <input
+ class="input "
+ value={newField}
+ onChange={(e) => setNewField(e.currentTarget.value)}
+ />
</p>
</div>
</div>
- <button class="button" onClick={(e) => {
- setNewField("")
- valueHandler({
- ...value,
- extra: {
- ...(value.extra ?? {}),
- [newField]: ""
- }
- })
- }}>add</button>
+ <button
+ class="button"
+ onClick={(e) => {
+ setNewField("");
+ valueHandler({
+ ...value,
+ extra: {
+ ...(value.extra ?? {}),
+ [newField]: "",
+ },
+ });
+ e.preventDefault();
+ }}
+ >
+ add
+ </button>
</div>
</InputGroup>
- }
+ )}
</FormProvider>
<div class="buttons is-right mt-5">
@@ -686,20 +779,24 @@ function asProduct(p: ProductAndQuantity): MerchantBackend.Product {
};
}
-
function DeadlineHelp({ duration }: { duration?: Duration }): VNode {
const { i18n } = useTranslationContext();
- const [now, setNow] = useState(AbsoluteTime.now())
+ const [now, setNow] = useState(AbsoluteTime.now());
useEffect(() => {
const iid = setInterval(() => {
- setNow(AbsoluteTime.now())
- }, 60 * 1000)
+ setNow(AbsoluteTime.now());
+ }, 60 * 1000);
return () => {
- clearInterval(iid)
- }
- })
- if (!duration) return <i18n.Translate>Disabled</i18n.Translate>
- const when = AbsoluteTime.addDuration(now, duration)
- if (when.t_ms === "never") return <i18n.Translate>No deadline</i18n.Translate>
- return <i18n.Translate>Deadline at {format(when.t_ms, "dd/MM/yy HH:mm")}</i18n.Translate>
+ clearInterval(iid);
+ };
+ });
+ if (!duration) return <i18n.Translate>Disabled</i18n.Translate>;
+ const when = AbsoluteTime.addDuration(now, duration);
+ if (when.t_ms === "never")
+ return <i18n.Translate>No deadline</i18n.Translate>;
+ return (
+ <i18n.Translate>
+ Deadline at {format(when.t_ms, "dd/MM/yy HH:mm")}
+ </i18n.Translate>
+ );
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
index 1efaaf6e0..69e9df52e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx
@@ -20,7 +20,7 @@
*/
import { AmountJson, Amounts, stringifyRefundUri } from "@gnu-taler/taler-util";
-import { useMerchantApiContext, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { format, formatDistance } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
@@ -33,6 +33,7 @@ import { InputGroup } from "../../../../components/form/InputGroup.js";
import { InputLocation } from "../../../../components/form/InputLocation.js";
import { TextField } from "../../../../components/form/TextField.js";
import { ProductList } from "../../../../components/product/ProductList.js";
+import { useSessionContext } from "../../../../context/session.js";
import { MerchantBackend } from "../../../../declaration.js";
import { datetimeFormatForSettings, usePreference } from "../../../../hooks/preference.js";
import { mergeRefunds } from "../../../../utils/amount.js";
@@ -415,10 +416,12 @@ function PaidPage({
})
const [value, valueHandler] = useState<Partial<Paid>>(order);
- const { url: backendURL } = useMerchantApiContext();
+ const {
+ state: { backendUrl },
+ } = useSessionContext();
const refundurl = stringifyRefundUri({
- merchantBaseUrl: backendURL.href,
+ merchantBaseUrl: backendUrl,
orderId: order.contract_terms.order_id
})
const refundable =
@@ -764,7 +767,3 @@ export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode {
</Fragment>
);
}
-
-async function copyToClipboard(text: string) {
- return navigator.clipboard.writeText(text);
-}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
index 87e84945c..cebc4afe6 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx
@@ -20,7 +20,10 @@
*/
import { Amounts } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+ useMerchantApiContext,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
import { h, VNode } from "preact";
import { StateUpdater, useState } from "preact/hooks";
@@ -33,10 +36,12 @@ import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputGroup } from "../../../../components/form/InputGroup.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { ConfirmModal } from "../../../../components/modal/index.js";
-import { useConfigContext } from "../../../../context/config.js";
import { MerchantBackend, WithId } from "../../../../declaration.js";
import { mergeRefunds } from "../../../../utils/amount.js";
-import { datetimeFormatForSettings, usePreference } from "../../../../hooks/preference.js";
+import {
+ datetimeFormatForSettings,
+ usePreference,
+} from "../../../../hooks/preference.js";
type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId;
interface Props {
@@ -141,10 +146,7 @@ function Table({
return (
<div class="table-container">
{hasMoreBefore && (
- <button
- class="button is-fullwidth"
- onClick={onLoadMoreBefore}
- >
+ <button class="button is-fullwidth" onClick={onLoadMoreBefore}>
<i18n.Translate>load newer orders</i18n.Translate>
</button>
)}
@@ -174,9 +176,9 @@ function Table({
{i.timestamp.t_s === "never"
? "never"
: format(
- new Date(i.timestamp.t_s * 1000),
- datetimeFormatForSettings(settings),
- )}
+ new Date(i.timestamp.t_s * 1000),
+ datetimeFormatForSettings(settings),
+ )}
</td>
<td
onClick={(): void => onSelect(i)}
@@ -218,10 +220,7 @@ function Table({
</tbody>
</table>
{hasMoreAfter && (
- <button
- class="button is-fullwidth"
- onClick={onLoadMoreAfter}
- >
+ <button class="button is-fullwidth" onClick={onLoadMoreAfter}>
<i18n.Translate>load older orders</i18n.Translate>
</button>
)}
@@ -268,7 +267,7 @@ export function RefundModal({
order.order_status === "paid" ? order.refund_details : []
).reduce(mergeRefunds, []);
- const config = useConfigContext();
+ const { config } = useMerchantApiContext();
const totalRefunded = refunds
.map((r) => r.amount)
.reduce(
@@ -362,9 +361,9 @@ export function RefundModal({
{r.timestamp.t_s === "never"
? "never"
: format(
- new Date(r.timestamp.t_s * 1000),
- datetimeFormatForSettings(settings),
- )}
+ new Date(r.timestamp.t_s * 1000),
+ datetimeFormatForSettings(settings),
+ )}
</td>
<td>{r.amount}</td>
<td>{r.reason}</td>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
index 83345de3e..930a0d82c 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
@@ -34,7 +34,6 @@ import {
import { Input } from "../../../../components/form/Input.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
-import { useBackendContext } from "../../../../context/backend.js";
import { MerchantBackend } from "../../../../declaration.js";
type Entity = MerchantBackend.OTP.OtpDeviceAddDetails;
@@ -49,7 +48,6 @@ const algorithmsNames = ["off", "30s 8d TOTP-SHA1", "30s 8d eTOTP-SHA1"];
export function CreatePage({ onCreate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
- const backend = useBackendContext();
const [state, setState] = useState<Partial<Entity>>({});
@@ -145,6 +143,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
...s,
otp_key: randomRfc3548Base32Key(),
}));
+ e.preventDefault();
}}
>
<i18n.Translate>random</i18n.Translate>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx
index c6591cdbe..60abc3ca6 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx
@@ -15,12 +15,11 @@
*/
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { VNode, h } from "preact";
import { QR } from "../../../../components/exception/QR.js";
import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js";
-import { useInstanceContext } from "../../../../context/instance.js";
+import { useSessionContext } from "../../../../context/session.js";
import { MerchantBackend } from "../../../../declaration.js";
-import { useBackendContext } from "../../../../context/backend.js";
type Entity = MerchantBackend.OTP.OtpDeviceAddDetails;
@@ -34,11 +33,13 @@ export function CreatedSuccessfully({
onConfirm,
}: Props): VNode {
const { i18n } = useTranslationContext();
- const { url: backendURL } = useBackendContext()
- const { id: instanceId } = useInstanceContext();
- const issuer = new URL(backendURL).hostname;
- const qrText = `otpauth://totp/${instanceId}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key}`;
- const qrTextSafe = `otpauth://totp/${instanceId}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key.substring(0, 6)}...`;
+ const {
+ state: { backendUrl },
+ } = useSessionContext();
+ const { state } = useSessionContext();
+ const issuer = backendUrl;
+ const qrText = `otpauth://totp/${state.instance}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key}`;
+ const qrTextSafe = `otpauth://totp/${state.instance}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key.substring(0, 6)}...`;
return (
<Template onConfirm={onConfirm} >
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
index d27f6a022..b07582252 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
@@ -23,11 +23,12 @@ import {
AmountString,
Amounts,
Duration,
- MerchantTemplateContractDetails,
assertUnreachable,
} from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import {
+ useTranslationContext
+} from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import {
@@ -39,12 +40,11 @@ import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputDuration } from "../../../../components/form/InputDuration.js";
import { InputNumber } from "../../../../components/form/InputNumber.js";
import { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js";
+import { InputTab } from "../../../../components/form/InputTab.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
-import { useBackendContext } from "../../../../context/backend.js";
+import { useSessionContext } from "../../../../context/session.js";
import { MerchantBackend } from "../../../../declaration.js";
import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
-import { undefinedIfEmpty } from "../../../../utils/table.js";
-import { InputTab } from "../../../../components/form/InputTab.js";
enum Steps {
BOTH_FIXED,
@@ -55,14 +55,14 @@ enum Steps {
// type Entity = MerchantBackend.Template.TemplateAddDetails & { type: Steps };
type Entity = {
- id?: string,
- description?: string,
- otpId?: string,
- summary?: string,
- amount?: AmountString,
- minimum_age?: number,
- pay_duration?: Duration,
- type: Steps,
+ id?: string;
+ description?: string;
+ otpId?: string;
+ summary?: string;
+ amount?: AmountString;
+ minimum_age?: number;
+ pay_duration?: Duration;
+ type: Steps;
};
interface Props {
@@ -72,8 +72,10 @@ interface Props {
export function CreatePage({ onCreate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
- const { url: backendURL } = useBackendContext()
- const devices = useInstanceOtpDevices()
+ const {
+ state: { backendUrl },
+ } = useSessionContext();
+ const devices = useInstanceOtpDevices();
const [state, setState] = useState<Partial<Entity>>({
minimum_age: 0,
@@ -83,9 +85,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
type: Steps.NON_FIXED,
});
- const parsedPrice = !state.amount
- ? undefined
- : Amounts.parse(state.amount);
+ const parsedPrice = !state.amount ? undefined : Amounts.parse(state.amount);
const errors: FormErrors<Entity> = {
id: !state.id
@@ -93,10 +93,10 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
: !/[a-zA-Z0-9]*/.test(state.id)
? i18n.str`no valid. only characters and numbers`
: undefined,
- description: !state.description
- ? i18n.str`should not be empty`
- : undefined,
- amount: !(state.type === Steps.FIXED_PRICE || state.type === Steps.BOTH_FIXED)
+ description: !state.description ? i18n.str`should not be empty` : undefined,
+ amount: !(
+ state.type === Steps.FIXED_PRICE || state.type === Steps.BOTH_FIXED
+ )
? undefined
: !state.amount
? i18n.str`required`
@@ -105,7 +105,9 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
: Amounts.isZero(parsedPrice)
? i18n.str`must be greater than 0`
: undefined,
- summary: !(state.type === Steps.FIXED_SUMMARY || state.type === Steps.BOTH_FIXED)
+ summary: !(
+ state.type === Steps.FIXED_SUMMARY || state.type === Steps.BOTH_FIXED
+ )
? undefined
: !state.summary
? i18n.str`required`
@@ -130,55 +132,60 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
const submitForm = () => {
if (hasErrors || state.type === undefined) return Promise.reject();
switch (state.type) {
- case Steps.FIXED_PRICE: return onCreate({
- template_id: state.id!,
- template_description: state.description!,
- template_contract: {
- minimum_age: state.minimum_age!,
- pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
- amount: state.amount!,
- // summary: state.summary,
- },
- otp_id: state.otpId!
- })
- case Steps.FIXED_SUMMARY: return onCreate({
- template_id: state.id!,
- template_description: state.description!,
- template_contract: {
- minimum_age: state.minimum_age!,
- pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
- // amount: state.amount!,
- summary: state.summary,
- },
- otp_id: state.otpId!,
- })
- case Steps.NON_FIXED: return onCreate({
- template_id: state.id!,
- template_description: state.description!,
- template_contract: {
- minimum_age: state.minimum_age!,
- pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
- // amount: state.amount!,
- // summary: state.summary,
- },
- otp_id: state.otpId!,
- })
- case Steps.BOTH_FIXED: return onCreate({
- template_id: state.id!,
- template_description: state.description!,
- template_contract: {
- minimum_age: state.minimum_age!,
- pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
- amount: state.amount!,
- summary: state.summary,
- },
- otp_id: state.otpId!,
- })
- default: assertUnreachable(state.type)
+ case Steps.FIXED_PRICE:
+ return onCreate({
+ template_id: state.id!,
+ template_description: state.description!,
+ template_contract: {
+ minimum_age: state.minimum_age!,
+ pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+ amount: state.amount!,
+ // summary: state.summary,
+ },
+ otp_id: state.otpId!,
+ });
+ case Steps.FIXED_SUMMARY:
+ return onCreate({
+ template_id: state.id!,
+ template_description: state.description!,
+ template_contract: {
+ minimum_age: state.minimum_age!,
+ pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+ // amount: state.amount!,
+ summary: state.summary,
+ },
+ otp_id: state.otpId!,
+ });
+ case Steps.NON_FIXED:
+ return onCreate({
+ template_id: state.id!,
+ template_description: state.description!,
+ template_contract: {
+ minimum_age: state.minimum_age!,
+ pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+ // amount: state.amount!,
+ // summary: state.summary,
+ },
+ otp_id: state.otpId!,
+ });
+ case Steps.BOTH_FIXED:
+ return onCreate({
+ template_id: state.id!,
+ template_description: state.description!,
+ template_contract: {
+ minimum_age: state.minimum_age!,
+ pay_duration: Duration.toTalerProtocolDuration(state.pay_duration!),
+ amount: state.amount!,
+ summary: state.summary,
+ },
+ otp_id: state.otpId!,
+ });
+ default:
+ assertUnreachable(state.type);
// return onCreate(state);
- };
- }
- const deviceList = !devices.ok ? [] : devices.data.otp_devices
+ }
+ };
+ const deviceList = !devices.ok ? [] : devices.data.otp_devices;
return (
<div>
@@ -193,7 +200,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
>
<InputWithAddon<Entity>
name="id"
- help={`${backendURL}/templates/${state.id ?? ""}`}
+ help={new URL(`templates/${state.id ?? ""}`, backendUrl).href}
label={i18n.str`Identifier`}
tooltip={i18n.str`Name of the template in URLs.`}
/>
@@ -207,12 +214,16 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
name="type"
label={i18n.str`Type`}
help={(() => {
- if (state.type === undefined) return ""
+ if (state.type === undefined) return "";
switch (state.type) {
- case Steps.NON_FIXED: return i18n.str`User will be able to input price and summary before payment.`
- case Steps.FIXED_PRICE: return i18n.str`User will be able to add a summary before payment.`
- case Steps.FIXED_SUMMARY: return i18n.str`User will be able to set the price before payment.`
- case Steps.BOTH_FIXED: return i18n.str`User will not be able to change the price or the summary.`
+ case Steps.NON_FIXED:
+ return i18n.str`User will be able to input price and summary before payment.`;
+ case Steps.FIXED_PRICE:
+ return i18n.str`User will be able to add a summary before payment.`;
+ case Steps.FIXED_SUMMARY:
+ return i18n.str`User will be able to set the price before payment.`;
+ case Steps.BOTH_FIXED:
+ return i18n.str`User will not be able to change the price or the summary.`;
}
})()}
tooltip={i18n.str`Define what the user be allowed to modify`}
@@ -224,28 +235,34 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
]}
toStr={(v: Steps): string => {
switch (v) {
- case Steps.NON_FIXED: return i18n.str`Simple`
- case Steps.FIXED_PRICE: return i18n.str`With price`
- case Steps.FIXED_SUMMARY: return i18n.str`With summary`
- case Steps.BOTH_FIXED: return i18n.str`With price and summary`
+ case Steps.NON_FIXED:
+ return i18n.str`Simple`;
+ case Steps.FIXED_PRICE:
+ return i18n.str`With price`;
+ case Steps.FIXED_SUMMARY:
+ return i18n.str`With summary`;
+ case Steps.BOTH_FIXED:
+ return i18n.str`With price and summary`;
}
}}
/>
- {state.type === Steps.BOTH_FIXED || state.type === Steps.FIXED_SUMMARY ?
+ {state.type === Steps.BOTH_FIXED ||
+ state.type === Steps.FIXED_SUMMARY ? (
<Input<Entity>
name="summary"
inputType="multiline"
label={i18n.str`Fixed summary`}
tooltip={i18n.str`If specified, this template will create order with the same summary`}
/>
- : undefined}
- {state.type === Steps.BOTH_FIXED || state.type === Steps.FIXED_PRICE ?
+ ) : undefined}
+ {state.type === Steps.BOTH_FIXED ||
+ state.type === Steps.FIXED_PRICE ? (
<InputCurrency<Entity>
name="amount"
label={i18n.str`Fixed price`}
tooltip={i18n.str`If specified, this template will create order with the same price`}
/>
- : undefined}
+ ) : undefined}
<InputNumber<Entity>
name="minimum_age"
label={i18n.str`Minimum age`}
@@ -262,28 +279,29 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
name="otpId"
label={i18n.str`OTP device`}
readonly
- side={<button
- class="button is-danger"
- data-tooltip={i18n.str`without otp device`}
- onClick={(): void => {
- setState((v) => ({ ...v, otpId: undefined }));
- }}
- >
- <span>
- <i18n.Translate>remove</i18n.Translate>
- </span>
- </button>}
+ side={
+ <button
+ class="button is-danger"
+ data-tooltip={i18n.str`without otp device`}
+ onClick={(): void => {
+ setState((v) => ({ ...v, otpId: undefined }));
+ }}
+ >
+ <span>
+ <i18n.Translate>remove</i18n.Translate>
+ </span>
+ </button>
+ }
tooltip={i18n.str`Use to verify transaction in offline mode.`}
/>
<InputSearchOnList
label={i18n.str`Search device`}
onChange={(p) => setState((v) => ({ ...v, otpId: p?.id }))}
- list={deviceList.map(e => ({
+ list={deviceList.map((e) => ({
description: e.device_description,
- id: e.otp_device_id
+ id: e.otp_device_id,
}))}
/>
-
</FormProvider>
<div class="buttons is-right mt-5">
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
index 809151565..1aa5bc317 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx
@@ -20,7 +20,10 @@
*/
import { stringifyPayTemplateUri } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+ useMerchantApiContext,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { QR } from "../../../../components/exception/QR.js";
@@ -30,9 +33,7 @@ import {
} from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
-import { useBackendContext } from "../../../../context/backend.js";
-import { useConfigContext } from "../../../../context/config.js";
-import { useInstanceContext } from "../../../../context/instance.js";
+import { useSessionContext } from "../../../../context/session.js";
import { MerchantBackend } from "../../../../declaration.js";
type Entity = MerchantBackend.Template.UsingTemplateDetails;
@@ -45,9 +46,10 @@ interface Props {
export function QrPage({ contract, id: templateId, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
- const { url: backendURL } = useBackendContext()
- const { id: instanceId } = useInstanceContext();
- const config = useConfigContext();
+ const {
+ state: { backendUrl },
+ } = useSessionContext();
+ const { config } = useMerchantApiContext();
const [state, setState] = useState<Partial<Entity>>({
amount: contract.amount,
@@ -59,30 +61,26 @@ export function QrPage({ contract, id: templateId, onBack }: Props): VNode {
const fixedAmount = !!contract.amount;
const fixedSummary = !!contract.summary;
- const templateParams: Record<string, string> = {}
+ const templateParams: Record<string, string> = {};
if (!fixedAmount) {
if (state.amount) {
- templateParams.amount = state.amount
+ templateParams.amount = state.amount;
} else {
- templateParams.amount = config.currency
+ templateParams.amount = config.currency;
}
}
if (!fixedSummary) {
- templateParams.summary = state.summary ?? ""
+ templateParams.summary = state.summary ?? "";
}
- const merchantBaseUrl = new URL(backendURL).href;
+ const merchantBaseUrl = backendUrl;
const payTemplateUri = stringifyPayTemplateUri({
merchantBaseUrl,
templateId,
- templateParams
- })
-
- const issuer = encodeURIComponent(
- `${new URL(backendURL).host}/${instanceId}`,
- );
+ templateParams,
+ });
return (
<div>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
index cdf2ebab4..ae11ad991 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx
@@ -26,7 +26,7 @@ import {
assertUnreachable
} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import {
@@ -37,11 +37,10 @@ import { Input } from "../../../../components/form/Input.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputDuration } from "../../../../components/form/InputDuration.js";
import { InputNumber } from "../../../../components/form/InputNumber.js";
+import { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js";
import { InputTab } from "../../../../components/form/InputTab.js";
-import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
-import { useBackendContext } from "../../../../context/backend.js";
+import { useSessionContext } from "../../../../context/session.js";
import { MerchantBackend } from "../../../../declaration.js";
-import { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js";
import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
enum Steps {
@@ -68,7 +67,10 @@ interface Props {
export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
- const { url: backendURL } = useBackendContext()
+ const {
+ state: { backendUrl },
+ } = useSessionContext();
+
const intialStep =
template.template_contract.amount === undefined && template.template_contract.summary === undefined
@@ -187,7 +189,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
<div class="level-left">
<div class="level-item">
<span class="is-size-4">
- {backendURL}/templates/{template.otp_id}
+ {new URL(`templates/${template.otp_id}`,backendUrl).href}
</span>
</div>
</div>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
index 1e9186624..f2b1db29b 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
@@ -25,19 +25,23 @@ import { useState } from "preact/hooks";
import { AsyncButton } from "../../../components/exception/AsyncButton.js";
import { FormProvider } from "../../../components/form/FormProvider.js";
import { Input } from "../../../components/form/Input.js";
-import { useInstanceContext } from "../../../context/instance.js";
-import { AccessToken } from "../../../declaration.js";
import { NotificationCard } from "../../../components/menu/index.js";
+import { useSessionContext } from "../../../context/session.js";
+import { AccessToken } from "@gnu-taler/taler-util";
interface Props {
- instanceId: string;
hasToken: boolean | undefined;
onClearToken: (c: AccessToken | undefined) => void;
onNewToken: (c: AccessToken | undefined, s: AccessToken) => void;
onBack?: () => void;
}
-export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearToken }: Props): VNode {
+export function DetailPage({
+ hasToken,
+ onBack,
+ onNewToken,
+ onClearToken,
+}: Props): VNode {
type State = { old_token: string; new_token: string; repeat_token: string };
const [form, setValue] = useState<Partial<State>>({
old_token: "",
@@ -47,9 +51,10 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
const { i18n } = useTranslationContext();
const errors = {
- old_token: hasToken && !form.old_token
- ? i18n.str`you need your access token to perform the operation`
- : undefined,
+ old_token:
+ hasToken && !form.old_token
+ ? i18n.str`you need your access token to perform the operation`
+ : undefined,
new_token: !form.new_token
? i18n.str`cannot be empty`
: form.new_token === form.old_token
@@ -65,15 +70,17 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
(k) => (errors as any)[k] !== undefined,
);
- const instance = useInstanceContext();
+ const { state } = useSessionContext();
- const text = i18n.str`You are updating the access token from instance with id "${instance.id}"`;
+ const text = i18n.str`You are updating the access token from instance with id "${state.instance}"`;
async function submitForm() {
if (hasErrors) return;
- const oldToken = hasToken ? `secret-token:${form.old_token}` as AccessToken : undefined;
+ const oldToken = hasToken
+ ? (`secret-token:${form.old_token}` as AccessToken)
+ : undefined;
const newToken = `secret-token:${form.new_token}` as AccessToken;
- onNewToken(oldToken, newToken)
+ onNewToken(oldToken, newToken);
}
return (
@@ -84,9 +91,7 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
<div class="level">
<div class="level-left">
<div class="level-item">
- <span class="is-size-4">
- {text}
- </span>
+ <span class="is-size-4">{text}</span>
</div>
</div>
</div>
@@ -94,7 +99,7 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
</section>
<hr />
- {!hasToken &&
+ {!hasToken && (
<NotificationCard
notification={{
message: i18n.str`This instance doesn't have authentication token.`,
@@ -102,7 +107,7 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
type: "WARN",
}}
/>
- }
+ )}
<div class="columns">
<div class="column" />
@@ -119,7 +124,8 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
/>
<p>
<i18n.Translate>
- Clearing the access token will mean public access to the instance.
+ Clearing the access token will mean public access to the
+ instance.
</i18n.Translate>
</p>
<div class="buttons is-right mt-5">
@@ -127,10 +133,11 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
class="button"
onClick={() => {
if (hasToken) {
- const oldToken = `secret-token:${form.old_token}` as AccessToken;
- onClearToken(oldToken)
+ const oldToken =
+ `secret-token:${form.old_token}` as AccessToken;
+ onClearToken(oldToken);
} else {
- onClearToken(undefined)
+ onClearToken(undefined);
}
}}
>
@@ -140,7 +147,6 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
</Fragment>
)}
-
<Input<State>
name="new_token"
label={i18n.str`New access token`}
@@ -176,7 +182,6 @@ export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearTo
</div>
<div class="column" />
</div>
-
</section>
</div>
);
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
index 13642ec22..d7bf7a6d5 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
@@ -16,15 +16,13 @@
import { HttpStatusCode } from "@gnu-taler/taler-util";
import { ErrorType, HttpError, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
-import { Loading } from "../../../components/exception/loading.js";
-import { AccessToken, MerchantBackend } from "../../../declaration.js";
-import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance.js";
-import { DetailPage } from "./DetailPage.js";
-import { useInstanceContext } from "../../../context/instance.js";
import { useState } from "preact/hooks";
+import { Loading } from "../../../components/exception/loading.js";
import { NotificationCard } from "../../../components/menu/index.js";
+import { MerchantBackend } from "../../../declaration.js";
+import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance.js";
import { Notification } from "../../../utils/types.js";
-import { useBackendContext } from "../../../context/backend.js";
+import { DetailPage } from "./DetailPage.js";
interface Props {
onUnauthorized: () => VNode;
@@ -45,7 +43,6 @@ export default function Token({
const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { clearAccessToken, setNewAccessToken } = useInstanceAPI();
- const { id } = useInstanceContext();
const result = useInstanceDetails()
if (result.loading) return <Loading />;
@@ -69,7 +66,6 @@ export default function Token({
<Fragment>
<NotificationCard notification={notif} />
<DetailPage
- instanceId={id}
onBack={onCancel}
hasToken={hasToken}
onClearToken={async (currentToken): Promise<void> => {
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
index eb25045a0..576c21cd2 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx
@@ -20,7 +20,7 @@
*/
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { h, VNode } from "preact";
+import { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import {
@@ -30,7 +30,6 @@ import {
import { Input } from "../../../../components/form/Input.js";
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
-import { useConfigContext } from "../../../../context/config.js";
import { MerchantBackend } from "../../../../declaration.js";
import {
CROCKFORD_BASE32_REGEX,
@@ -47,7 +46,6 @@ interface Props {
export function CreatePage({ accounts, onCreate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
- const { currency } = useConfigContext();
const [state, setState] = useState<Partial<Entity>>({
wtid: "",
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
index ff0d55d2d..f0f0bfac9 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx
@@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { Duration } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
@@ -28,10 +29,9 @@ import {
FormProvider,
} from "../../../components/form/FormProvider.js";
import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js";
-import { useInstanceContext } from "../../../context/instance.js";
import { MerchantBackend } from "../../../declaration.js";
import { undefinedIfEmpty } from "../../../utils/table.js";
-import { Duration } from "@gnu-taler/taler-util";
+import { useSessionContext } from "../../../context/session.js";
export type Entity = Omit<Omit<MerchantBackend.Instances.InstanceReconfigurationMessage, "default_pay_delay">, "default_wire_transfer_delay"> & {
default_pay_delay: Duration,
@@ -64,7 +64,7 @@ export function UpdatePage({
selected,
onBack,
}: Props): VNode {
- const { id } = useInstanceContext();
+ const { state } = useSessionContext();
const [value, valueHandler] = useState<Partial<Entity>>(convert(selected));
@@ -125,7 +125,7 @@ export function UpdatePage({
<div class="level-left">
<div class="level-item">
<span class="is-size-4">
- <i18n.Translate>Instance id</i18n.Translate>: <b>{id}</b>
+ <i18n.Translate>Instance id</i18n.Translate>: <b>{state.instance}</b>
</span>
</div>
</div>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
index be3793ac3..de1371974 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx
@@ -24,8 +24,7 @@ import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { Loading } from "../../../components/exception/loading.js";
import { NotificationCard } from "../../../components/menu/index.js";
-import { useInstanceContext } from "../../../context/instance.js";
-import { AccessToken, MerchantBackend } from "../../../declaration.js";
+import { MerchantBackend } from "../../../declaration.js";
import {
useInstanceAPI,
useInstanceDetails,
@@ -65,7 +64,6 @@ function CommonUpdate(
onConfirm,
onLoadError,
onNotFound,
- onUpdateError,
onUnauthorized,
}: Props,
result: HttpResponse<
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
index b89e5e6bf..83604711e 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx
@@ -28,12 +28,8 @@ import {
FormProvider,
} from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
-import { InputCurrency } from "../../../../components/form/InputCurrency.js";
-import { InputDuration } from "../../../../components/form/InputDuration.js";
-import { InputNumber } from "../../../../components/form/InputNumber.js";
-import { useBackendContext } from "../../../../context/backend.js";
-import { MerchantBackend } from "../../../../declaration.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
+import { MerchantBackend } from "../../../../declaration.js";
type Entity = MerchantBackend.Webhooks.WebhookAddDetails;
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
index 304ac90f3..be21629d5 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/UpdatePage.tsx
@@ -28,7 +28,6 @@ import {
FormProvider,
} from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
-import { useBackendContext } from "../../../../context/backend.js";
import { MerchantBackend, WithId } from "../../../../declaration.js";
type Entity = MerchantBackend.Webhooks.WebhookPatchDetails & WithId;
diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx
index d94b7e506..1c0b915bd 100644
--- a/packages/merchant-backoffice-ui/src/paths/login/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/login/index.tsx
@@ -19,7 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { HttpStatusCode } from "@gnu-taler/taler-util";
+import {
+ AccessToken,
+ HttpStatusCode,
+ TalerAuthentication,
+} from "@gnu-taler/taler-util";
import {
useMerchantApiContext,
useTranslationContext,
@@ -27,40 +31,68 @@ import {
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { NotificationCard } from "../../components/menu/index.js";
-import { AccessToken } from "../../declaration.js";
-import { DEFAULT_ADMIN_USERNAME, useSessionState } from "../../hooks/session.js";
+import {
+ DEFAULT_ADMIN_USERNAME,
+ useSessionContext,
+} from "../../context/session.js";
import { Notification } from "../../utils/types.js";
-interface Props {
-}
+interface Props {}
-function normalizeToken(r: string): AccessToken {
- return `secret-token:${r}` as AccessToken;
-}
+const tokenRequest = {
+ scope: "write",
+ duration: {
+ d_us: "forever" as const,
+ },
+ refreshable: true,
+};
export function LoginPage(_p: Props): VNode {
const [token, setToken] = useState("");
const [notif, setNotif] = useState<Notification | undefined>(undefined);
- const { state, logIn } = useSessionState();
+ const { state, logIn } = useSessionContext();
const { lib } = useMerchantApiContext();
const { i18n } = useTranslationContext();
+ async function doImpersonateImpl(instanceId: string) {
+ const result = await lib
+ .impersonate(instanceId)
+ .createAccessTokenMerchant(token, tokenRequest);
+ if (result.type === "ok") {
+ const { token } = result.body;
+ logIn({ token });
+ return;
+ } else {
+ switch (result.case) {
+ case HttpStatusCode.Unauthorized: {
+ setNotif({
+ message: "Your password is incorrect",
+ type: "ERROR",
+ });
+ return;
+ }
+ case HttpStatusCode.NotFound: {
+ setNotif({
+ message: "Your instance not found",
+ type: "ERROR",
+ });
+ return;
+ }
+ }
+ }
+ }
async function doLoginImpl() {
- const secretToken = normalizeToken(token);
- const result = await lib.authenticate.createAccessToken(secretToken, {
- scope: "write",
- duration: {
- d_us: "forever"
- },
- refreshable: true,
- });
+ const result = await lib.authenticate.createAccessTokenMerchant(
+ token,
+ tokenRequest,
+ );
if (result.type === "ok") {
- const { access_token } = result.body;
- logIn({ instance: state.instance, token: access_token });
+ const { token } = result.body;
+ logIn({ token });
return;
} else {
- switch(result.case) {
+ switch (result.case) {
case HttpStatusCode.Unauthorized: {
setNotif({
message: "Your password is incorrect",
@@ -79,8 +111,8 @@ export function LoginPage(_p: Props): VNode {
}
}
- if (state.isAdmin && state.instance !== DEFAULT_ADMIN_USERNAME) {
- //admin trying to access another instance
+ if (state.status === "loggedIn" && state.impersonate !== undefined) {
+ //the user is loggedin but trying to do an impersonation
return (
<div class="columns is-centered" style={{ margin: "auto" }}>
<div class="column is-two-thirds ">
@@ -115,7 +147,9 @@ export function LoginPage(_p: Props): VNode {
placeholder={"current access token"}
name="token"
onKeyPress={(e) =>
- e.keyCode === 13 ? doLoginImpl() : null
+ e.keyCode === 13
+ ? doImpersonateImpl(state.instance)
+ : null
}
value={token}
onInput={(e): void => setToken(e?.currentTarget.value)}
@@ -133,7 +167,7 @@ export function LoginPage(_p: Props): VNode {
borderTop: 0,
}}
>
- <AsyncButton onClick={doLoginImpl}>
+ <AsyncButton onClick={() => doImpersonateImpl(state.instance)}>
<i18n.Translate>Confirm</i18n.Translate>
</AsyncButton>
</footer>
diff --git a/packages/merchant-backoffice-ui/src/paths/settings/index.tsx b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
index 4efda43be..6290f48e6 100644
--- a/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/settings/index.tsx
@@ -1,10 +1,30 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2024 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
import { useLang, useTranslationContext } from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
-import { FormErrors, FormProvider } from "../../components/form/FormProvider.js";
+import {
+ FormErrors,
+ FormProvider,
+} from "../../components/form/FormProvider.js";
import { InputSelector } from "../../components/form/InputSelector.js";
import { InputToggle } from "../../components/form/InputToggle.js";
import { LangSelector } from "../../components/menu/LangSelector.js";
-import { Settings, usePreference } from "../../hooks/preference.js";
+import { Preferences, usePreference } from "../../hooks/preference.js";
+import { AbsoluteTime } from "@gnu-taler/taler-util";
function getBrowserLang(): string | undefined {
if (typeof window === "undefined") return undefined;
@@ -14,99 +34,107 @@ function getBrowserLang(): string | undefined {
}
export function Settings({ onClose }: { onClose?: () => void }): VNode {
- const { i18n } = useTranslationContext()
- const borwserLang = getBrowserLang()
- const { update } = useLang(undefined, {})
+ const { i18n } = useTranslationContext();
+ const borwserLang = getBrowserLang();
+ const { update } = useLang(undefined, {});
- const [value, updateValue] = usePreference()
- const errors: FormErrors<Settings> = {
- }
+ const [value, , updateValue] = usePreference();
+ const errors: FormErrors<Preferences> = {};
- function valueHandler(s: (d: Partial<Settings>) => Partial<Settings>): void {
- const next = s(value)
- const v: Settings = {
+ function valueHandler(s: (d: Partial<Preferences>) => Partial<Preferences>): void {
+ const next = s(value);
+ const v: Preferences = {
advanceOrderMode: next.advanceOrderMode ?? false,
- dateFormat: next.dateFormat ?? "ymd"
- }
- updateValue(v)
+ hideKycUntil: next.hideKycUntil ?? AbsoluteTime.never(),
+ dateFormat: next.dateFormat ?? "ymd",
+ };
+ updateValue(v);
}
- return <div>
- <section class="section is-main-section">
- <div class="columns">
- <div class="column" />
- <div class="column is-four-fifths">
- <div>
-
- <FormProvider<Settings>
- name="settings"
- errors={errors}
- object={value}
- valueHandler={valueHandler}
- >
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">
- <i18n.Translate>Language</i18n.Translate>
- <span class="icon has-tooltip-right" data-tooltip={"Force language setting instance of taking the browser"}>
- <i class="mdi mdi-information" />
- </span>
- </label>
- </div>
- <div class="field field-body has-addons is-flex-grow-3">
- <LangSelector />
- &nbsp;
- {borwserLang !== undefined && <button
- data-tooltip={i18n.str`generate random secret key`}
- class="button is-info mr-2"
- onClick={(e) => {
- update(borwserLang.substring(0, 2))
- }}
- >
- <i18n.Translate>Set default</i18n.Translate>
- </button>}
+ return (
+ <div>
+ <section class="section is-main-section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <div>
+ <FormProvider<Preferences>
+ name="settings"
+ errors={errors}
+ object={value}
+ valueHandler={valueHandler}
+ >
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ <i18n.Translate>Language</i18n.Translate>
+ <span
+ class="icon has-tooltip-right"
+ data-tooltip={
+ "Force language setting instance of taking the browser"
+ }
+ >
+ <i class="mdi mdi-information" />
+ </span>
+ </label>
+ </div>
+ <div class="field field-body has-addons is-flex-grow-3">
+ <LangSelector />
+ &nbsp;
+ {borwserLang !== undefined && (
+ <button
+ data-tooltip={i18n.str`generate random secret key`}
+ class="button is-info mr-2"
+ onClick={(e) => {
+ update(borwserLang.substring(0, 2));
+ e.preventDefault()
+ }}
+ >
+ <i18n.Translate>Set default</i18n.Translate>
+ </button>
+ )}
+ </div>
</div>
- </div>
- <InputToggle<Settings>
- label={i18n.str`Advance order creation`}
- tooltip={i18n.str`Shows more options in the order creation form`}
- name="advanceOrderMode"
- />
- <InputSelector<Settings>
- name="dateFormat"
- label={i18n.str`Date format`}
- expand={true}
- help={
- value.dateFormat === "dmy" ? "31/12/2001" : value.dateFormat === "mdy" ? "12/31/2001" : value.dateFormat === "ymd" ? "2001/12/31" : ""
- }
- toStr={(e) => {
- if (e === "ymd") return "year month day"
- if (e === "mdy") return "month day year"
- if (e === "dmy") return "day month year"
- return "choose one"
- }}
- values={[
- "ymd",
- "mdy",
- "dmy",
- ]}
- tooltip={i18n.str`how the date is going to be displayed`}
- />
- </FormProvider>
+ <InputToggle<Preferences>
+ label={i18n.str`Advance order creation`}
+ tooltip={i18n.str`Shows more options in the order creation form`}
+ name="advanceOrderMode"
+ />
+ <InputSelector<Preferences>
+ name="dateFormat"
+ label={i18n.str`Date format`}
+ expand={true}
+ help={
+ value.dateFormat === "dmy"
+ ? "31/12/2001"
+ : value.dateFormat === "mdy"
+ ? "12/31/2001"
+ : value.dateFormat === "ymd"
+ ? "2001/12/31"
+ : ""
+ }
+ toStr={(e) => {
+ if (e === "ymd") return "year month day";
+ if (e === "mdy") return "month day year";
+ if (e === "dmy") return "day month year";
+ return "choose one";
+ }}
+ values={["ymd", "mdy", "dmy"]}
+ tooltip={i18n.str`how the date is going to be displayed`}
+ />
+ </FormProvider>
+ </div>
</div>
+ <div class="column" />
</div>
- <div class="column" />
- </div>
- </section >
- {onClose &&
- <section class="section is-main-section">
- <button
- class="button"
- onClick={onClose}
- >
- <i18n.Translate>Close</i18n.Translate>
- </button>
</section>
- }
- </div >
-} \ No newline at end of file
+ {onClose && (
+ <section class="section is-main-section">
+ <button class="button" onClick={onClose}>
+ <i18n.Translate>Close</i18n.Translate>
+ </button>
+ </section>
+ )}
+ </div>
+ );
+}