some fixes and validations
This commit is contained in:
parent
5f681813cf
commit
96d110379e
@ -24,8 +24,7 @@ import { InputProps, useField } from "./useField.js";
|
|||||||
interface Props<T> extends InputProps<T> {
|
interface Props<T> extends InputProps<T> {
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
expand?: boolean;
|
expand?: boolean;
|
||||||
values: string[];
|
values: any[];
|
||||||
convert?: (v: string) => any;
|
|
||||||
toStr?: (v?: any) => string;
|
toStr?: (v?: any) => string;
|
||||||
fromStr?: (s: string) => any;
|
fromStr?: (s: string) => any;
|
||||||
}
|
}
|
||||||
@ -42,11 +41,11 @@ export function InputSelector<T>({
|
|||||||
label,
|
label,
|
||||||
help,
|
help,
|
||||||
values,
|
values,
|
||||||
convert,
|
fromStr = defaultFromString,
|
||||||
toStr = defaultToString,
|
toStr = defaultToString,
|
||||||
}: Props<keyof T>): VNode {
|
}: Props<keyof T>): VNode {
|
||||||
const { error, value, onChange } = useField<T>(name);
|
const { error, value, onChange } = useField<T>(name);
|
||||||
|
console.log(error);
|
||||||
return (
|
return (
|
||||||
<div class="field is-horizontal">
|
<div class="field is-horizontal">
|
||||||
<div class="field-label is-normal">
|
<div class="field-label is-normal">
|
||||||
@ -68,18 +67,17 @@ export function InputSelector<T>({
|
|||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const v = convert
|
onChange(fromStr(e.currentTarget.value));
|
||||||
? convert(e.currentTarget.value)
|
|
||||||
: e.currentTarget.value;
|
|
||||||
onChange(v);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{placeholder && <option>{placeholder}</option>}
|
{placeholder && <option>{placeholder}</option>}
|
||||||
{values.map((v, i) => (
|
{values.map((v, i) => {
|
||||||
<option key={i} value={v} selected={value === v}>
|
return (
|
||||||
{toStr(v)}
|
<option key={i} value={v} selected={value === v}>
|
||||||
</option>
|
{toStr(v)}
|
||||||
))}
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</select>
|
</select>
|
||||||
{help}
|
{help}
|
||||||
</p>
|
</p>
|
||||||
|
@ -96,7 +96,6 @@ export function InputWithAddon<T>({
|
|||||||
<i class="mdi mdi-alert" />
|
<i class="mdi mdi-alert" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{help}
|
|
||||||
{children}
|
{children}
|
||||||
</p>
|
</p>
|
||||||
{addonAfter && (
|
{addonAfter && (
|
||||||
@ -106,6 +105,7 @@ export function InputWithAddon<T>({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{error && <p class="help is-danger">{error}</p>}
|
{error && <p class="help is-danger">{error}</p>}
|
||||||
|
<span class="has-text-grey">{help}</span>
|
||||||
</div>
|
</div>
|
||||||
{side}
|
{side}
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentChildren, VNode } from "preact";
|
import { ComponentChildren, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
import { useFormContext } from "./FormProvider.js";
|
import { useFormContext } from "./FormProvider.js";
|
||||||
|
|
||||||
interface Use<V> {
|
interface Use<V> {
|
||||||
@ -37,10 +38,11 @@ export function useField<T>(name: keyof T): Use<T[typeof name]> {
|
|||||||
useFormContext<T>();
|
useFormContext<T>();
|
||||||
type P = typeof name;
|
type P = typeof name;
|
||||||
type V = T[P];
|
type V = T[P];
|
||||||
|
const [isDirty, setDirty] = useState(false);
|
||||||
const updateField =
|
const updateField =
|
||||||
(field: P) =>
|
(field: P) =>
|
||||||
(value: V): void => {
|
(value: V): void => {
|
||||||
|
setDirty(true);
|
||||||
return valueHandler((prev) => {
|
return valueHandler((prev) => {
|
||||||
return setValueDeeper(prev, String(field).split("."), value);
|
return setValueDeeper(prev, String(field).split("."), value);
|
||||||
});
|
});
|
||||||
@ -50,7 +52,6 @@ export function useField<T>(name: keyof T): Use<T[typeof name]> {
|
|||||||
const defaultFromString = (v: string): V => v as any;
|
const defaultFromString = (v: string): V => v as any;
|
||||||
const value = readField(object, String(name));
|
const value = readField(object, String(name));
|
||||||
const initial = readField(initialObject, String(name));
|
const initial = readField(initialObject, String(name));
|
||||||
const isDirty = value !== initial;
|
|
||||||
const hasError = readField(errors, String(name));
|
const hasError = readField(errors, String(name));
|
||||||
return {
|
return {
|
||||||
error: isDirty ? hasError : undefined,
|
error: isDirty ? hasError : undefined,
|
||||||
|
@ -144,12 +144,18 @@ export function CreatePage({
|
|||||||
|
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
const parsedPrice = !value.pricing?.order_price
|
||||||
|
? undefined
|
||||||
|
: Amounts.parse(value.pricing.order_price);
|
||||||
|
|
||||||
const errors: FormErrors<Entity> = {
|
const errors: FormErrors<Entity> = {
|
||||||
pricing: undefinedIfEmpty({
|
pricing: undefinedIfEmpty({
|
||||||
summary: !value.pricing?.summary ? i18n.str`required` : undefined,
|
summary: !value.pricing?.summary ? i18n.str`required` : undefined,
|
||||||
order_price: !value.pricing?.order_price
|
order_price: !value.pricing?.order_price
|
||||||
? i18n.str`required`
|
? i18n.str`required`
|
||||||
: Amounts.isZero(value.pricing.order_price)
|
: !parsedPrice
|
||||||
|
? i18n.str`not valid`
|
||||||
|
: Amounts.isZero(parsedPrice)
|
||||||
? i18n.str`must be greater than 0`
|
? i18n.str`must be greater than 0`
|
||||||
: undefined,
|
: undefined,
|
||||||
}),
|
}),
|
||||||
@ -333,8 +339,8 @@ export function CreatePage({
|
|||||||
}, [hasProducts, totalAsString]);
|
}, [hasProducts, totalAsString]);
|
||||||
|
|
||||||
const discountOrRise = rate(
|
const discountOrRise = rate(
|
||||||
value.pricing?.order_price || `${config.currency}:0`,
|
parsedPrice ?? Amounts.zeroOfCurrency(config.currency),
|
||||||
totalAsString,
|
totalPrice.amount,
|
||||||
);
|
);
|
||||||
|
|
||||||
const minAgeByProducts = allProducts.reduce(
|
const minAgeByProducts = allProducts.reduce(
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Amounts,
|
||||||
|
MerchantTemplateContractDetails,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
@ -35,7 +39,10 @@ import { InputSelector } from "../../../../components/form/InputSelector.js";
|
|||||||
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
|
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
|
||||||
import { useBackendContext } from "../../../../context/backend.js";
|
import { useBackendContext } from "../../../../context/backend.js";
|
||||||
import { MerchantBackend } from "../../../../declaration.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";
|
import { undefinedIfEmpty } from "../../../../utils/table.js";
|
||||||
|
|
||||||
type Entity = MerchantBackend.Template.TemplateAddDetails;
|
type Entity = MerchantBackend.Template.TemplateAddDetails;
|
||||||
@ -45,17 +52,14 @@ interface Props {
|
|||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const algorithms = ["0", "1", "2"];
|
const algorithms = [0, 1, 2];
|
||||||
const algorithmsNames = [
|
const algorithmsNames = ["off", "30s 8d TOTP-SHA1", "30s 8d eTOTP-SHA1"];
|
||||||
"off",
|
|
||||||
"30s 8d TOTP-SHA1 without amount",
|
|
||||||
"30s 8d eTOTP-SHA1 with amount",
|
|
||||||
];
|
|
||||||
|
|
||||||
export function CreatePage({ onCreate, onBack }: Props): VNode {
|
export function CreatePage({ onCreate, onBack }: Props): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const backend = useBackendContext();
|
const backend = useBackendContext();
|
||||||
|
|
||||||
|
const [showKey, setShowKey] = useState(false);
|
||||||
const [state, setState] = useState<Partial<Entity>>({
|
const [state, setState] = useState<Partial<Entity>>({
|
||||||
template_contract: {
|
template_contract: {
|
||||||
minimum_age: 0,
|
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> = {
|
const errors: FormErrors<Entity> = {
|
||||||
template_id: !state.template_id ? i18n.str`should not be empty` : undefined,
|
template_id: !state.template_id ? i18n.str`should not be empty` : undefined,
|
||||||
template_description: !state.template_description
|
template_description: !state.template_description
|
||||||
@ -73,6 +81,13 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
template_contract: !state.template_contract
|
template_contract: !state.template_contract
|
||||||
? undefined
|
? undefined
|
||||||
: undefinedIfEmpty({
|
: 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:
|
minimum_age:
|
||||||
state.template_contract.minimum_age < 0
|
state.template_contract.minimum_age < 0
|
||||||
? i18n.str`should be greater that 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
|
: state.template_contract.pay_duration.d_us < 1000 * 1000 //less than one second
|
||||||
? i18n.str`to short`
|
? i18n.str`to short`
|
||||||
: undefined,
|
: 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(
|
const hasErrors = Object.keys(errors).some(
|
||||||
@ -144,21 +168,32 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
/>
|
/>
|
||||||
<InputSelector<Entity>
|
<InputSelector<Entity>
|
||||||
name="pos_algorithm"
|
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`}
|
tooltip={i18n.str`Algorithm to use to verify transaction in offline mode`}
|
||||||
values={algorithms}
|
values={algorithms}
|
||||||
toStr={(v) => algorithmsNames[v]}
|
toStr={(v) => algorithmsNames[v]}
|
||||||
convert={(v) => Number(v)}
|
fromStr={(v) => Number(v)}
|
||||||
/>
|
/>
|
||||||
{state.pos_algorithm && state.pos_algorithm > 0 ? (
|
{state.pos_algorithm && state.pos_algorithm > 0 ? (
|
||||||
<Input<Entity>
|
<InputWithAddon<Entity>
|
||||||
name="pos_key"
|
name="pos_key"
|
||||||
label={i18n.str`Point-of-sale 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`}
|
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={
|
side={
|
||||||
<span data-tooltip={i18n.str`generate random secret key`}>
|
<span style={{ display: "flex" }}>
|
||||||
<button
|
<button
|
||||||
|
data-tooltip={i18n.str`generate random secret key`}
|
||||||
class="button is-info mr-3"
|
class="button is-info mr-3"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
const pos_key = randomBase32Key();
|
const pos_key = randomBase32Key();
|
||||||
@ -167,6 +202,23 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<i18n.Translate>random</i18n.Translate>
|
<i18n.Translate>random</i18n.Translate>
|
||||||
</button>
|
</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>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -127,6 +127,15 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column" />
|
<div class="column" />
|
||||||
<div class="column is-four-fifths">
|
<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
|
<FormProvider
|
||||||
object={state}
|
object={state}
|
||||||
valueHandler={setState}
|
valueHandler={setState}
|
||||||
@ -134,7 +143,11 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<InputCurrency<Entity>
|
<InputCurrency<Entity>
|
||||||
name="amount"
|
name="amount"
|
||||||
label={i18n.str`Amount`}
|
label={
|
||||||
|
fixedAmount
|
||||||
|
? i18n.str`Fixed amount`
|
||||||
|
: i18n.str`Default amount`
|
||||||
|
}
|
||||||
readonly={fixedAmount}
|
readonly={fixedAmount}
|
||||||
tooltip={i18n.str`Amount of the order`}
|
tooltip={i18n.str`Amount of the order`}
|
||||||
/>
|
/>
|
||||||
@ -142,7 +155,11 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {
|
|||||||
name="summary"
|
name="summary"
|
||||||
inputType="multiline"
|
inputType="multiline"
|
||||||
readonly={fixedSummary}
|
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`}
|
tooltip={i18n.str`Title of the order to be shown to the customer`}
|
||||||
/>
|
/>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Amounts,
|
||||||
|
MerchantTemplateContractDetails,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
@ -35,7 +39,10 @@ import { InputSelector } from "../../../../components/form/InputSelector.js";
|
|||||||
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
|
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
|
||||||
import { useBackendContext } from "../../../../context/backend.js";
|
import { useBackendContext } from "../../../../context/backend.js";
|
||||||
import { MerchantBackend, WithId } from "../../../../declaration.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";
|
import { undefinedIfEmpty } from "../../../../utils/table.js";
|
||||||
|
|
||||||
type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId;
|
type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId;
|
||||||
@ -46,12 +53,8 @@ interface Props {
|
|||||||
template: Entity;
|
template: Entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
const algorithms = ["0", "1", "2"];
|
const algorithms = [0, 1, 2];
|
||||||
const algorithmsNames = [
|
const algorithmsNames = ["off", "30s 8d TOTP-SHA1", "30s 8d eTOTP-SHA1"];
|
||||||
"off",
|
|
||||||
"30s 8d TOTP-SHA1 without amount",
|
|
||||||
"30s 8d eTOTP-SHA1 with amount",
|
|
||||||
];
|
|
||||||
|
|
||||||
export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -60,6 +63,10 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
|||||||
const [showKey, setShowKey] = useState(false);
|
const [showKey, setShowKey] = useState(false);
|
||||||
const [state, setState] = useState<Partial<Entity>>(template);
|
const [state, setState] = useState<Partial<Entity>>(template);
|
||||||
|
|
||||||
|
const parsedPrice = !state.template_contract?.amount
|
||||||
|
? undefined
|
||||||
|
: Amounts.parse(state.template_contract?.amount);
|
||||||
|
|
||||||
const errors: FormErrors<Entity> = {
|
const errors: FormErrors<Entity> = {
|
||||||
template_description: !state.template_description
|
template_description: !state.template_description
|
||||||
? i18n.str`should not be empty`
|
? i18n.str`should not be empty`
|
||||||
@ -67,6 +74,13 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
|||||||
template_contract: !state.template_contract
|
template_contract: !state.template_contract
|
||||||
? undefined
|
? undefined
|
||||||
: undefinedIfEmpty({
|
: 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:
|
minimum_age:
|
||||||
state.template_contract.minimum_age < 0
|
state.template_contract.minimum_age < 0
|
||||||
? i18n.str`should be greater that 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
|
: state.template_contract.pay_duration.d_us < 1000 * 1000 // less than one second
|
||||||
? i18n.str`to short`
|
? i18n.str`to short`
|
||||||
: undefined,
|
: 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(
|
const hasErrors = Object.keys(errors).some(
|
||||||
@ -155,20 +178,21 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
|||||||
/>
|
/>
|
||||||
<InputSelector<Entity>
|
<InputSelector<Entity>
|
||||||
name="pos_algorithm"
|
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`}
|
tooltip={i18n.str`Algorithm to use to verify transaction in offline mode`}
|
||||||
values={algorithms}
|
values={algorithms}
|
||||||
toStr={(v) => algorithmsNames[v]}
|
toStr={(v) => algorithmsNames[v]}
|
||||||
convert={(v) => Number(v)}
|
fromStr={(v) => Number(v)}
|
||||||
/>
|
/>
|
||||||
{state.pos_algorithm && state.pos_algorithm > 0 ? (
|
{state.pos_algorithm && state.pos_algorithm > 0 ? (
|
||||||
<InputWithAddon<Entity>
|
<InputWithAddon<Entity>
|
||||||
name="pos_key"
|
name="pos_key"
|
||||||
label={i18n.str`Point-of-sale key`}
|
label={i18n.str`Point-of-sale key`}
|
||||||
inputType={showKey ? "text" : "password"}
|
inputType={showKey ? "text" : "password"}
|
||||||
help=""
|
help="Be sure to be very hard to guess or use the random generator"
|
||||||
expand
|
expand
|
||||||
tooltip={i18n.str`Useful to validate the purchase`}
|
tooltip={i18n.str`Useful to validate the purchase`}
|
||||||
|
fromStr={(v) => v.toUpperCase()}
|
||||||
addonAfter={
|
addonAfter={
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
{showKey ? (
|
{showKey ? (
|
||||||
@ -179,7 +203,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
side={
|
side={
|
||||||
<span>
|
<span style={{ display: "flex" }}>
|
||||||
<button
|
<button
|
||||||
data-tooltip={i18n.str`generate random secret key`}
|
data-tooltip={i18n.str`generate random secret key`}
|
||||||
class="button is-info mr-3"
|
class="button is-info mr-3"
|
||||||
|
@ -59,14 +59,12 @@ export function mergeRefunds(
|
|||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const rate = (one: string, two: string): number => {
|
export function rate(a: AmountJson, b: AmountJson): number {
|
||||||
const a = Amounts.parseOrThrow(one);
|
|
||||||
const b = Amounts.parseOrThrow(two);
|
|
||||||
const af = toFloat(a);
|
const af = toFloat(a);
|
||||||
const bf = toFloat(b);
|
const bf = toFloat(b);
|
||||||
if (bf === 0) return 0;
|
if (bf === 0) return 0;
|
||||||
return af / bf;
|
return af / bf;
|
||||||
};
|
}
|
||||||
|
|
||||||
function toFloat(amount: AmountJson): number {
|
function toFloat(amount: AmountJson): number {
|
||||||
return amount.value + amount.fraction / amountFractionalBase;
|
return amount.value + amount.fraction / amountFractionalBase;
|
||||||
|
@ -46,6 +46,14 @@ function encodeBase32(data: ArrayBuffer) {
|
|||||||
return sb;
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isBase32RFC3548Charset(s: string): boolean {
|
||||||
|
for (let idx = 0; idx < s.length; idx++) {
|
||||||
|
const c = s.charAt(idx);
|
||||||
|
if (encTable.indexOf(c) === -1) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export function randomBase32Key(): string {
|
export function randomBase32Key(): string {
|
||||||
var buf = new Uint8Array(20);
|
var buf = new Uint8Array(20);
|
||||||
window.crypto.getRandomValues(buf);
|
window.crypto.getRandomValues(buf);
|
||||||
|
Loading…
Reference in New Issue
Block a user