diff options
author | Özgür Kesim <oec-taler@kesim.org> | 2023-08-15 13:47:29 +0200 |
---|---|---|
committer | Özgür Kesim <oec-taler@kesim.org> | 2023-08-15 13:47:29 +0200 |
commit | 819949d7f2cfcce8d334cbfdb701255daac64951 (patch) | |
tree | 592043554f59ec209f9afc1c31c20c4fb585c3ca /packages/merchant-backoffice-ui/src/paths/instance | |
parent | 77ea209ddb6d457db0ce113f91247207cc29fbd8 (diff) | |
parent | adb0e70f151e63d103f27852312319520f4d0a84 (diff) |
Merge branch 'master' into age-withdraw
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance')
6 files changed, 225 insertions, 217 deletions
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx index c8cc20ae0..fa9347c6e 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx @@ -43,6 +43,7 @@ import { Duration, MerchantBackend, WithId } from "../../../../declaration.js"; import { OrderCreateSchema as schema } from "../../../../schemas/index.js"; import { rate } from "../../../../utils/amount.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; +import { useSettings } from "../../../../hooks/useSettings.js"; interface Props { onCreate: (d: MerchantBackend.Orders.PostOrderRequest) => void; @@ -62,8 +63,8 @@ function with_defaults(config: InstanceConfig): Partial<Entity> { !config.default_pay_delay || config.default_pay_delay.d_us === "forever" ? undefined : add(new Date(), { - seconds: config.default_pay_delay.d_us / (1000 * 1000), - }); + seconds: config.default_pay_delay.d_us / (1000 * 1000), + }); return { inventoryProducts: {}, @@ -138,7 +139,7 @@ export function CreatePage({ const [value, valueHandler] = useState(with_defaults(instanceConfig)); const config = useConfigContext(); const zero = Amounts.zeroOfCurrency(config.currency); - + const [settings] = useSettings() const inventoryList = Object.values(value.inventoryProducts || {}); const productList = Object.values(value.products || {}); @@ -154,10 +155,10 @@ export function CreatePage({ order_price: !value.pricing?.order_price ? i18n.str`required` : !parsedPrice - ? i18n.str`not valid` - : Amounts.isZero(parsedPrice) - ? i18n.str`must be greater than 0` - : undefined, + ? i18n.str`not valid` + : Amounts.isZero(parsedPrice) + ? i18n.str`must be greater than 0` + : undefined, }), extra: value.extra && !stringIsValidJSON(value.extra) @@ -167,47 +168,47 @@ export function CreatePage({ refund_deadline: !value.payments?.refund_deadline ? undefined : !isFuture(value.payments.refund_deadline) - ? i18n.str`should be in the future` - : value.payments.pay_deadline && - isBefore(value.payments.refund_deadline, value.payments.pay_deadline) - ? i18n.str`refund deadline cannot be before pay deadline` - : value.payments.wire_transfer_deadline && - isBefore( - value.payments.wire_transfer_deadline, - value.payments.refund_deadline, - ) - ? i18n.str`wire transfer deadline cannot be before refund deadline` - : undefined, + ? i18n.str`should be in the future` + : value.payments.pay_deadline && + isBefore(value.payments.refund_deadline, value.payments.pay_deadline) + ? i18n.str`refund deadline cannot be before pay deadline` + : value.payments.wire_transfer_deadline && + isBefore( + value.payments.wire_transfer_deadline, + value.payments.refund_deadline, + ) + ? i18n.str`wire transfer deadline cannot be before refund deadline` + : undefined, pay_deadline: !value.payments?.pay_deadline ? undefined : !isFuture(value.payments.pay_deadline) - ? i18n.str`should be in the future` - : value.payments.wire_transfer_deadline && - isBefore( - value.payments.wire_transfer_deadline, - value.payments.pay_deadline, - ) - ? i18n.str`wire transfer deadline cannot be before pay deadline` - : undefined, + ? i18n.str`should be in the future` + : value.payments.wire_transfer_deadline && + isBefore( + value.payments.wire_transfer_deadline, + value.payments.pay_deadline, + ) + ? i18n.str`wire transfer deadline cannot be before pay deadline` + : undefined, auto_refund_deadline: !value.payments?.auto_refund_deadline ? undefined : !isFuture(value.payments.auto_refund_deadline) - ? i18n.str`should be in the future` - : !value.payments?.refund_deadline - ? i18n.str`should have a refund deadline` - : !isAfter( - value.payments.refund_deadline, - value.payments.auto_refund_deadline, - ) - ? i18n.str`auto refund cannot be after refund deadline` - : undefined, + ? i18n.str`should be in the future` + : !value.payments?.refund_deadline + ? i18n.str`should have a refund deadline` + : !isAfter( + value.payments.refund_deadline, + value.payments.auto_refund_deadline, + ) + ? i18n.str`auto refund cannot be after refund deadline` + : undefined, }), shipping: undefinedIfEmpty({ delivery_date: !value.shipping?.delivery_date ? undefined : !isFuture(value.shipping.delivery_date) - ? i18n.str`should be in the future` - : undefined, + ? i18n.str`should be in the future` + : undefined, }), }; const hasErrors = Object.keys(errors).some( @@ -227,27 +228,27 @@ export function CreatePage({ extra: value.extra, pay_deadline: value.payments.pay_deadline ? { - t_s: Math.floor(value.payments.pay_deadline.getTime() / 1000), - } + t_s: Math.floor(value.payments.pay_deadline.getTime() / 1000), + } : undefined, wire_transfer_deadline: value.payments.wire_transfer_deadline ? { - t_s: Math.floor( - value.payments.wire_transfer_deadline.getTime() / 1000, - ), - } + t_s: Math.floor( + value.payments.wire_transfer_deadline.getTime() / 1000, + ), + } : undefined, refund_deadline: value.payments.refund_deadline ? { - t_s: Math.floor(value.payments.refund_deadline.getTime() / 1000), - } + t_s: Math.floor(value.payments.refund_deadline.getTime() / 1000), + } : undefined, auto_refund: value.payments.auto_refund_deadline ? { - d_us: Math.floor( - value.payments.auto_refund_deadline.getTime() * 1000, - ), - } + d_us: Math.floor( + value.payments.auto_refund_deadline.getTime() * 1000, + ), + } : undefined, wire_fee_amortization: value.payments.wire_fee_amortization as number, max_fee: value.payments.max_fee as string, @@ -374,13 +375,15 @@ export function CreatePage({ inventory={instanceInventory} /> - <NonInventoryProductFrom - productToEdit={editingProduct} - onAddProduct={(p) => { - setEditingProduct(undefined); - return addNewProduct(p); - }} - /> + {settings.advanceOrderMode && + <NonInventoryProductFrom + productToEdit={editingProduct} + onAddProduct={(p) => { + setEditingProduct(undefined); + return addNewProduct(p); + }} + /> + } {allProducts.length > 0 && ( <ProductList @@ -423,8 +426,8 @@ export function CreatePage({ discountOrRise > 0 && (discountOrRise < 1 ? `discount of %${Math.round( - (1 - discountOrRise) * 100, - )}` + (1 - discountOrRise) * 100, + )}` : `rise of %${Math.round((discountOrRise - 1) * 100)}`) } tooltip={i18n.str`Amount to be paid by the customer`} @@ -445,102 +448,108 @@ export function CreatePage({ tooltip={i18n.str`Title of the order to be shown to the customer`} /> - <InputGroup - name="shipping" - label={i18n.str`Shipping and Fulfillment`} - initialActive - > - <InputDate - name="shipping.delivery_date" - label={i18n.str`Delivery date`} - tooltip={i18n.str`Deadline for physical delivery assured by the merchant.`} - /> - {value.shipping?.delivery_date && ( - <InputGroup - name="shipping.delivery_location" - label={i18n.str`Location`} - tooltip={i18n.str`address where the products will be delivered`} - > - <InputLocation name="shipping.delivery_location" /> - </InputGroup> - )} - <Input - name="shipping.fullfilment_url" - label={i18n.str`Fulfillment URL`} - tooltip={i18n.str`URL to which the user will be redirected after successful payment.`} - /> - </InputGroup> + {settings.advanceOrderMode && + <InputGroup + name="shipping" + label={i18n.str`Shipping and Fulfillment`} + initialActive + > + <InputDate + name="shipping.delivery_date" + label={i18n.str`Delivery date`} + tooltip={i18n.str`Deadline for physical delivery assured by the merchant.`} + /> + {value.shipping?.delivery_date && ( + <InputGroup + name="shipping.delivery_location" + label={i18n.str`Location`} + tooltip={i18n.str`address where the products will be delivered`} + > + <InputLocation name="shipping.delivery_location" /> + </InputGroup> + )} + <Input + name="shipping.fullfilment_url" + label={i18n.str`Fulfillment URL`} + tooltip={i18n.str`URL to which the user will be redirected after successful payment.`} + /> + </InputGroup> + } - <InputGroup - name="payments" - label={i18n.str`Taler payment options`} - tooltip={i18n.str`Override default Taler payment settings for this order`} - > - <InputDate - name="payments.pay_deadline" - label={i18n.str`Payment deadline`} - tooltip={i18n.str`Deadline for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline.`} - /> - <InputDate - name="payments.refund_deadline" - label={i18n.str`Refund deadline`} - tooltip={i18n.str`Time until which the order can be refunded by the merchant.`} - /> - <InputDate - name="payments.wire_transfer_deadline" - label={i18n.str`Wire transfer deadline`} - tooltip={i18n.str`Deadline for the exchange to make the wire transfer.`} - /> - <InputDate - name="payments.auto_refund_deadline" - label={i18n.str`Auto-refund deadline`} - tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`} - /> + {settings.advanceOrderMode && + <InputGroup + name="payments" + label={i18n.str`Taler payment options`} + tooltip={i18n.str`Override default Taler payment settings for this order`} + > + <InputDate + name="payments.pay_deadline" + label={i18n.str`Payment deadline`} + tooltip={i18n.str`Deadline for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline.`} + /> + <InputDate + name="payments.refund_deadline" + label={i18n.str`Refund deadline`} + tooltip={i18n.str`Time until which the order can be refunded by the merchant.`} + /> + <InputDate + name="payments.wire_transfer_deadline" + label={i18n.str`Wire transfer deadline`} + tooltip={i18n.str`Deadline for the exchange to make the wire transfer.`} + /> + <InputDate + name="payments.auto_refund_deadline" + label={i18n.str`Auto-refund deadline`} + tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`} + /> - <InputCurrency - name="payments.max_fee" - label={i18n.str`Maximum deposit fee`} - tooltip={i18n.str`Maximum deposit fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`} - /> - <InputCurrency - name="payments.max_wire_fee" - label={i18n.str`Maximum wire fee`} - tooltip={i18n.str`Maximum aggregate wire fees the merchant is willing to cover for this order. Wire fees exceeding this amount are to be covered by the customers.`} - /> - <InputNumber - name="payments.wire_fee_amortization" - label={i18n.str`Wire fee amortization`} - tooltip={i18n.str`Factor by which wire fees exceeding the above threshold are divided to determine the share of excess wire fees to be paid explicitly by the consumer.`} - /> - <InputBoolean - name="payments.createToken" - label={i18n.str`Create token`} - tooltip={i18n.str`Uncheck this option if the merchant backend generated an order ID with enough entropy to prevent adversarial claims.`} - /> - <InputNumber - name="payments.minimum_age" - label={i18n.str`Minimum age required`} - tooltip={i18n.str`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`} - help={ - minAgeByProducts > 0 - ? i18n.str`Min age defined by the producs is ${minAgeByProducts}` - : undefined - } - /> - </InputGroup> + <InputCurrency + name="payments.max_fee" + label={i18n.str`Maximum deposit fee`} + tooltip={i18n.str`Maximum deposit fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`} + /> + <InputCurrency + name="payments.max_wire_fee" + label={i18n.str`Maximum wire fee`} + tooltip={i18n.str`Maximum aggregate wire fees the merchant is willing to cover for this order. Wire fees exceeding this amount are to be covered by the customers.`} + /> + <InputNumber + name="payments.wire_fee_amortization" + label={i18n.str`Wire fee amortization`} + tooltip={i18n.str`Factor by which wire fees exceeding the above threshold are divided to determine the share of excess wire fees to be paid explicitly by the consumer.`} + /> + <InputBoolean + name="payments.createToken" + label={i18n.str`Create token`} + tooltip={i18n.str`Uncheck this option if the merchant backend generated an order ID with enough entropy to prevent adversarial claims.`} + /> + <InputNumber + name="payments.minimum_age" + label={i18n.str`Minimum age required`} + tooltip={i18n.str`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`} + help={ + minAgeByProducts > 0 + ? i18n.str`Min age defined by the producs is ${minAgeByProducts}` + : undefined + } + /> + </InputGroup> + } - <InputGroup - name="extra" - label={i18n.str`Additional information`} - tooltip={i18n.str`Custom information to be included in the contract for this order.`} - > - <Input + {settings.advanceOrderMode && + <InputGroup name="extra" - inputType="multiline" - label={`Value`} - tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`} - /> - </InputGroup> + label={i18n.str`Additional information`} + tooltip={i18n.str`Custom information to be included in the contract for this order.`} + > + <Input + name="extra" + inputType="multiline" + label={`Value`} + tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`} + /> + </InputGroup> + } </FormProvider> <div class="buttons is-right mt-5"> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx index 8dabfbe12..8965d41c9 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx @@ -21,7 +21,7 @@ import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { format } from "date-fns"; +import { format, formatDistance } from "date-fns"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { FormProvider } from "../../../../components/form/FormProvider.js"; @@ -223,6 +223,7 @@ function ClaimedPage({ </div> </div> </div> + <div class="level"> <div class="level-left"> <div class="level-item"> @@ -419,6 +420,11 @@ function PaidPage({ } } + const now = new Date() + const nextEvent = events.find((e) => { + return e.when.getTime() > now.getTime() + }) + const [value, valueHandler] = useState<Partial<Paid>>(order); const { url } = useBackendContext(); const refundHost = url.replace(/.*:\/\//, ""); // remove protocol part @@ -504,22 +510,13 @@ function PaidPage({ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", - // maxWidth: '100%', }} > <p> - <a - href={order.contract_terms.fulfillment_url} - rel="nofollow" - target="new" - > - {order.contract_terms.fulfillment_url} - </a> - </p> - <p> - {format( - new Date(order.contract_terms.timestamp.t_s * 1000), - "yyyy/MM/dd HH:mm:ss", + <i18n.Translate>Next event in </i18n.Translate> {formatDistance( + nextEvent!.when, + new Date(), + // "yyyy/MM/dd HH:mm:ss", )} </p> </div> @@ -668,9 +665,9 @@ function UnpaidPage({ {order.creation_time.t_s === "never" ? "never" : format( - new Date(order.creation_time.t_s * 1000), - "yyyy-MM-dd HH:mm:ss", - )} + new Date(order.creation_time.t_s * 1000), + "yyyy-MM-dd HH:mm:ss", + )} </p> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx index d73ba3acc..e68889a92 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/Timeline.tsx @@ -67,7 +67,7 @@ export function Timeline({ events: e }: Props) { ); case "start": return ( - <div class="timeline-marker is-icon is-success"> + <div class="timeline-marker is-icon"> <i class="mdi mdi-flag " /> </div> ); @@ -104,7 +104,7 @@ export function Timeline({ events: e }: Props) { } })()} <div class="timeline-content"> - <p class="heading">{format(e.when, "yyyy/MM/dd HH:mm:ss")}</p> + {e.description !== "now" && <p class="heading">{format(e.when, "yyyy/MM/dd HH:mm:ss")}</p>} <p>{e.description}</p> </div> </div> @@ -117,12 +117,12 @@ export interface Event { when: Date; description: string; type: - | "start" - | "refund" - | "refund-taken" - | "wired" - | "wired-range" - | "deadline" - | "delivery" - | "now"; + | "start" + | "refund" + | "refund-taken" + | "wired" + | "wired-range" + | "deadline" + | "delivery" + | "now"; } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx index 56d9dda74..37770d273 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx @@ -164,7 +164,7 @@ export function ListPage({ <div class="field has-addons"> {jumpToDate && ( <div class="control"> - <a class="button" onClick={() => onSelectDate(undefined)}> + <a class="button is-fullwidth" onClick={() => onSelectDate(undefined)}> <span class="icon" data-tooltip={i18n.str`clear date filter`} @@ -191,7 +191,7 @@ export function ListPage({ <div class="control"> <span class="has-tooltip-left" data-tooltip={dateTooltip}> <a - class="button" + class="button is-fullwidth" onClick={() => { setPickDate(true); }} 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 4dde202c4..e20b9bc27 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 @@ -85,34 +85,34 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { template_contract: !state.template_contract ? undefined : undefinedIfEmpty({ - amount: !state.template_contract?.amount - ? undefined - : !parsedPrice + 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` + ? 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" + 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>), + ? 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, + ? 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( @@ -139,7 +139,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { > <InputWithAddon<Entity> name="template_id" - addonBefore={`${backend.url}/instances/templates/`} + help={`${backend.url}/instances/templates/${state.template_id ?? ""}`} label={i18n.str`Identifier`} tooltip={i18n.str`Name of the template in URLs.`} /> 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 90084f113..0f30efafd 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 @@ -34,6 +34,7 @@ import { useBackendContext } from "../../../../context/backend.js"; import { useConfigContext } from "../../../../context/config.js"; import { useInstanceContext } from "../../../../context/instance.js"; import { MerchantBackend } from "../../../../declaration.js"; +import { stringifyPayTemplateUri } from "@gnu-taler/taler-util"; type Entity = MerchantBackend.Template.UsingTemplateDetails; @@ -64,46 +65,47 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode { const fixedAmount = !!template.template_contract.amount; const fixedSummary = !!template.template_contract.summary; - const params = new URLSearchParams(); + const templateParams: Record<string, string> = {} if (!fixedAmount) { if (state.amount) { - params.append("amount", state.amount); + templateParams.amount = state.amount } else { - params.append("amount", config.currency); + templateParams.amount = config.currency } } + if (!fixedSummary) { - params.append("summary", state.summary ?? ""); + templateParams.summary = state.summary ?? "" } - const paramsStr = fixedAmount && fixedSummary ? "" : "?" + params.toString(); - const merchantURL = new URL(backendUrl); - - const talerProto = - merchantURL.protocol === "http:" ? "taler+http:" : "taler:"; + const merchantBaseUrl = new URL(backendUrl).href; - const payTemplateUri = `${talerProto}//pay-template/${merchantURL.hostname}/${templateId}${paramsStr}`; + const payTemplateUri = stringifyPayTemplateUri({ + merchantBaseUrl, + templateId, + templateParams + }) const issuer = encodeURIComponent( - `${new URL(backendUrl).hostname}/${instanceId}`, + `${new URL(backendUrl).host}/${instanceId}`, ); const oauthUri = !template.pos_algorithm ? undefined : template.pos_algorithm === 1 - ? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30` - : template.pos_algorithm === 2 - ? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30` - : undefined; + ? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30` + : template.pos_algorithm === 2 + ? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30` + : undefined; const keySlice = template.pos_key?.substring(0, 4); const oauthUriWithoutSecret = !template.pos_algorithm ? undefined : template.pos_algorithm === 1 - ? `otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30` - : template.pos_algorithm === 2 - ? `otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30` - : undefined; + ? `otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30` + : template.pos_algorithm === 2 + ? `otpauth://totp/${issuer}:${templateId}?secret=${keySlice}...&issuer=${issuer}&algorithm=SHA1&digits=8&period=30` + : undefined; return ( <div> {oauthUri && ( |