summaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx')
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx249
1 files changed, 90 insertions, 159 deletions
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 2ba637f44..ad36df3cc 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
@@ -25,7 +25,7 @@ import {
Duration,
TalerError,
TalerMerchantApi,
- assertUnreachable,
+ TranslatedString
} from "@gnu-taler/taler-util";
import {
useMerchantApiContext,
@@ -42,18 +42,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 { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js";
-import { InputTab } from "../../../../components/form/InputTab.js";
+import { InputSelector } from "../../../../components/form/InputSelector.js";
+import { InputToggle } from "../../../../components/form/InputToggle.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
+import { TextField } from "../../../../components/form/TextField.js";
import { useInstanceOtpDevices } from "../../../../hooks/otp.js";
-enum Steps {
- BOTH_FIXED,
- FIXED_PRICE,
- FIXED_SUMMARY,
- NON_FIXED,
-}
-
// type Entity = TalerMerchantApi.TemplateAddDetails & { type: Steps };
type Entity = {
id?: string;
@@ -63,7 +57,9 @@ type Entity = {
amount?: AmountString;
minimum_age?: number;
pay_duration?: Duration;
- type: Steps;
+ summary_editable?: boolean;
+ amount_editable?: boolean;
+ currency_editable?: boolean;
};
interface Props {
@@ -81,9 +77,18 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
pay_duration: {
d_ms: 1000 * 60 * 30, //30 min
},
- type: Steps.NON_FIXED,
});
+ function updateState(up: (s: Partial<Entity>) => Partial<Entity>) {
+ setState((old) => {
+ const newState = up(old)
+ if (!newState.amount_editable) {
+ newState.currency_editable = false
+ }
+ return newState
+ })
+ }
+
const parsedPrice = !state.amount ? undefined : Amounts.parse(state.amount);
const errors: FormErrors<Entity> = {
@@ -93,24 +98,14 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
? 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
- )
- ? undefined
- : !state.amount
- ? i18n.str`required`
+ amount:
+ !state.amount
+ ? undefined
: !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.summary
- ? i18n.str`required`
- : undefined,
minimum_age:
state.minimum_age && state.minimum_age < 0
? i18n.str`should be greater that 0`
@@ -125,67 +120,33 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
};
const hasErrors = Object.keys(errors).some(
- (k) => (errors as Record<string,unknown>)[k] !== undefined,
+ (k) => (errors as Record<string, unknown>)[k] !== undefined,
);
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);
- // return onCreate(state);
- }
+ if (hasErrors) return Promise.reject();
+ 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_editable ? undefined : state.amount,
+ summary: state.summary_editable ? undefined : state.summary,
+ },
+ editable_defaults: {
+ amount: !state.amount_editable ? undefined : state.amount,
+ summary: !state.summary_editable ? undefined : state.summary,
+ },
+ otp_id: state.otpId!,
+ });
+
};
const deviceList = !devices || devices instanceof TalerError || devices.type === "fail" ? [] : devices.body;
-
+ const deviceMap = deviceList.reduce((prev, cur) => {
+ prev[cur.otp_device_id] = cur.device_description as TranslatedString
+ return prev
+ }, {} as Record<string, TranslatedString>)
return (
<div>
<section class="section is-main-section">
@@ -194,7 +155,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
<div class="column is-four-fifths">
<FormProvider
object={state}
- valueHandler={setState}
+ valueHandler={updateState}
errors={errors}
>
<InputWithAddon<Entity>
@@ -209,59 +170,36 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
help=""
tooltip={i18n.str`Describe what this template stands for`}
/>
- <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.`;
- 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`}
- values={[
- Steps.NON_FIXED,
- Steps.FIXED_PRICE,
- Steps.FIXED_SUMMARY,
- Steps.BOTH_FIXED,
- ]}
- 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`;
- }
- }}
+
+ <Input<Entity>
+ name="summary"
+ inputType="multiline"
+ label={i18n.str`Summary`}
+ tooltip={i18n.str`If specified, this template will create order with the same 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 ? (
- <InputCurrency<Entity>
- name="amount"
- label={i18n.str`Fixed price`}
- tooltip={i18n.str`If specified, this template will create order with the same price`}
- />
- ) : undefined}
+ <InputToggle<Entity>
+ name="summary_editable"
+ label={i18n.str`Summary is editable`}
+ tooltip={i18n.str`Allow the user to change the summary.`}
+ />
+
+ <InputCurrency<Entity>
+ name="amount"
+ label={i18n.str`Amount`}
+ tooltip={i18n.str`If specified, this template will create order with the same price`}
+ />
+ <InputToggle<Entity>
+ name="amount_editable"
+ label={i18n.str`Amount is editable`}
+ tooltip={i18n.str`Allow the user to select the amount to pay.`}
+ />
+ {/* <InputToggle<Entity>
+ name="currency_editable"
+ readonly={!state.amount_editable}
+ label={i18n.str`Currency is editable`}
+ tooltip={i18n.str`Allow the user to change currency.`}
+ /> */}
+
<InputNumber<Entity>
name="minimum_age"
label={i18n.str`Minimum age`}
@@ -274,33 +212,26 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
help=""
tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
/>
- <Input<Entity>
+ {!deviceList.length ? <TextField
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, otpId: p?.id }))}
- list={deviceList.map((e) => ({
- description: e.device_description,
- id: e.otp_device_id,
- }))}
- />
+ tooltip={i18n.str`Use to verify transaction while offline.`}
+ >
+ <i18n.Translate>No OTP device.</i18n.Translate>&nbsp;<a href="/otp-devices/new"><i18n.Translate>Add one first</i18n.Translate></a>
+ </TextField> :
+ <InputSelector<Entity>
+ name="otpId"
+ label={i18n.str`OTP device`}
+ values={[undefined, ...deviceList.map(e => e.otp_device_id)]}
+ toStr={(v?: string) => {
+ if (!v) {
+ return i18n.str`No device`
+ }
+ return deviceMap[v]
+ }}
+ tooltip={i18n.str`Use to verify transaction in offline mode.`}
+ />
+ }
</FormProvider>
<div class="buttons is-right mt-5">