diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/templates')
3 files changed, 142 insertions, 124 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 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> |