aboutsummaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-01-18 16:16:21 -0300
committerSebastian <sebasjm@gmail.com>2024-01-18 16:16:34 -0300
commit421b5c0acfa310fcb7f1893bf81e38f2876e1cbf (patch)
tree4511c7833fd79bc1aea3ab9471d489033fc4293d /packages/merchant-backoffice-ui
parentcb860500b5269e6fb18345e8646d906039d36496 (diff)
fix templates
Diffstat (limited to 'packages/merchant-backoffice-ui')
-rw-r--r--packages/merchant-backoffice-ui/src/Application.tsx6
-rw-r--r--packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx5
-rw-r--r--packages/merchant-backoffice-ui/src/InstanceRoutes.tsx20
-rw-r--r--packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx3
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx9
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx190
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx205
8 files changed, 284 insertions, 156 deletions
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx
index e832d3107..0c509ef45 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -19,14 +19,16 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { HttpStatusCode, LibtoolVersion } from "@gnu-taler/taler-util";
+import { HttpStatusCode, LibtoolVersion, TranslatedString } from "@gnu-taler/taler-util";
import {
ErrorType,
TranslationProvider,
+ notifyError,
+ notifyException,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
-import { useMemo } from "preact/hooks";
+import { useEffect, useErrorBoundary, useMemo } from "preact/hooks";
import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js";
import { Loading } from "./components/exception/loading.js";
import {
diff --git a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
index 47177e97e..3dc34d1a9 100644
--- a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx
@@ -18,12 +18,12 @@
*
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser";
import { createHashHistory } from "history";
import { Fragment, VNode, h } from "preact";
import { Route, Router, route } from "preact-router";
-import { useState } from "preact/hooks";
+import { useEffect, useErrorBoundary, useState } from "preact/hooks";
import { InstanceRoutes } from "./InstanceRoutes.js";
import {
NotConnectedAppMenu,
@@ -54,7 +54,6 @@ export function ApplicationReadyRoutes(): VNode {
updateToken(token)
setUnauthorized(false)
}
-
const result = useBackendInstancesTestForAdmin();
const clearTokenAndGoToRoot = () => {
diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index c3c20bcc4..b5680eabb 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -23,11 +23,12 @@ import {
useTranslationContext,
HttpError,
ErrorType,
+ GlobalNotificationsBanner,
} from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
import { Fragment, FunctionComponent, h, VNode } from "preact";
import { Route, route, Router } from "preact-router";
-import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
+import { useCallback, useEffect, useErrorBoundary, useMemo, useState } from "preact/hooks";
import { Loading } from "./components/exception/loading.js";
import { Menu, NotificationCard } from "./components/menu/index.js";
import { useBackendContext } from "./context/backend.js";
@@ -77,6 +78,7 @@ import { Notification } from "./utils/types.js";
import { LoginToken, MerchantBackend } from "./declaration.js";
import { Settings } from "./paths/settings/index.js";
import { dateFormatForSettings, useSettings } from "./hooks/useSettings.js";
+import { TranslatedString } from "@gnu-taler/taler-util";
export enum InstancePaths {
error = "/error",
@@ -151,7 +153,7 @@ export function InstanceRoutes({
const [token, updateToken] = useBackendInstanceToken(id);
const { i18n } = useTranslationContext();
- type GlobalNotifState = (Notification & { to: string }) | undefined;
+ type GlobalNotifState = (Notification & { to: string | undefined }) | undefined;
const [globalNotification, setGlobalNotification] =
useState<GlobalNotifState>(undefined);
@@ -163,9 +165,8 @@ export function InstanceRoutes({
}
onLoginPass()
};
- // const updateLoginStatus = (url: string, token?: string) => {
- // changeToken(token);
- // };
+
+ const [error] = useErrorBoundary();
const value = useMemo(
() => ({ id, token, admin, changeToken }),
@@ -264,6 +265,15 @@ export function InstanceRoutes({
/>
<KycBanner />
<NotificationCard notification={globalNotification} />
+ {error &&
+ <NotificationCard notification={{
+ message: "Internal error, please repot",
+ type: "ERROR",
+ description: <pre>
+ {(error instanceof Error ? error.stack : String(error)) as TranslatedString}
+ </pre>
+ }} />
+ }
<Router
onChange={(e) => {
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx
index 7aa2703a4..c9226ad69 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx
@@ -58,6 +58,9 @@ export function InputDuration<T>({
} else if (value.d_ms === "forever") {
strValue = i18n.str`forever`;
} else {
+ if (value.d_ms === undefined) {
+ throw Error(`assertion error: duration should have a d_ms but got '${JSON.stringify(value)}'`)
+ }
strValue = formatDuration(
intervalToDuration({ start: 0, end: value.d_ms }),
{
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 5f1ae26a3..94424132b 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
@@ -28,16 +28,11 @@ 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 { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
+import { useBackendContext } from "../../../../context/backend.js";
+import { MerchantBackend } from "../../../../declaration.js";
import { isBase32RFC3548Charset, randomBase32Key } from "../../../../utils/crypto.js";
-import { QR } from "../../../../components/exception/QR.js";
-import { useInstanceContext } from "../../../../context/instance.js";
type Entity = MerchantBackend.OTP.OtpDeviceAddDetails;
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 947f3572c..a2c0c9dfb 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
@@ -20,8 +20,11 @@
*/
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";
@@ -50,10 +53,20 @@ enum Steps {
NON_FIXED,
}
-type Entity = MerchantBackend.Template.TemplateAddDetails & { type: 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,
+};
interface Props {
- onCreate: (d: Entity) => Promise<void>;
+ onCreate: (d: MerchantBackend.Template.TemplateAddDetails) => Promise<void>;
onBack?: () => void;
}
@@ -63,57 +76,51 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
const devices = useInstanceOtpDevices()
const [state, setState] = useState<Partial<Entity>>({
- template_contract: {
- minimum_age: 0,
- pay_duration: {
- d_us: 1000 * 1000 * 60 * 30, //30 min
- },
+ minimum_age: 0,
+ pay_duration: {
+ d_ms: 1000 * 60 * 30, //30 min
},
type: Steps.NON_FIXED,
});
- const parsedPrice = !state.template_contract?.amount
+ const parsedPrice = !state.amount
? undefined
- : Amounts.parse(state.template_contract?.amount);
+ : Amounts.parse(state.amount);
const errors: FormErrors<Entity> = {
- template_id: !state.template_id
+ id: !state.id
? i18n.str`should not be empty`
- : !/[a-zA-Z0-9]*/.test(state.template_id)
+ : !/[a-zA-Z0-9]*/.test(state.id)
? i18n.str`no valid. only characters and numbers`
: undefined,
- template_description: !state.template_description
+ description: !state.description
? i18n.str`should not be empty`
: undefined,
- template_contract: !state.template_contract
+ amount: !(state.type === Steps.FIXED_PRICE || state.type === Steps.BOTH_FIXED)
? undefined
- : undefinedIfEmpty({
- amount: !(state.type === Steps.FIXED_PRICE || state.type === Steps.BOTH_FIXED)
- ? undefined
- : !state.template_contract?.amount
- ? i18n.str`required`
- : !parsedPrice
- ? i18n.str`not valid`
- : Amounts.isZero(parsedPrice)
- ? i18n.str`must be greater than 0`
- : undefined,
- summary: !(state.type === Steps.FIXED_SUMMARY || state.type === Steps.BOTH_FIXED)
- ? undefined
- : !state.template_contract?.summary
- ? i18n.str`required`
+ : !state.amount
+ ? i18n.str`required`
+ : !parsedPrice
+ ? i18n.str`not valid`
+ : Amounts.isZero(parsedPrice)
+ ? i18n.str`must be greater than 0`
: undefined,
- minimum_age:
- state.template_contract.minimum_age < 0
- ? i18n.str`should be greater that 0`
- : undefined,
- pay_duration: !state.template_contract.pay_duration
- ? i18n.str`can't be empty`
- : state.template_contract.pay_duration.d_us === "forever"
- ? undefined
- : state.template_contract.pay_duration.d_us < 1000 * 1000 //less than one second
- ? i18n.str`to short`
- : undefined,
- } as Partial<MerchantTemplateContractDetails>),
+ summary: !(state.type === Steps.FIXED_SUMMARY || state.type === Steps.BOTH_FIXED)
+ ? undefined
+ : !state.summary
+ ? i18n.str`required`
+ : undefined,
+ minimum_age:
+ state.minimum_age && state.minimum_age < 0
+ ? i18n.str`should be greater that 0`
+ : undefined,
+ pay_duration: !state.pay_duration
+ ? i18n.str`can't be empty`
+ : state.pay_duration.d_ms === "forever"
+ ? undefined
+ : state.pay_duration.d_ms < 1000 //less than one second
+ ? i18n.str`to short`
+ : undefined,
};
const hasErrors = Object.keys(errors).some(
@@ -121,21 +128,56 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
);
const submitForm = () => {
- if (hasErrors) return Promise.reject();
- if (state.template_contract) {
- if (state.type === Steps.NON_FIXED) {
- delete state.template_contract.amount;
- delete state.template_contract.summary;
- } else if (state.type === Steps.FIXED_SUMMARY) {
- delete state.template_contract.amount;
- } else if (state.type === Steps.FIXED_PRICE) {
- delete state.template_contract.summary;
- }
- }
- delete state.type
- return onCreate(state as any);
- };
-
+ 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)
+ // return onCreate(state);
+ };
+ }
const deviceList = !devices.ok ? [] : devices.data.otp_devices
return (
@@ -150,21 +192,22 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
errors={errors}
>
<InputWithAddon<Entity>
- name="template_id"
- help={`${backendURL}/templates/${state.template_id ?? ""}`}
+ name="id"
+ help={`${backendURL}/templates/${state.id ?? ""}`}
label={i18n.str`Identifier`}
tooltip={i18n.str`Name of the template in URLs.`}
/>
<Input<Entity>
- name="template_description"
+ name="description"
label={i18n.str`Description`}
help=""
tooltip={i18n.str`Describe what this template stands for`}
/>
- <InputTab
+ <InputTab<Entity>
name="type"
label={i18n.str`Type`}
help={(() => {
+ 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.`
@@ -189,41 +232,52 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
}}
/>
{state.type === Steps.BOTH_FIXED || state.type === Steps.FIXED_SUMMARY ?
- <Input
- name="template_contract.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 ?
- <InputCurrency
- name="template_contract.amount"
+ <InputCurrency<Entity>
+ name="amount"
label={i18n.str`Fixed price`}
tooltip={i18n.str`If specified, this template will create order with the same price`}
/>
: undefined}
- <InputNumber
- name="template_contract.minimum_age"
+ <InputNumber<Entity>
+ name="minimum_age"
label={i18n.str`Minimum age`}
help=""
tooltip={i18n.str`Is this contract restricted to some age?`}
/>
- <InputDuration
- name="template_contract.pay_duration"
+ <InputDuration<Entity>
+ name="pay_duration"
label={i18n.str`Payment timeout`}
help=""
tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
/>
<Input<Entity>
- name="otp_id"
+ 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>}
tooltip={i18n.str`Use to verify transaction in offline mode.`}
/>
<InputSearchOnList
label={i18n.str`Search device`}
- onChange={(p) => setState((v) => ({ ...v, otp_id: p?.id }))}
+ onChange={(p) => setState((v) => ({ ...v, otpId: p?.id }))}
list={deviceList.map(e => ({
description: e.device_description,
id: e.otp_device_id
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
index b9767442f..2d50e3924 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx
@@ -86,7 +86,7 @@ export default function ListTemplates({
<NotificationCard notification={notif} />
<JumpToElementById
- testIfExist={testTemplateExist}
+ testIfExist={testTemplateExist}
onSelect={onSelect}
description={i18n.str`jump to template with the given template ID`}
palceholder={i18n.str`template id`}
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 b578d4664..cfb521c73 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
@@ -20,8 +20,10 @@
*/
import {
+ AmountString,
Amounts,
- MerchantTemplateContractDetails,
+ Duration,
+ assertUnreachable
} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
@@ -35,11 +37,12 @@ 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 { InputTab } from "../../../../components/form/InputTab.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
import { useBackendContext } from "../../../../context/backend.js";
-import { MerchantBackend, WithId } from "../../../../declaration.js";
-import { undefinedIfEmpty } from "../../../../utils/table.js";
-import { InputTab } from "../../../../components/form/InputTab.js";
+import { MerchantBackend } from "../../../../declaration.js";
+import { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js";
+import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
enum Steps {
BOTH_FIXED,
@@ -48,12 +51,19 @@ enum Steps {
NON_FIXED,
}
-type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId;
+type Entity = {
+ description?: string,
+ otpId?: string | null,
+ summary?: string,
+ amount?: AmountString,
+ minimum_age?: number,
+ pay_duration?: Duration,
+};
interface Props {
- onUpdate: (d: Entity) => Promise<void>;
+ onUpdate: (d: MerchantBackend.Template.TemplatePatchDetails) => Promise<void>;
onBack?: () => void;
- template: Entity;
+ template: MerchantBackend.Template.TemplateDetails;
}
export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
@@ -61,53 +71,59 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
const { url: backendURL } = useBackendContext()
const intialStep =
- template.template_contract?.amount === undefined && template.template_contract?.summary === undefined
+ template.template_contract.amount === undefined && template.template_contract.summary === undefined
? Steps.NON_FIXED
- : template.template_contract?.summary === undefined
+ : template.template_contract.summary === undefined
? Steps.FIXED_PRICE
- : template.template_contract?.amount === undefined
+ : template.template_contract.amount === undefined
? Steps.FIXED_SUMMARY
: Steps.BOTH_FIXED;
- const [state, setState] = useState<Partial<Entity & { type: Steps }>>({ ...template, type: intialStep });
+ const [state, setState] = useState<Partial<Entity & { type: Steps }>>({
+ amount: template.template_contract.amount as AmountString | undefined,
+ description: template.template_description,
+ minimum_age: template.template_contract.minimum_age,
+ otpId: template.otp_id,
+ pay_duration: template.template_contract.pay_duration ? Duration.fromTalerProtocolDuration(template.template_contract.pay_duration) : undefined,
+ summary: template.template_contract.summary,
+ type: intialStep,
+ });
+ const devices = useInstanceOtpDevices()
+ const deviceList = !devices.ok ? [] : devices.data.otp_devices
- const parsedPrice = !state.template_contract?.amount
+ const parsedPrice = !state.amount
? undefined
- : Amounts.parse(state.template_contract?.amount);
+ : Amounts.parse(state.amount);
const errors: FormErrors<Entity> = {
- template_description: !state.template_description
+ description: !state.description
? i18n.str`should not be empty`
: undefined,
- template_contract: !state.template_contract
+ amount: !(state.type === Steps.FIXED_PRICE || state.type === Steps.BOTH_FIXED)
? undefined
- : undefinedIfEmpty({
- amount: !(state.type === Steps.FIXED_PRICE || state.type === Steps.BOTH_FIXED)
- ? undefined
- : !state.template_contract?.amount
- ? i18n.str`required`
- : !parsedPrice
- ? i18n.str`not valid`
- : Amounts.isZero(parsedPrice)
- ? i18n.str`must be greater than 0`
- : undefined,
- summary: !(state.type === Steps.FIXED_SUMMARY || state.type === Steps.BOTH_FIXED)
- ? undefined
- : !state.template_contract?.summary
- ? i18n.str`required`
- : undefined,
- minimum_age:
- state.template_contract.minimum_age < 0
- ? i18n.str`should be greater that 0`
+ : !state.amount
+ ? i18n.str`required`
+ : !parsedPrice
+ ? i18n.str`not valid`
+ : Amounts.isZero(parsedPrice)
+ ? i18n.str`must be greater than 0`
: undefined,
- pay_duration: !state.template_contract.pay_duration
- ? i18n.str`can't be empty`
- : state.template_contract.pay_duration.d_us === "forever"
- ? undefined
- : state.template_contract.pay_duration.d_us < 1000 * 1000 // less than one second
- ? i18n.str`to short`
- : undefined,
- } as Partial<MerchantTemplateContractDetails>),
+ summary: !(state.type === Steps.FIXED_SUMMARY || state.type === Steps.BOTH_FIXED)
+ ? undefined
+ : !state.summary
+ ? i18n.str`required`
+ : undefined,
+ minimum_age:
+ state.minimum_age && state.minimum_age < 0
+ ? i18n.str`should be greater that 0`
+ : undefined,
+ pay_duration: !state.pay_duration
+ ? i18n.str`can't be empty`
+ : state.pay_duration.d_ms === "forever"
+ ? undefined
+ : state.pay_duration.d_ms < 1000 // less than one second
+ ? i18n.str`to short`
+ : undefined,
};
const hasErrors = Object.keys(errors).some(
@@ -115,19 +131,50 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
);
const submitForm = () => {
- if (hasErrors) return Promise.reject();
- if (state.template_contract) {
- if (state.type === Steps.NON_FIXED) {
- delete state.template_contract.amount;
- delete state.template_contract.summary;
- } else if (state.type === Steps.FIXED_SUMMARY) {
- delete state.template_contract.amount;
- } else if (state.type === Steps.FIXED_PRICE) {
- delete state.template_contract.summary;
- }
+ if (hasErrors || state.type === undefined) return Promise.reject();
+ switch (state.type) {
+ case Steps.FIXED_PRICE: return onUpdate({
+ 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 onUpdate({
+ 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 onUpdate({
+ 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 onUpdate({
+ 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)
}
- delete state.type
- return onUpdate(state as any);
};
@@ -140,7 +187,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.id}
+ {backendURL}/templates/{template.otp_id}
</span>
</div>
</div>
@@ -157,16 +204,9 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
valueHandler={setState}
errors={errors}
>
- <InputWithAddon<Entity>
- name="id"
- addonBefore={`templates/`}
- readonly
- label={i18n.str`Identifier`}
- tooltip={i18n.str`Name of the template in URLs.`}
- />
<Input<Entity>
- name="template_description"
+ name="description"
label={i18n.str`Description`}
help=""
tooltip={i18n.str`Describe what this template stands for`}
@@ -199,32 +239,57 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
}}
/>
{state.type === Steps.BOTH_FIXED || state.type === Steps.FIXED_SUMMARY ?
- <Input
- name="template_contract.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 ?
- <InputCurrency
- name="template_contract.amount"
+ <InputCurrency<Entity>
+ name="amount"
label={i18n.str`Fixed price`}
tooltip={i18n.str`If specified, this template will create order with the same price`}
/>
: undefined}
- <InputNumber
- name="template_contract.minimum_age"
+ <InputNumber<Entity>
+ name="minimum_age"
label={i18n.str`Minimum age`}
help=""
tooltip={i18n.str`Is this contract restricted to some age?`}
/>
- <InputDuration
- name="template_contract.pay_duration"
+ <InputDuration<Entity>
+ name="pay_duration"
label={i18n.str`Payment timeout`}
help=""
tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
/>
+ <Input<Entity>
+ name="otpId"
+ label={i18n.str`OTP device`}
+ readonly
+ side={<button
+ class="button is-danger"
+ data-tooltip={i18n.str`remove otp device for this template`}
+ onClick={(): void => {
+ setState((v) => ({ ...v, otpId: null }));
+ }}
+ >
+ <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 => ({
+ description: e.device_description,
+ id: e.otp_device_id
+ }))}
+ />
</FormProvider>
<div class="buttons is-right mt-5">