diff options
| author | Sebastian <sebasjm@gmail.com> | 2023-03-13 11:12:46 -0300 | 
|---|---|---|
| committer | Sebastian <sebasjm@gmail.com> | 2023-03-13 11:27:52 -0300 | 
| commit | 96d110379e9bfbffedfeebf44c1c972b12fffff4 (patch) | |
| tree | e7065608cd1561d000f9d2eef79a4ff3e4611e16 /packages/merchant-backoffice-ui/src/paths/instance/templates | |
| parent | 5f681813cf1bb7bb5c0baa41f29011d0029d003d (diff) | |
some fixes and validations
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/templates')
3 files changed, 120 insertions, 27 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 144e968c5..f6aa9a9ae 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 @@ -19,6 +19,10 @@   * @author Sebastian Javier Marchano (sebasjm)   */ +import { +  Amounts, +  MerchantTemplateContractDetails, +} from "@gnu-taler/taler-util";  import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";  import { h, VNode } from "preact";  import { useState } from "preact/hooks"; @@ -35,7 +39,10 @@ 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 { randomBase32Key } from "../../../../utils/crypto.js"; +import { +  isBase32RFC3548Charset, +  randomBase32Key, +} from "../../../../utils/crypto.js";  import { undefinedIfEmpty } from "../../../../utils/table.js";  type Entity = MerchantBackend.Template.TemplateAddDetails; @@ -45,17 +52,14 @@ interface Props {    onBack?: () => void;  } -const algorithms = ["0", "1", "2"]; -const algorithmsNames = [ -  "off", -  "30s 8d TOTP-SHA1 without amount", -  "30s 8d eTOTP-SHA1 with amount", -]; +const algorithms = [0, 1, 2]; +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 [showKey, setShowKey] = useState(false);    const [state, setState] = useState<Partial<Entity>>({      template_contract: {        minimum_age: 0, @@ -65,6 +69,10 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {      },    }); +  const parsedPrice = !state.template_contract?.amount +    ? undefined +    : Amounts.parse(state.template_contract?.amount); +    const errors: FormErrors<Entity> = {      template_id: !state.template_id ? i18n.str`should not be empty` : undefined,      template_description: !state.template_description @@ -73,6 +81,13 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {      template_contract: !state.template_contract        ? undefined        : undefinedIfEmpty({ +          amount: !state.template_contract?.amount +            ? undefined +            : !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` @@ -84,7 +99,16 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {              : state.template_contract.pay_duration.d_us < 1000 * 1000 //less than one second              ? i18n.str`to short`              : undefined, -        }), +        } as Partial<MerchantTemplateContractDetails>), +    pos_key: !state.pos_key +      ? !state.pos_algorithm +        ? undefined +        : i18n.str`required` +      : !isBase32RFC3548Charset(state.pos_key) +      ? i18n.str`just letters and numbers from 2 to 7` +      : state.pos_key.length !== 32 +      ? i18n.str`size of the key should be 32` +      : undefined,    };    const hasErrors = Object.keys(errors).some( @@ -144,21 +168,32 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {                />                <InputSelector<Entity>                  name="pos_algorithm" -                label={i18n.str`Veritifaction algorithm`} +                label={i18n.str`Verification algorithm`}                  tooltip={i18n.str`Algorithm to use to verify transaction in offline mode`}                  values={algorithms}                  toStr={(v) => algorithmsNames[v]} -                convert={(v) => Number(v)} +                fromStr={(v) => Number(v)}                />                {state.pos_algorithm && state.pos_algorithm > 0 ? ( -                <Input<Entity> +                <InputWithAddon<Entity>                    name="pos_key"                    label={i18n.str`Point-of-sale key`} -                  help="" +                  help="Be sure to be very hard to guess or use the random generator"                    tooltip={i18n.str`Useful to validate the purchase`} +                  fromStr={(v) => v.toUpperCase()} +                  addonAfter={ +                    <span class="icon"> +                      {showKey ? ( +                        <i class="mdi mdi-eye" /> +                      ) : ( +                        <i class="mdi mdi-eye-off" /> +                      )} +                    </span> +                  }                    side={ -                    <span data-tooltip={i18n.str`generate random secret key`}> +                    <span style={{ display: "flex" }}>                        <button +                        data-tooltip={i18n.str`generate random secret key`}                          class="button is-info mr-3"                          onClick={(e) => {                            const pos_key = randomBase32Key(); @@ -167,6 +202,23 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {                        >                          <i18n.Translate>random</i18n.Translate>                        </button> +                      <button +                        data-tooltip={ +                          showKey +                            ? i18n.str`show secret key` +                            : i18n.str`hide secret key` +                        } +                        class="button is-info mr-3" +                        onClick={(e) => { +                          setShowKey(!showKey); +                        }} +                      > +                        {showKey ? ( +                          <i18n.Translate>hide</i18n.Translate> +                        ) : ( +                          <i18n.Translate>show</i18n.Translate> +                        )} +                      </button>                      </span>                    }                  /> 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 a6b616907..64e9a86fe 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 @@ -127,6 +127,15 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {          <div class="columns">            <div class="column" />            <div class="column is-four-fifths"> +            <p class="is-size-5 mt-5 mb-5"> +              <i18n.Translate> +                Here you can specify a default value for fields that are not +                fixed. Default values can be edited by the customer before the +                payment. +              </i18n.Translate> +            </p> + +            <p></p>              <FormProvider                object={state}                valueHandler={setState} @@ -134,7 +143,11 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {              >                <InputCurrency<Entity>                  name="amount" -                label={i18n.str`Amount`} +                label={ +                  fixedAmount +                    ? i18n.str`Fixed amount` +                    : i18n.str`Default amount` +                }                  readonly={fixedAmount}                  tooltip={i18n.str`Amount of the order`}                /> @@ -142,7 +155,11 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {                  name="summary"                  inputType="multiline"                  readonly={fixedSummary} -                label={i18n.str`Order summary`} +                label={ +                  fixedSummary +                    ? i18n.str`Fixed summary` +                    : i18n.str`Default summary` +                }                  tooltip={i18n.str`Title of the order to be shown to the customer`}                />              </FormProvider> 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 9fcfcc4bf..d12d1d2d3 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 @@ -19,6 +19,10 @@   * @author Sebastian Javier Marchano (sebasjm)   */ +import { +  Amounts, +  MerchantTemplateContractDetails, +} from "@gnu-taler/taler-util";  import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";  import { h, VNode } from "preact";  import { useState } from "preact/hooks"; @@ -35,7 +39,10 @@ import { InputSelector } from "../../../../components/form/InputSelector.js";  import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";  import { useBackendContext } from "../../../../context/backend.js";  import { MerchantBackend, WithId } from "../../../../declaration.js"; -import { randomBase32Key } from "../../../../utils/crypto.js"; +import { +  isBase32RFC3548Charset, +  randomBase32Key, +} from "../../../../utils/crypto.js";  import { undefinedIfEmpty } from "../../../../utils/table.js";  type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId; @@ -46,12 +53,8 @@ interface Props {    template: Entity;  } -const algorithms = ["0", "1", "2"]; -const algorithmsNames = [ -  "off", -  "30s 8d TOTP-SHA1 without amount", -  "30s 8d eTOTP-SHA1 with amount", -]; +const algorithms = [0, 1, 2]; +const algorithmsNames = ["off", "30s 8d TOTP-SHA1", "30s 8d eTOTP-SHA1"];  export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {    const { i18n } = useTranslationContext(); @@ -60,6 +63,10 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {    const [showKey, setShowKey] = useState(false);    const [state, setState] = useState<Partial<Entity>>(template); +  const parsedPrice = !state.template_contract?.amount +    ? undefined +    : Amounts.parse(state.template_contract?.amount); +    const errors: FormErrors<Entity> = {      template_description: !state.template_description        ? i18n.str`should not be empty` @@ -67,6 +74,13 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {      template_contract: !state.template_contract        ? undefined        : undefinedIfEmpty({ +          amount: !state.template_contract?.amount +            ? undefined +            : !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` @@ -78,7 +92,16 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {              : state.template_contract.pay_duration.d_us < 1000 * 1000 // less than one second              ? i18n.str`to short`              : undefined, -        }), +        } as Partial<MerchantTemplateContractDetails>), +    pos_key: !state.pos_key +      ? !state.pos_algorithm +        ? undefined +        : i18n.str`required` +      : !isBase32RFC3548Charset(state.pos_key) +      ? i18n.str`just letters and numbers from 2 to 7` +      : state.pos_key.length !== 32 +      ? i18n.str`size of the key should be 32` +      : undefined,    };    const hasErrors = Object.keys(errors).some( @@ -155,20 +178,21 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {                  />                  <InputSelector<Entity>                    name="pos_algorithm" -                  label={i18n.str`Veritifaction algorithm`} +                  label={i18n.str`Verification algorithm`}                    tooltip={i18n.str`Algorithm to use to verify transaction in offline mode`}                    values={algorithms}                    toStr={(v) => algorithmsNames[v]} -                  convert={(v) => Number(v)} +                  fromStr={(v) => Number(v)}                  />                  {state.pos_algorithm && state.pos_algorithm > 0 ? (                    <InputWithAddon<Entity>                      name="pos_key"                      label={i18n.str`Point-of-sale key`}                      inputType={showKey ? "text" : "password"} -                    help="" +                    help="Be sure to be very hard to guess or use the random generator"                      expand                      tooltip={i18n.str`Useful to validate the purchase`} +                    fromStr={(v) => v.toUpperCase()}                      addonAfter={                        <span class="icon">                          {showKey ? ( @@ -179,7 +203,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {                        </span>                      }                      side={ -                      <span> +                      <span style={{ display: "flex" }}>                          <button                            data-tooltip={i18n.str`generate random secret key`}                            class="button is-info mr-3" | 
