diff options
Diffstat (limited to 'packages/bank-ui/src/pages/regional/ConversionConfig.tsx')
-rw-r--r-- | packages/bank-ui/src/pages/regional/ConversionConfig.tsx | 1464 |
1 files changed, 827 insertions, 637 deletions
diff --git a/packages/bank-ui/src/pages/regional/ConversionConfig.tsx b/packages/bank-ui/src/pages/regional/ConversionConfig.tsx index 8845ec9a0..818a131e0 100644 --- a/packages/bank-ui/src/pages/regional/ConversionConfig.tsx +++ b/packages/bank-ui/src/pages/regional/ConversionConfig.tsx @@ -15,13 +15,14 @@ */ import { + AbsoluteTime, AmountJson, Amounts, HttpStatusCode, TalerBankConversionApi, TalerError, TranslatedString, - assertUnreachable + assertUnreachable, } from "@gnu-taler/taler-util"; import { Attention, @@ -30,18 +31,30 @@ import { ShowInputErrorLabel, useLocalNotification, useTranslationContext, - utils + utils, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { useBankCoreApiContext } from "../../context/config.js"; import { useSessionState } from "../../hooks/session.js"; -import { TransferCalculation, useCashinEstimator, useCashoutEstimator, useConversionInfo } from "../../hooks/regional.js"; +import { + TransferCalculation, + useCashinEstimator, + useCashoutEstimator, + useConversionInfo, +} from "../../hooks/regional.js"; import { RouteDefinition } from "../../route.js"; import { undefinedIfEmpty } from "../../utils.js"; import { InputAmount, RenderAmount } from "../PaytoWireTransferForm.js"; import { ProfileNavigation } from "../ProfileNavigation.js"; -import { FormErrors, FormStatus, FormValues, RecursivePartial, UIField, useFormState } from "../../hooks/form.js"; +import { + FormErrors, + FormStatus, + FormValues, + RecursivePartial, + UIField, + useFormState, +} from "../../hooks/form.js"; interface Props { routeMyAccountDetails: RouteDefinition; @@ -53,11 +66,12 @@ interface Props { onUpdateSuccess: () => void; } -type FormType = { amount: AmountJson, conv: TalerBankConversionApi.ConversionRate } - +type FormType = { + amount: AmountJson; + conv: TalerBankConversionApi.ConversionRate; +}; function useComponentState({ - onUpdateSuccess, routeCancel, routeConversionConfig, routeMyAccountCashout, @@ -67,9 +81,11 @@ function useComponentState({ }: Props): utils.RecursiveState<VNode> { const { i18n } = useTranslationContext(); - const result = useConversionInfo() - const info = result && !(result instanceof TalerError) && result.type === "ok" ? - result.body : undefined; + const result = useConversionInfo(); + const info = + result && !(result instanceof TalerError) && result.type === "ok" + ? result.body + : undefined; const { state: credentials } = useSessionState(); const creds = @@ -78,17 +94,17 @@ function useComponentState({ : credentials; if (!info) { - return <i18n.Translate>loading...</i18n.Translate> + return <i18n.Translate>loading...</i18n.Translate>; } if (!creds) { - return <i18n.Translate>only admin can setup conversion</i18n.Translate> + return <i18n.Translate>only admin can setup conversion</i18n.Translate>; } - return () => { + return function afterComponentLoads() { const { i18n } = useTranslationContext(); - const { bank, conversion, config } = useBankCoreApiContext(); + const { conversion } = useBankCoreApiContext(); const [notification, notify, handleError] = useLocalNotification(); @@ -96,66 +112,91 @@ function useComponentState({ amount: "100", conv: { cashin_min_amount: info.conversion_rate.cashin_min_amount.split(":")[1], - cashin_tiny_amount: info.conversion_rate.cashin_tiny_amount.split(":")[1], + cashin_tiny_amount: + info.conversion_rate.cashin_tiny_amount.split(":")[1], cashin_fee: info.conversion_rate.cashin_fee.split(":")[1], cashin_ratio: info.conversion_rate.cashin_ratio, cashin_rounding_mode: info.conversion_rate.cashin_rounding_mode, - cashout_min_amount: info.conversion_rate.cashout_min_amount.split(":")[1], - cashout_tiny_amount: info.conversion_rate.cashout_tiny_amount.split(":")[1], + cashout_min_amount: + info.conversion_rate.cashout_min_amount.split(":")[1], + cashout_tiny_amount: + info.conversion_rate.cashout_tiny_amount.split(":")[1], cashout_fee: info.conversion_rate.cashout_fee.split(":")[1], cashout_ratio: info.conversion_rate.cashout_ratio, cashout_rounding_mode: info.conversion_rate.cashout_rounding_mode, - } - } + }, + }; const [form, status] = useFormState<FormType>( initalState, - createFormValidator(i18n, info.regional_currency, info.fiat_currency) - ) + createFormValidator(i18n, info.regional_currency, info.fiat_currency), + ); - const { - estimateByDebit: calculateCashoutFromDebit, - } = useCashoutEstimator(); + const { estimateByDebit: calculateCashoutFromDebit } = + useCashoutEstimator(); - const { - estimateByDebit: calculateCashinFromDebit, - } = useCashinEstimator(); + const { estimateByDebit: calculateCashinFromDebit } = useCashinEstimator(); - const [calculationResult, setCalc] = useState<{ cashin: TransferCalculation, cashout: TransferCalculation }>() + const [calculationResult, setCalc] = useState<{ + cashin: TransferCalculation; + cashout: TransferCalculation; + }>(); useEffect(() => { async function doAsync() { await handleError(async () => { if (!info) return; if (!form.amount?.value || form.amount.error) return; - const in_amount = Amounts.parseOrThrow(`${info.fiat_currency}:${form.amount.value}`) - const in_fee = Amounts.parseOrThrow(info.conversion_rate.cashin_fee) + const in_amount = Amounts.parseOrThrow( + `${info.fiat_currency}:${form.amount.value}`, + ); + const in_fee = Amounts.parseOrThrow(info.conversion_rate.cashin_fee); const cashin = await calculateCashinFromDebit(in_amount, in_fee); if (cashin === "amount-is-too-small") { - setCalc(undefined) + setCalc(undefined); return; } // const out_amount = Amounts.parseOrThrow(`${info.regional_currency}:${form.amount.value}`) - const out_fee = Amounts.parseOrThrow(info.conversion_rate.cashout_fee) - const cashout = await calculateCashoutFromDebit(cashin.credit, out_fee); + const out_fee = Amounts.parseOrThrow( + info.conversion_rate.cashout_fee, + ); + const cashout = await calculateCashoutFromDebit( + cashin.credit, + out_fee, + ); setCalc({ cashin, cashout }); }); } doAsync(); - }, [form.amount?.value, form.conv?.cashin_fee?.value, form.conv?.cashout_fee?.value]); - - const [section, setSection] = useState<"detail" | "cashout" | "cashin">("detail") - const cashinCalc = calculationResult?.cashin === "amount-is-too-small" ? undefined : calculationResult?.cashin - const cashoutCalc = calculationResult?.cashout === "amount-is-too-small" ? undefined : calculationResult?.cashout + }, [ + form.amount?.value, + form.conv?.cashin_fee?.value, + form.conv?.cashout_fee?.value, + ]); + + const [section, setSection] = useState<"detail" | "cashout" | "cashin">( + "detail", + ); + const cashinCalc = + calculationResult?.cashin === "amount-is-too-small" + ? undefined + : calculationResult?.cashin; + const cashoutCalc = + calculationResult?.cashout === "amount-is-too-small" + ? undefined + : calculationResult?.cashout; async function doUpdate() { - if (!creds) return + if (!creds) return; await handleError(async () => { if (status.status === "fail") return; - const resp = await conversion.updateConversionRate(creds.token, status.result.conv) + const resp = await conversion.updateConversionRate( + creds.token, + status.result.conv, + ); if (resp.type === "ok") { - setSection("detail") + setSection("detail"); } else { switch (resp.case) { case HttpStatusCode.Unauthorized: { @@ -164,6 +205,7 @@ function useComponentState({ title: i18n.str`Wrong credentials`, description: resp.detail.hint as TranslatedString, debug: resp.detail, + when: AbsoluteTime.now(), }); } case HttpStatusCode.NotImplemented: { @@ -172,6 +214,7 @@ function useComponentState({ title: i18n.str`Conversion is disabled`, description: resp.detail.hint as TranslatedString, debug: resp.detail, + when: AbsoluteTime.now(), }); } default: @@ -181,16 +224,16 @@ function useComponentState({ }); } - const in_ratio = Number.parseFloat(info.conversion_rate.cashin_ratio) - const out_ratio = Number.parseFloat(info.conversion_rate.cashout_ratio) + const in_ratio = Number.parseFloat(info.conversion_rate.cashin_ratio); + const out_ratio = Number.parseFloat(info.conversion_rate.cashout_ratio); const both_high = in_ratio > 1 && out_ratio > 1; const both_low = in_ratio < 1 && out_ratio < 1; - return ( <div> - <ProfileNavigation current="conversion" + <ProfileNavigation + current="conversion" routeMyAccountCashout={routeMyAccountCashout} routeMyAccountDelete={routeMyAccountDelete} routeMyAccountDetails={routeMyAccountDetails} @@ -200,7 +243,6 @@ function useComponentState({ <LocalNotificationBanner notification={notification} /> <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg"> - <div class="px-4 sm:px-0"> <h2 class="text-base font-semibold leading-7 text-gray-900"> <i18n.Translate>Conversion</i18n.Translate> @@ -218,7 +260,7 @@ function useComponentState({ aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onChange={() => { - setSection("detail") + setSection("detail"); }} /> <span class="flex flex-1"> @@ -242,7 +284,7 @@ function useComponentState({ aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onChange={() => { - setSection("cashout") + setSection("cashout"); }} /> <span class="flex flex-1"> @@ -265,7 +307,7 @@ function useComponentState({ aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onChange={() => { - setSection("cashin") + setSection("cashin"); }} /> <span class="flex flex-1"> @@ -277,7 +319,6 @@ function useComponentState({ </span> </label> </div> - </div> <form @@ -288,8 +329,9 @@ function useComponentState({ e.preventDefault(); }} > - {section == "cashin" && - <ConversionForm id="cashin" + {section == "cashin" && ( + <ConversionForm + id="cashin" inputCurrency={info.fiat_currency} outputCurrency={info.regional_currency} fee={form?.conv?.cashin_fee} @@ -297,682 +339,830 @@ function useComponentState({ ratio={form?.conv?.cashin_ratio} rounding={form?.conv?.cashin_rounding_mode} tiny={form?.conv?.cashin_tiny_amount} - />} - - {section == "cashout" && <Fragment> - <ConversionForm id="cashout" - inputCurrency={info.regional_currency} - outputCurrency={info.fiat_currency} - fee={form?.conv?.cashout_fee} - minimum={form?.conv?.cashout_min_amount} - ratio={form?.conv?.cashout_ratio} - rounding={form?.conv?.cashout_rounding_mode} - tiny={form?.conv?.cashout_tiny_amount} /> - </Fragment>} - - {section == "detail" && <Fragment> - <div class="px-6 pt-6"> - <div class="justify-between items-center flex "> - <dt class="text-sm text-gray-600"> - <i18n.Translate>Cashin ratio</i18n.Translate> - </dt> - <dd class="text-sm text-gray-900"> - {info.conversion_rate.cashin_ratio} - </dd> - </div> - </div> + )} + + {section == "cashout" && ( + <Fragment> + <ConversionForm + id="cashout" + inputCurrency={info.regional_currency} + outputCurrency={info.fiat_currency} + fee={form?.conv?.cashout_fee} + minimum={form?.conv?.cashout_min_amount} + ratio={form?.conv?.cashout_ratio} + rounding={form?.conv?.cashout_rounding_mode} + tiny={form?.conv?.cashout_tiny_amount} + /> + </Fragment> + )} - <div class="px-6 pt-6"> - <div class="justify-between items-center flex "> - <dt class="text-sm text-gray-600"> - <i18n.Translate>Cashout ratio</i18n.Translate> - </dt> - <dd class="text-sm text-gray-900"> - {info.conversion_rate.cashout_ratio} - </dd> + {section == "detail" && ( + <Fragment> + <div class="px-6 pt-6"> + <div class="justify-between items-center flex "> + <dt class="text-sm text-gray-600"> + <i18n.Translate>Cashin ratio</i18n.Translate> + </dt> + <dd class="text-sm text-gray-900"> + {info.conversion_rate.cashin_ratio} + </dd> + </div> </div> - </div> - {both_low || both_high ? <div class="p-4"> - <Attention title={i18n.str`Bad ratios`} type="warning"> - <i18n.Translate> - One of the ratios should be higher or equal than 1 an the other should be lower or equal than 1. - </i18n.Translate> - </Attention> - </div> : undefined} - - <div class="px-6 pt-6"> - <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - <div class="sm:col-span-5"> - <label - for="amount" - class="block text-sm font-medium leading-6 text-gray-900" - >{i18n.str`Initial amount`}</label> - <InputAmount - name="amount" - left - currency={info.fiat_currency} - value={form.amount?.value ?? ""} - onChange={form.amount?.onUpdate} - /> - <ShowInputErrorLabel - message={form.amount?.error} - isDirty={form.amount?.value !== undefined} - /> - <p class="mt-2 text-sm text-gray-500"> - <i18n.Translate>Use it to test how the conversion will affect the amount.</i18n.Translate> - </p> + <div class="px-6 pt-6"> + <div class="justify-between items-center flex "> + <dt class="text-sm text-gray-600"> + <i18n.Translate>Cashout ratio</i18n.Translate> + </dt> + <dd class="text-sm text-gray-900"> + {info.conversion_rate.cashout_ratio} + </dd> </div> </div> - </div> - {!cashoutCalc || !cashinCalc ? undefined : ( + {both_low || both_high ? ( + <div class="p-4"> + <Attention title={i18n.str`Bad ratios`} type="warning"> + <i18n.Translate> + One of the ratios should be higher or equal than 1 an + the other should be lower or equal than 1. + </i18n.Translate> + </Attention> + </div> + ) : undefined} + <div class="px-6 pt-6"> - <div class="sm:col-span-5"> - <dl class="mt-4 space-y-4"> - <div class="justify-between items-center flex "> - <dt class="text-sm text-gray-600"> - <i18n.Translate>Sending to this bank</i18n.Translate> - </dt> - <dd class="text-sm text-gray-900"> - <RenderAmount - value={cashinCalc.debit} - negative - withColor - spec={info.regional_currency_specification} - /> - </dd> - </div> + <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> + <div class="sm:col-span-5"> + <label + for="amount" + class="block text-sm font-medium leading-6 text-gray-900" + >{i18n.str`Initial amount`}</label> + <InputAmount + name="amount" + left + currency={info.fiat_currency} + value={form.amount?.value ?? ""} + onChange={form.amount?.onUpdate} + /> + <ShowInputErrorLabel + message={form.amount?.error} + isDirty={form.amount?.value !== undefined} + /> + <p class="mt-2 text-sm text-gray-500"> + <i18n.Translate> + Use it to test how the conversion will affect the + amount. + </i18n.Translate> + </p> + </div> + </div> + </div> - {Amounts.isZero(cashinCalc.beforeFee) ? undefined : ( - <div class="flex items-center justify-between afu "> - <dt class="flex items-center text-sm text-gray-600"> - <span> - <i18n.Translate>Converted</i18n.Translate> - </span> + {!cashoutCalc || !cashinCalc ? undefined : ( + <div class="px-6 pt-6"> + <div class="sm:col-span-5"> + <dl class="mt-4 space-y-4"> + <div class="justify-between items-center flex "> + <dt class="text-sm text-gray-600"> + <i18n.Translate> + Sending to this bank + </i18n.Translate> </dt> <dd class="text-sm text-gray-900"> <RenderAmount - value={cashinCalc.beforeFee} - spec={info.fiat_currency_specification} + value={cashinCalc.debit} + negative + withColor + spec={info.regional_currency_specification} /> </dd> </div> - )} - <div class="flex justify-between items-center border-t-2 afu pt-4"> - <dt class="text-lg text-gray-900 font-medium"> - <i18n.Translate>Cashin after fee</i18n.Translate> - </dt> - <dd class="text-lg text-gray-900 font-medium"> - <RenderAmount - value={cashinCalc.credit} - withColor - spec={info.fiat_currency_specification} - /> - </dd> - </div> - </dl> - </div> - - <div class="sm:col-span-5"> - <dl class="mt-4 space-y-4"> - <div class="justify-between items-center flex "> - <dt class="text-sm text-gray-600"> - <i18n.Translate>Sending from this bank</i18n.Translate> - </dt> - <dd class="text-sm text-gray-900"> - <RenderAmount - value={cashoutCalc.debit} - negative - withColor - spec={info.fiat_currency_specification} - /> - </dd> - </div> - {Amounts.isZero(cashoutCalc.beforeFee) ? undefined : ( - <div class="flex items-center justify-between afu"> - <dt class="flex items-center text-sm text-gray-600"> - <span> - <i18n.Translate>Converted</i18n.Translate> - </span> + {Amounts.isZero(cashinCalc.beforeFee) ? undefined : ( + <div class="flex items-center justify-between afu "> + <dt class="flex items-center text-sm text-gray-600"> + <span> + <i18n.Translate>Converted</i18n.Translate> + </span> + </dt> + <dd class="text-sm text-gray-900"> + <RenderAmount + value={cashinCalc.beforeFee} + spec={info.fiat_currency_specification} + /> + </dd> + </div> + )} + <div class="flex justify-between items-center border-t-2 afu pt-4"> + <dt class="text-lg text-gray-900 font-medium"> + <i18n.Translate>Cashin after fee</i18n.Translate> + </dt> + <dd class="text-lg text-gray-900 font-medium"> + <RenderAmount + value={cashinCalc.credit} + withColor + spec={info.fiat_currency_specification} + /> + </dd> + </div> + </dl> + </div> + + <div class="sm:col-span-5"> + <dl class="mt-4 space-y-4"> + <div class="justify-between items-center flex "> + <dt class="text-sm text-gray-600"> + <i18n.Translate> + Sending from this bank + </i18n.Translate> </dt> <dd class="text-sm text-gray-900"> <RenderAmount - value={cashoutCalc.beforeFee} + value={cashoutCalc.debit} + negative + withColor + spec={info.fiat_currency_specification} + /> + </dd> + </div> + + {Amounts.isZero(cashoutCalc.beforeFee) ? undefined : ( + <div class="flex items-center justify-between afu"> + <dt class="flex items-center text-sm text-gray-600"> + <span> + <i18n.Translate>Converted</i18n.Translate> + </span> + </dt> + <dd class="text-sm text-gray-900"> + <RenderAmount + value={cashoutCalc.beforeFee} + spec={info.regional_currency_specification} + /> + </dd> + </div> + )} + <div class="flex justify-between items-center border-t-2 afu pt-4"> + <dt class="text-lg text-gray-900 font-medium"> + <i18n.Translate>Cashout after fee</i18n.Translate> + </dt> + <dd class="text-lg text-gray-900 font-medium"> + <RenderAmount + value={cashoutCalc.credit} + withColor spec={info.regional_currency_specification} /> </dd> </div> - )} - <div class="flex justify-between items-center border-t-2 afu pt-4"> - <dt class="text-lg text-gray-900 font-medium"> - <i18n.Translate>Cashout after fee</i18n.Translate> - </dt> - <dd class="text-lg text-gray-900 font-medium"> - <RenderAmount - value={cashoutCalc.credit} - withColor - spec={info.regional_currency_specification} - /> - </dd> + </dl> + </div> + + {cashoutCalc && + status.status === "ok" && + Amounts.cmp(status.result.amount, cashoutCalc.credit) < + 0 ? ( + <div class="p-4"> + <Attention + title={i18n.str`Bad configuration`} + type="warning" + > + <i18n.Translate> + This configuration allows users to cash out more of + what has been cashed in. + </i18n.Translate> + </Attention> </div> - </dl> + ) : undefined} </div> - - {cashoutCalc && status.status === "ok" && Amounts.cmp(status.result.amount, cashoutCalc.credit) < 0 ? <div class="p-4"> - <Attention title={i18n.str`Bad configuration`} type="warning"> - <i18n.Translate> - This configuration allows users to cash out more of what has been cashed in. - </i18n.Translate> - </Attention> - </div> : undefined} - </div> - )} - </Fragment>} - + )} + </Fragment> + )} <div class="flex items-center justify-between mt-4 gap-x-6 border-t border-gray-900/10 px-4 py-4"> - <a name="cancel" + <a + name="cancel" href={routeCancel.url({})} class="text-sm font-semibold leading-6 text-gray-900" > <i18n.Translate>Cancel</i18n.Translate> </a> - {section == "cashin" || section == "cashout" ? <Fragment> - <button - type="submit" - name="update conversion" - class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" - onClick={async () => { - doUpdate() - }} - > - <i18n.Translate>Update</i18n.Translate> - </button> - </Fragment> : <div />} + {section == "cashin" || section == "cashout" ? ( + <Fragment> + <button + type="submit" + name="update conversion" + class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + onClick={async () => { + doUpdate(); + }} + > + <i18n.Translate>Update</i18n.Translate> + </button> + </Fragment> + ) : ( + <div /> + )} </div> - - </form> </div> </div> ); - - } + }; } export const ConversionConfig = utils.recursive(useComponentState); /** - * - * @param i18n - * @param regional - * @param fiat + * + * @param i18n + * @param regional + * @param fiat * @returns form validator */ -function createFormValidator(i18n: InternationalizationAPI, regional: string, fiat: string) { +function createFormValidator( + i18n: InternationalizationAPI, + regional: string, + fiat: string, +) { return function check(state: FormValues<FormType>): FormStatus<FormType> { + const cashin_min_amount = Amounts.parse( + `${fiat}:${state.conv.cashin_min_amount}`, + ); + const cashin_tiny_amount = Amounts.parse( + `${regional}:${state.conv.cashin_tiny_amount}`, + ); + const cashin_fee = Amounts.parse(`${regional}:${state.conv.cashin_fee}`); - const cashin_min_amount = Amounts.parse(`${fiat}:${state.conv.cashin_min_amount}`) - const cashin_tiny_amount = Amounts.parse(`${regional}:${state.conv.cashin_tiny_amount}`) - const cashin_fee = Amounts.parse(`${regional}:${state.conv.cashin_fee}`) - - const cashout_min_amount = Amounts.parse(`${regional}:${state.conv.cashout_min_amount}`) - const cashout_tiny_amount = Amounts.parse(`${fiat}:${state.conv.cashout_tiny_amount}`) - const cashout_fee = Amounts.parse(`${fiat}:${state.conv.cashout_fee}`) + const cashout_min_amount = Amounts.parse( + `${regional}:${state.conv.cashout_min_amount}`, + ); + const cashout_tiny_amount = Amounts.parse( + `${fiat}:${state.conv.cashout_tiny_amount}`, + ); + const cashout_fee = Amounts.parse(`${fiat}:${state.conv.cashout_fee}`); - const am = Amounts.parse(`${fiat}:${state.amount}`) + const am = Amounts.parse(`${fiat}:${state.amount}`); - const cashin_ratio = Number.parseFloat(state.conv.cashin_ratio ?? "") - const cashout_ratio = Number.parseFloat(state.conv.cashout_ratio ?? "") + const cashin_ratio = Number.parseFloat(state.conv.cashin_ratio ?? ""); + const cashout_ratio = Number.parseFloat(state.conv.cashout_ratio ?? ""); const errors = undefinedIfEmpty<FormErrors<FormType>>({ conv: undefinedIfEmpty<FormErrors<FormType["conv"]>>({ - cashin_min_amount: !state.conv.cashin_min_amount ? i18n.str`required` : - !cashin_min_amount ? i18n.str`invalid` : - undefined, - cashin_tiny_amount: !state.conv.cashin_tiny_amount ? i18n.str`required` : - !cashin_tiny_amount ? i18n.str`invalid` : - undefined, - cashin_fee: !state.conv.cashin_fee ? i18n.str`required` : - !cashin_fee ? i18n.str`invalid` : - undefined, - - cashout_min_amount: !state.conv.cashout_min_amount ? i18n.str`required` : - !cashout_min_amount ? i18n.str`invalid` : - undefined, - cashout_tiny_amount: !state.conv.cashin_tiny_amount ? i18n.str`required` : - !cashout_tiny_amount ? i18n.str`invalid` : - undefined, - cashout_fee: !state.conv.cashin_fee ? i18n.str`required` : - !cashout_fee ? i18n.str`invalid` : - undefined, - - cashin_rounding_mode: !state.conv.cashin_rounding_mode ? i18n.str`required` : undefined, - cashout_rounding_mode: !state.conv.cashout_rounding_mode ? i18n.str`required` : undefined, - - cashin_ratio: !state.conv.cashin_ratio ? i18n.str`required` : Number.isNaN(cashin_ratio) ? i18n.str`invalid` : undefined, - cashout_ratio: !state.conv.cashout_ratio ? i18n.str`required` : Number.isNaN(cashout_ratio) ? i18n.str`invalid` : undefined, + cashin_min_amount: !state.conv.cashin_min_amount + ? i18n.str`required` + : !cashin_min_amount + ? i18n.str`invalid` + : undefined, + cashin_tiny_amount: !state.conv.cashin_tiny_amount + ? i18n.str`required` + : !cashin_tiny_amount + ? i18n.str`invalid` + : undefined, + cashin_fee: !state.conv.cashin_fee + ? i18n.str`required` + : !cashin_fee + ? i18n.str`invalid` + : undefined, + + cashout_min_amount: !state.conv.cashout_min_amount + ? i18n.str`required` + : !cashout_min_amount + ? i18n.str`invalid` + : undefined, + cashout_tiny_amount: !state.conv.cashin_tiny_amount + ? i18n.str`required` + : !cashout_tiny_amount + ? i18n.str`invalid` + : undefined, + cashout_fee: !state.conv.cashin_fee + ? i18n.str`required` + : !cashout_fee + ? i18n.str`invalid` + : undefined, + + cashin_rounding_mode: !state.conv.cashin_rounding_mode + ? i18n.str`required` + : undefined, + cashout_rounding_mode: !state.conv.cashout_rounding_mode + ? i18n.str`required` + : undefined, + + cashin_ratio: !state.conv.cashin_ratio + ? i18n.str`required` + : Number.isNaN(cashin_ratio) + ? i18n.str`invalid` + : undefined, + cashout_ratio: !state.conv.cashout_ratio + ? i18n.str`required` + : Number.isNaN(cashout_ratio) + ? i18n.str`invalid` + : undefined, }), - amount: !state.amount ? i18n.str`required` : - !am ? i18n.str`invalid` : - undefined, - }) + amount: !state.amount + ? i18n.str`required` + : !am + ? i18n.str`invalid` + : undefined, + }); const result: RecursivePartial<FormType> = { amount: am, conv: { - cashin_fee: !errors?.conv?.cashin_fee ? Amounts.stringify(cashin_fee!) : undefined, - cashin_min_amount: !errors?.conv?.cashin_min_amount ? Amounts.stringify(cashin_min_amount!) : undefined, - cashin_ratio: !errors?.conv?.cashin_ratio ? String(cashin_ratio!) : undefined, - cashin_rounding_mode: !errors?.conv?.cashin_rounding_mode ? (state.conv.cashin_rounding_mode!) : undefined, - cashin_tiny_amount: !errors?.conv?.cashin_tiny_amount ? Amounts.stringify(cashin_tiny_amount!) : undefined, - cashout_fee: !errors?.conv?.cashout_fee ? Amounts.stringify(cashout_fee!) : undefined, - cashout_min_amount: !errors?.conv?.cashout_min_amount ? Amounts.stringify(cashout_min_amount!) : undefined, - cashout_ratio: !errors?.conv?.cashout_ratio ? String(cashout_ratio!) : undefined, - cashout_rounding_mode: !errors?.conv?.cashout_rounding_mode ? (state.conv.cashout_rounding_mode!) : undefined, - cashout_tiny_amount: !errors?.conv?.cashout_tiny_amount ? Amounts.stringify(cashout_tiny_amount!) : undefined, - } - - } - return errors === undefined ? - { status: "ok", result: result as FormType, errors } : - { status: "fail", result, errors } - } + cashin_fee: !errors?.conv?.cashin_fee + ? Amounts.stringify(cashin_fee!) + : undefined, + cashin_min_amount: !errors?.conv?.cashin_min_amount + ? Amounts.stringify(cashin_min_amount!) + : undefined, + cashin_ratio: !errors?.conv?.cashin_ratio + ? String(cashin_ratio!) + : undefined, + cashin_rounding_mode: !errors?.conv?.cashin_rounding_mode + ? state.conv.cashin_rounding_mode! + : undefined, + cashin_tiny_amount: !errors?.conv?.cashin_tiny_amount + ? Amounts.stringify(cashin_tiny_amount!) + : undefined, + cashout_fee: !errors?.conv?.cashout_fee + ? Amounts.stringify(cashout_fee!) + : undefined, + cashout_min_amount: !errors?.conv?.cashout_min_amount + ? Amounts.stringify(cashout_min_amount!) + : undefined, + cashout_ratio: !errors?.conv?.cashout_ratio + ? String(cashout_ratio!) + : undefined, + cashout_rounding_mode: !errors?.conv?.cashout_rounding_mode + ? state.conv.cashout_rounding_mode! + : undefined, + cashout_tiny_amount: !errors?.conv?.cashout_tiny_amount + ? Amounts.stringify(cashout_tiny_amount!) + : undefined, + }, + }; + return errors === undefined + ? { status: "ok", result: result as FormType, errors } + : { status: "fail", result, errors }; + }; } - -function ConversionForm({ id, inputCurrency, outputCurrency, fee, minimum, ratio, rounding, tiny }: { - inputCurrency: string, - outputCurrency: string, - minimum: UIField | undefined, - tiny: UIField | undefined, - fee: UIField | undefined, - rounding: UIField | undefined, - ratio: UIField | undefined, - id: string, +function ConversionForm({ + id, + inputCurrency, + outputCurrency, + fee, + minimum, + ratio, + rounding, + tiny, +}: { + inputCurrency: string; + outputCurrency: string; + minimum: UIField | undefined; + tiny: UIField | undefined; + fee: UIField | undefined; + rounding: UIField | undefined; + ratio: UIField | undefined; + id: string; }): VNode { const { i18n } = useTranslationContext(); - return <Fragment> - <div class="px-6 pt-6"> - <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - <div class="sm:col-span-5"> - <label - for="cashin_min_amount" - class="block text-sm font-medium leading-6 text-gray-900" - >{i18n.str`Minimum amount`}</label> - <InputAmount - name="cashin_min_amount" - left - currency={inputCurrency} - value={minimum?.value ?? ""} - onChange={minimum?.onUpdate} + return ( + <Fragment> + <div class="px-6 pt-6"> + <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> + <div class="sm:col-span-5"> + <label + for={`${id}_min_amount`} + class="block text-sm font-medium leading-6 text-gray-900" + >{i18n.str`Minimum amount`}</label> + <InputAmount + name={`${id}_min_amount`} + left + currency={inputCurrency} + value={minimum?.value ?? ""} + onChange={minimum?.onUpdate} + /> + <ShowInputErrorLabel + message={minimum?.error} + isDirty={minimum?.value !== undefined} + /> + <p class="mt-2 text-sm text-gray-500"> + <i18n.Translate> + Only cashout operation above this threshold will be allowed + </i18n.Translate> + </p> + </div> + </div> + </div> + + <div class="px-6 pt-6"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for={`${id}_ratio`} + > + {i18n.str`Ratio`} + </label> + <div class="mt-2"> + <input + type="number" + class="block rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="current" + id={`${id}_ratio`} + data-error={!!ratio?.error && ratio?.value !== undefined} + value={ratio?.value ?? ""} + onChange={(e) => { + ratio?.onUpdate(e.currentTarget.value); + }} + autocomplete="off" /> <ShowInputErrorLabel - message={minimum?.error} - isDirty={minimum?.value !== undefined} + message={ratio?.error} + isDirty={ratio?.value !== undefined} /> - <p class="mt-2 text-sm text-gray-500"> - <i18n.Translate>Only cashout operation above this threshold will be allowed</i18n.Translate> - </p> </div> + <p class="mt-2 text-sm text-gray-500"> + <i18n.Translate>Conversion ratio between currencies</i18n.Translate> + </p> </div> - </div> - - <div class="px-6 pt-6"> - <label - class="block text-sm font-medium leading-6 text-gray-900" - for="password" - > - {i18n.str`Ratio`} - </label> - <div class="mt-2"> - <input - type="number" - class="block rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - name="current" - id="cashin_ratio" - data-error={!!ratio?.error && ratio?.value !== undefined} - value={ratio?.value ?? ""} - onChange={(e) => { - ratio?.onUpdate(e.currentTarget.value); - }} - autocomplete="off" - /> - <ShowInputErrorLabel - message={ratio?.error} - isDirty={ratio?.value !== undefined} - /> + + <div class="px-6 pt-4"> + <Attention title={i18n.str`Example conversion`}> + <i18n.Translate> + 1 {inputCurrency} will be converted into {ratio?.value}{" "} + {outputCurrency} + </i18n.Translate> + </Attention> </div> - <p class="mt-2 text-sm text-gray-500"> - <i18n.Translate> - Conversion ratio between currencies - </i18n.Translate> - </p> - </div> - - <div class="px-6 pt-4"> - <Attention title={i18n.str`Example conversion`}> - <i18n.Translate>1 {inputCurrency} will be converted into {ratio?.value} {outputCurrency}</i18n.Translate> - </Attention> - </div> - - <div class="px-6 pt-6"> - <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - <div class="sm:col-span-5"> - <label - for="cashin_tiny_amount" - class="block text-sm font-medium leading-6 text-gray-900" - >{i18n.str`Rounding value`}</label> - <InputAmount - name="cashin_tiny_amount" - left - currency={outputCurrency} - value={tiny?.value ?? ""} - onChange={tiny?.onUpdate} - /> - <ShowInputErrorLabel - message={tiny?.error} - isDirty={tiny?.value !== undefined} - /> - <p class="mt-2 text-sm text-gray-500"> - <i18n.Translate>Smallest difference between two amounts after the ratio is applied.</i18n.Translate> - </p> + + <div class="px-6 pt-6"> + <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> + <div class="sm:col-span-5"> + <label + for={`${id}_tiny_amount`} + class="block text-sm font-medium leading-6 text-gray-900" + >{i18n.str`Rounding value`}</label> + <InputAmount + name={`${id}_tiny_amount`} + left + currency={outputCurrency} + value={tiny?.value ?? ""} + onChange={tiny?.onUpdate} + /> + <ShowInputErrorLabel + message={tiny?.error} + isDirty={tiny?.value !== undefined} + /> + <p class="mt-2 text-sm text-gray-500"> + <i18n.Translate> + Smallest difference between two amounts after the ratio is + applied. + </i18n.Translate> + </p> + </div> </div> </div> - </div> - - <div class="px-6 pt-6"> - <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - <div class="sm:col-span-5"> - <label - class="block text-sm font-medium leading-6 text-gray-900" - for="channel" - > - {i18n.str`Rounding mode`} - </label> - <div class="mt-2 max-w-xl text-sm text-gray-500"> - <div class="px-4 mt-4 grid grid-cols-1 gap-y-6"> - <label - onClick={(e) => { - e.preventDefault(); - rounding?.onUpdate("zero") - }} - data-selected={rounding?.value === "zero"} - class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border bg-white data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600" - > - <input - type="radio" - name="channel" - value="Newsletter" - class="sr-only" - /> - <span class="flex flex-1"> - <span class="flex flex-col"> - <span - id="project-type-0-label" - class="block text-sm font-medium text-gray-900 " - > - <i18n.Translate>Zero</i18n.Translate> + + <div class="px-6 pt-6"> + <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> + <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for={`${id}_channel`} + > + {i18n.str`Rounding mode`} + </label> + <div class="mt-2 max-w-xl text-sm text-gray-500"> + <div class="px-4 mt-4 grid grid-cols-1 gap-y-6"> + <label + onClick={(e) => { + e.preventDefault(); + rounding?.onUpdate("zero"); + }} + data-selected={rounding?.value === "zero"} + class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border bg-white data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600" + > + <input + type="radio" + name="channel" + value="Newsletter" + class="sr-only" + /> + <span class="flex flex-1"> + <span class="flex flex-col"> + <span class="block text-sm font-medium text-gray-900 "> + <i18n.Translate>Zero</i18n.Translate> + </span> + <i18n.Translate> + Amount will be round below to the largest possible value + smaller than the input. + </i18n.Translate> </span> - <i18n.Translate>Amount will be round below to the largest possible value smaller than the input.</i18n.Translate> </span> - </span> + <svg + data-selected={rounding?.value === "zero"} + class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" + viewBox="0 0 20 20" + fill="currentColor" + aria-hidden="true" + > + <path + fill-rule="evenodd" + d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" + clip-rule="evenodd" + /> + </svg> + </label> + + <label + onClick={(e) => { + e.preventDefault(); + rounding?.onUpdate("up"); + }} + data-selected={rounding?.value === "up"} + class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600" + > + <input + type="radio" + name="channel" + value="Existing Customers" + class="sr-only" + /> + <span class="flex flex-1"> + <span class="flex flex-col"> + <span class="block text-sm font-medium text-gray-900 "> + <i18n.Translate>Up</i18n.Translate> + </span> + <i18n.Translate> + Amount will be round up to the smallest possible value + larger than the input. + </i18n.Translate> + </span> + </span> + <svg + data-selected={rounding?.value === "up"} + class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" + viewBox="0 0 20 20" + fill="currentColor" + aria-hidden="true" + > + <path + fill-rule="evenodd" + d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" + clip-rule="evenodd" + /> + </svg> + </label> + <label + onClick={(e) => { + e.preventDefault(); + rounding?.onUpdate("nearest"); + }} + data-selected={rounding?.value === "nearest"} + class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600" + > + <input + type="radio" + name="channel" + value="Existing Customers" + class="sr-only" + /> + <span class="flex flex-1"> + <span class="flex flex-col"> + <span class="block text-sm font-medium text-gray-900 "> + <i18n.Translate>Nearest</i18n.Translate> + </span> + <i18n.Translate> + Amount will be round to the closest possible value. + </i18n.Translate> + </span> + </span> + <svg + data-selected={rounding?.value === "nearest"} + class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" + viewBox="0 0 20 20" + fill="currentColor" + aria-hidden="true" + > + <path + fill-rule="evenodd" + d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" + clip-rule="evenodd" + /> + </svg> + </label> + </div> + </div> + </div> + </div> + </div> + + <div class="px-6 pt-4"> + <Attention title={i18n.str`Examples`}> + <section class="grid grid-cols-1 gap-y-3 text-gray-600"> + <details class="group text-sm"> + <summary class="flex cursor-pointer flex-row items-center justify-between "> + <i18n.Translate> + Rounding an amount of 1.24 with rounding value 0.1 + </i18n.Translate> <svg - data-selected={rounding?.value === "zero"} - class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" - viewBox="0 0 20 20" - fill="currentColor" + class="h-6 w-6 rotate-0 transform group-open:rotate-180" + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="2" + stroke="currentColor" aria-hidden="true" > <path - fill-rule="evenodd" - d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" - clip-rule="evenodd" - /> + stroke-linecap="round" + stroke-linejoin="round" + d="M19 9l-7 7-7-7" + ></path> </svg> - </label> - - <label - onClick={(e) => { - e.preventDefault(); - rounding?.onUpdate("up") - }} - data-selected={rounding?.value === "up"} - class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600" - > - <input - type="radio" - name="channel" - value="Existing Customers" - class="sr-only" - /> - <span class="flex flex-1"> - <span class="flex flex-col"> - <span - id="project-type-0-label" - class="block text-sm font-medium text-gray-900 " - > - <i18n.Translate>Up</i18n.Translate> - </span> - <i18n.Translate>Amount will be round up to the smallest possible value larger than the input.</i18n.Translate> - </span> - </span> + </summary> + <p class="text-gray-900 my-4"> + <i18n.Translate> + Given the rounding value of 0.1 the possible values closest to + 1.24 are: 1.1, 1.2, 1.3, 1.4. + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "zero" mode the value will be rounded to 1.2 + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "nearest" mode the value will be rounded to 1.2 + </i18n.Translate> + </p> + <p class="text-gray-900 mt-4"> + <i18n.Translate> + With the "up" mode the value will be rounded to 1.3 + </i18n.Translate> + </p> + </details> + <details class="group "> + <summary class="flex cursor-pointer flex-row items-center justify-between "> + <i18n.Translate> + Rounding an amount of 1.26 with rounding value 0.1 + </i18n.Translate> <svg - data-selected={rounding?.value === "up"} - class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" - viewBox="0 0 20 20" - fill="currentColor" + class="h-6 w-6 rotate-0 transform group-open:rotate-180" + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="2" + stroke="currentColor" aria-hidden="true" > <path - fill-rule="evenodd" - d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" - clip-rule="evenodd" - /> + stroke-linecap="round" + stroke-linejoin="round" + d="M19 9l-7 7-7-7" + ></path> </svg> - </label> - <label - onClick={(e) => { - e.preventDefault(); - rounding?.onUpdate("nearest") - }} - data-selected={rounding?.value === "nearest"} - class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600" - > - <input - type="radio" - name="channel" - value="Existing Customers" - class="sr-only" - /> - <span class="flex flex-1"> - <span class="flex flex-col"> - <span - id="project-type-0-label" - class="block text-sm font-medium text-gray-900 " - > - <i18n.Translate>Nearest</i18n.Translate> - </span> - <i18n.Translate>Amount will be round to the closest possible value.</i18n.Translate> - </span> - </span> + </summary> + <p class="text-gray-900 my-4"> + <i18n.Translate> + Given the rounding value of 0.1 the possible values closest to + 1.24 are: 1.1, 1.2, 1.3, 1.4. + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "zero" mode the value will be rounded to 1.2 + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "nearest" mode the value will be rounded to 1.3 + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "up" mode the value will be rounded to 1.3 + </i18n.Translate> + </p> + </details> + <details class="group "> + <summary class="flex cursor-pointer flex-row items-center justify-between "> + <i18n.Translate> + Rounding an amount of 1.24 with rounding value 0.3 + </i18n.Translate> <svg - data-selected={rounding?.value === "nearest"} - class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" - viewBox="0 0 20 20" - fill="currentColor" + class="h-6 w-6 rotate-0 transform group-open:rotate-180" + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="2" + stroke="currentColor" aria-hidden="true" > <path - fill-rule="evenodd" - d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" - clip-rule="evenodd" - /> + stroke-linecap="round" + stroke-linejoin="round" + d="M19 9l-7 7-7-7" + ></path> </svg> - </label> - </div> - </div> - </div> + </summary> + <p class="text-gray-900 my-4"> + <i18n.Translate> + Given the rounding value of 0.3 the possible values closest to + 1.24 are: 0.9, 1.2, 1.5, 1.8. + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "zero" mode the value will be rounded to 1.2 + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "nearest" mode the value will be rounded to 1.2 + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "up" mode the value will be rounded to 1.5 + </i18n.Translate> + </p> + </details> + <details class="group "> + <summary class="flex cursor-pointer flex-row items-center justify-between "> + <i18n.Translate> + Rounding an amount of 1.26 with rounding value 0.3 + </i18n.Translate> + <svg + class="h-6 w-6 rotate-0 transform group-open:rotate-180" + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="2" + stroke="currentColor" + aria-hidden="true" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + d="M19 9l-7 7-7-7" + ></path> + </svg> + </summary> + <p class="text-gray-900 my-4"> + <i18n.Translate> + Given the rounding value of 0.3 the possible values closest to + 1.24 are: 0.9, 1.2, 1.5, 1.8. + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "zero" mode the value will be rounded to 1.2 + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "nearest" mode the value will be rounded to 1.3 + </i18n.Translate> + </p> + <p class="text-gray-900 my-4"> + <i18n.Translate> + With the "up" mode the value will be rounded to 1.3 + </i18n.Translate> + </p> + </details> + </section> + </Attention> </div> - </div> - <div class="px-6 pt-4"> - <Attention title={i18n.str`Examples`}> - <section class="grid grid-cols-1 gap-y-3 text-gray-600"> - <details class="group text-sm"> - <summary class="flex cursor-pointer flex-row items-center justify-between "> + <div class="px-6 pt-6"> + <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> + <div class="sm:col-span-5"> + <label + for={`${id}_fee`} + class="block text-sm font-medium leading-6 text-gray-900" + >{i18n.str`Fee`}</label> + <InputAmount + name={`${id}_fee`} + left + currency={outputCurrency} + value={fee?.value ?? ""} + onChange={fee?.onUpdate} + /> + <ShowInputErrorLabel + message={fee?.error} + isDirty={fee?.value !== undefined} + /> + <p class="mt-2 text-sm text-gray-500"> <i18n.Translate> - Rounding an amount of 1.24 with rounding value 0.1 - </i18n.Translate> - <svg class="h-6 w-6 rotate-0 transform group-open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"> - <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path> - </svg> - </summary> - <p class="text-gray-900 my-4"> - <i18n.Translate> - Given the rounding value of 0.1 the possible values closest to 1.24 are: 1.1, 1.2, 1.3, 1.4. - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "zero" mode the value will be rounded to 1.2 - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "nearest" mode the value will be rounded to 1.2 - </i18n.Translate> - </p> - <p class="text-gray-900 mt-4"> - <i18n.Translate> - With the "up" mode the value will be rounded to 1.3 - </i18n.Translate> - </p> - </details> - <details class="group "> - <summary class="flex cursor-pointer flex-row items-center justify-between "> - <i18n.Translate> - Rounding an amount of 1.26 with rounding value 0.1 - </i18n.Translate> - <svg class="h-6 w-6 rotate-0 transform group-open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"> - <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path> - </svg> - </summary> - <p class="text-gray-900 my-4"> - <i18n.Translate> - Given the rounding value of 0.1 the possible values closest to 1.24 are: 1.1, 1.2, 1.3, 1.4. - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "zero" mode the value will be rounded to 1.2 - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "nearest" mode the value will be rounded to 1.3 - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "up" mode the value will be rounded to 1.3 - </i18n.Translate> - </p> - </details> - <details class="group "> - <summary class="flex cursor-pointer flex-row items-center justify-between "> - <i18n.Translate> - Rounding an amount of 1.24 with rounding value 0.3 - </i18n.Translate> - <svg class="h-6 w-6 rotate-0 transform group-open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"> - <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path> - </svg> - </summary> - <p class="text-gray-900 my-4"> - <i18n.Translate> - Given the rounding value of 0.3 the possible values closest to 1.24 are: 0.9, 1.2, 1.5, 1.8. - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "zero" mode the value will be rounded to 1.2 - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "nearest" mode the value will be rounded to 1.2 - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "up" mode the value will be rounded to 1.5 - </i18n.Translate> - </p> - </details> - <details class="group "> - <summary class="flex cursor-pointer flex-row items-center justify-between "> - <i18n.Translate> - Rounding an amount of 1.26 with rounding value 0.3 - </i18n.Translate> - <svg class="h-6 w-6 rotate-0 transform group-open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"> - <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path> - </svg> - </summary> - <p class="text-gray-900 my-4"> - <i18n.Translate> - Given the rounding value of 0.3 the possible values closest to 1.24 are: 0.9, 1.2, 1.5, 1.8. + Amount to be deducted before amount is credited. </i18n.Translate> </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "zero" mode the value will be rounded to 1.2 - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "nearest" mode the value will be rounded to 1.3 - </i18n.Translate> - </p> - <p class="text-gray-900 my-4"> - <i18n.Translate> - With the "up" mode the value will be rounded to 1.3 - </i18n.Translate> - </p> - </details> - </section> - </Attention> - </div> - - - - <div class="px-6 pt-6"> - <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - <div class="sm:col-span-5"> - <label - for="cashin_fee" - class="block text-sm font-medium leading-6 text-gray-900" - >{i18n.str`Fee`}</label> - <InputAmount - name="cashin_fee" - left - currency={outputCurrency} - value={fee?.value ?? ""} - onChange={fee?.onUpdate} - /> - <ShowInputErrorLabel - message={fee?.error} - isDirty={fee?.value !== undefined} - /> - <p class="mt-2 text-sm text-gray-500"> - <i18n.Translate>Amount to be deducted before amount is credited.</i18n.Translate> - </p> + </div> </div> </div> - </div> - - </Fragment> + </Fragment> + ); } |