diff --git a/src/components/Earn.tsx b/src/components/Earn.tsx
index f87f4eea..857980a9 100644
--- a/src/components/Earn.tsx
+++ b/src/components/Earn.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import { Formik, FormikErrors } from 'formik'
import * as rb from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
@@ -132,7 +132,7 @@ function CurrentOffer({ offer, nickname }: CurrentOfferProps) {
{t('earn.current.text_cjfee')}
{isRelativeOffer(offer.ordertype) ? (
- <>{factorToPercentage(parseInt(offer.cjfee, 10) || 0)}%>
+ <>{factorToPercentage(parseFloat(offer.cjfee) || 0)}%>
) : (
<>
React.ReactNode | string
+ onSubmit: (values: EarnFormValues) => Promise
+ isLoading: boolean
+ disabled?: boolean
+}
+
+const EarnForm = ({
+ initialValues = FORM_INPUT_DEFAULT_VALUES,
+ submitButtonText,
+ onSubmit,
+ isLoading,
+ disabled = false,
+}: EarnFormProps) => {
+ const { t } = useTranslation()
+
+ const validate = (values: EarnFormValues) => {
+ const errors = {} as FormikErrors
+ const isRelOffer = isRelativeOffer(values.offertype)
+ const isAbsOffer = isAbsoluteOffer(values.offertype)
+
+ if (!isRelOffer && !isAbsOffer) {
+ // currently no need for translation, this should never occur -> input is controlled by toggle
+ errors.offertype = 'Offertype is not supported'
+ }
+
+ if (isRelOffer) {
+ if (typeof values.feeRel !== 'number' || values.feeRel < feeRelMin || values.feeRel > feeRelMax) {
+ errors.feeRel = t('earn.feedback_invalid_rel_fee', {
+ feeRelPercentageMin: `${factorToPercentage(feeRelMin)}%`,
+ feeRelPercentageMax: `${factorToPercentage(feeRelMax)}%`,
+ })
+ }
+ }
+
+ if (isAbsOffer) {
+ if (typeof values.feeAbs !== 'number' || values.feeAbs < 0) {
+ errors.feeAbs = t('earn.feedback_invalid_abs_fee')
+ }
+ }
+
+ if (typeof values.minsize !== 'number' || values.minsize < 0) {
+ errors.minsize = t('earn.feedback_invalid_min_amount')
+ }
+
+ return errors
+ }
+
+ return (
+
+ {({ handleSubmit, setFieldValue, handleChange, handleBlur, values, touched, errors, isSubmitting }) => (
+ <>
+
+
+ <>
+
+ {
+ checked && setFieldValue('offertype', tab.value, true)
+ }}
+ initialValue={values.offertype}
+ disabled={isLoading || isSubmitting}
+ />
+
+ {values.offertype === OFFERTYPE_REL ? (
+
+
+ {t('earn.label_rel_fee', {
+ fee: typeof values.feeRel === 'number' ? `(${factorToPercentage(values.feeRel)}%)` : '',
+ })}
+
+ {t('earn.description_rel_fee')}
+ {isLoading ? (
+
+
+
+ ) : (
+
+
+ %
+
+ {
+ const value = e.target.value || ''
+ setFieldValue('feeRel', value !== '' ? percentageToFactor(parseFloat(value)) : '', true)
+ }}
+ onBlur={handleBlur}
+ value={typeof values.feeRel === 'number' ? factorToPercentage(values.feeRel) : ''}
+ isValid={touched.feeRel && !errors.feeRel}
+ isInvalid={touched.feeRel && !!errors.feeRel}
+ min={0}
+ step={feeRelPercentageStep}
+ />
+ {errors.feeRel}
+
+ )}
+
+ ) : (
+
+
+ {t('earn.label_abs_fee', {
+ fee:
+ typeof values.feeAbs === 'number'
+ ? `(${values.feeAbs} ${values.feeAbs === 1 ? 'sat' : 'sats'})`
+ : '',
+ })}
+
+ {t('earn.description_abs_fee')}
+ {isLoading ? (
+
+
+
+ ) : (
+
+
+
+
+
+ {errors.feeAbs}
+
+ )}
+
+ )}
+
+
+ {t('earn.label_min_amount')}
+ {isLoading ? (
+
+
+
+ ) : (
+
+
+
+
+
+ {errors.minsize}
+
+ )}
+
+ >
+
+
+ {submitButtonText(isSubmitting)}
+
+
+ >
+ )}
+
+ )
+}
+
const toStartMakerRequest = (values: EarnFormValues): Api.StartMakerRequest => {
// both fee properties need to be provided.
// prevent providing an invalid value by setting the ignored prop to zero
@@ -225,32 +429,38 @@ export default function Earn({ wallet }: EarnProps) {
const [isWaitingMakerStop, setIsWaitingMakerStop] = useState(false)
const [isShowReport, setIsShowReport] = useState(false)
const [isShowOrderbook, setIsShowOrderbook] = useState(false)
+
+ const [initialValues, setInitialValues] = useState(initialFormValues())
+
const fidelityBonds = useMemo(() => {
return currentWalletInfo?.fidelityBondSummary.fbOutputs || []
}, [currentWalletInfo])
const [moveToJarFidelityBondId, setMoveToJarFidelityBondId] = useState()
- const startMakerService = (values: EarnFormValues) => {
- setIsSending(true)
- setIsWaitingMakerStart(true)
-
- // There is no response data to check if maker got started:
- // Wait for the websocket or session response!
- return (
- Api.postMakerStart({ ...wallet }, toStartMakerRequest(values))
- .then((res) => (res.ok ? true : Api.Helper.throwError(res)))
- // show the loader a little longer to avoid flickering
- .then((result) => new Promise((r) => setTimeout(() => r(result), 200)))
- .catch((e) => {
- setIsWaitingMakerStart(false)
- throw e
- })
- .finally(() => setIsSending(false))
- )
- }
+ const startMakerService = useCallback(
+ (values: EarnFormValues) => {
+ setIsSending(true)
+ setIsWaitingMakerStart(true)
+
+ // There is no response data to check if maker got started:
+ // Wait for the websocket or session response!
+ return (
+ Api.postMakerStart({ ...wallet }, toStartMakerRequest(values))
+ .then((res) => (res.ok ? true : Api.Helper.throwError(res)))
+ // show the loader a little longer to avoid flickering
+ .then((result) => new Promise((r) => setTimeout(() => r(result), 200)))
+ .catch((e) => {
+ setIsWaitingMakerStart(false)
+ throw e
+ })
+ .finally(() => setIsSending(false))
+ )
+ },
+ [wallet],
+ )
- const stopMakerService = () => {
+ const stopMakerService = useCallback(() => {
setIsSending(true)
setIsWaitingMakerStop(true)
@@ -264,7 +474,7 @@ export default function Earn({ wallet }: EarnProps) {
throw e
})
.finally(() => setIsSending(false))
- }
+ }, [wallet])
useEffect(() => {
if (isSending) return
@@ -308,65 +518,53 @@ export default function Earn({ wallet }: EarnProps) {
})
}, [isSending, serviceInfo, isWaitingMakerStart, isWaitingMakerStop, t])
- const reloadFidelityBonds = ({ delay }: { delay: number }) => {
- const abortCtrl = new AbortController()
+ const reloadFidelityBonds = useCallback(
+ ({ delay }: { delay: number }) => {
+ const abortCtrl = new AbortController()
- setIsLoading(true)
+ setIsLoading(true)
- new Promise((resolve) => {
- setTimeout(async () => {
- resolve(await reloadCurrentWalletInfo.reloadUtxos({ signal: abortCtrl.signal }))
- }, delay)
- })
- .catch((err) => {
- if (abortCtrl.signal.aborted) return
- setAlert({ variant: 'danger', message: err.message })
+ new Promise((resolve) => {
+ setTimeout(async () => {
+ resolve(await reloadCurrentWalletInfo.reloadUtxos({ signal: abortCtrl.signal }))
+ }, delay)
})
- .finally(() => {
- if (abortCtrl.signal.aborted) return
- setIsLoading(false)
- })
- }
-
- const feeRelMin = 0.0
- const feeRelMax = 0.1 // 10%
- const feeRelPercentageStep = 0.0001
+ .catch((err) => {
+ if (abortCtrl.signal.aborted) return
+ setAlert({ variant: 'danger', message: err.message || t('global.errors.reason_unknown') })
+ })
+ .finally(() => {
+ if (abortCtrl.signal.aborted) return
+ setIsLoading(false)
+ })
+ },
+ [reloadCurrentWalletInfo, t],
+ )
- const initialValues = initialFormValues()
+ const onSubmitStart = useCallback(
+ async (values: EarnFormValues) => {
+ if (isLoading || isSending || isWaitingMakerStart || isWaitingMakerStop) {
+ return
+ }
- const validate = (values: EarnFormValues) => {
- const errors = {} as FormikErrors
- const isRelOffer = isRelativeOffer(values.offertype)
- const isAbsOffer = isAbsoluteOffer(values.offertype)
+ setAlert(undefined)
- if (!isRelOffer && !isAbsOffer) {
- // currently no need for translation, this should never occur -> input is controlled by toggle
- errors.offertype = 'Offertype is not supported'
- }
+ try {
+ persistFormValues(values)
+ setInitialValues(initialFormValues())
- if (isRelOffer) {
- if (typeof values.feeRel !== 'number' || values.feeRel < feeRelMin || values.feeRel > feeRelMax) {
- errors.feeRel = t('earn.feedback_invalid_rel_fee', {
- feeRelPercentageMin: `${factorToPercentage(feeRelMin)}%`,
- feeRelPercentageMax: `${factorToPercentage(feeRelMax)}%`,
- })
- }
- }
+ setServiceInfoAlert({ variant: 'success', message: t('earn.alert_starting') })
- if (isAbsOffer) {
- if (typeof values.feeAbs !== 'number' || values.feeAbs < 0) {
- errors.feeAbs = t('earn.feedback_invalid_abs_fee')
+ await startMakerService(values)
+ } catch (e: any) {
+ setServiceInfoAlert(undefined)
+ setAlert({ variant: 'danger', message: e.message || t('global.errors.reason_unknown') })
}
- }
-
- if (typeof values.minsize !== 'number' || values.minsize < 0) {
- errors.minsize = t('earn.feedback_invalid_min_amount')
- }
-
- return errors
- }
+ },
+ [startMakerService, isLoading, isSending, isWaitingMakerStart, isWaitingMakerStop, t],
+ )
- const onSubmit = async (values: EarnFormValues) => {
+ const onSubmitStop = useCallback(async () => {
if (isLoading || isSending || isWaitingMakerStart || isWaitingMakerStop) {
return
}
@@ -374,21 +572,13 @@ export default function Earn({ wallet }: EarnProps) {
setAlert(undefined)
try {
- if (serviceInfo?.makerRunning === true) {
- setServiceInfoAlert({ variant: 'success', message: t('earn.alert_stopping') })
- await stopMakerService()
- } else {
- persistFormValues(values)
-
- setServiceInfoAlert({ variant: 'success', message: t('earn.alert_starting') })
-
- await startMakerService(values)
- }
+ setServiceInfoAlert({ variant: 'success', message: t('earn.alert_stopping') })
+ await stopMakerService()
} catch (e: any) {
setServiceInfoAlert(undefined)
setAlert({ variant: 'danger', message: e.message || t('global.errors.reason_unknown') })
}
- }
+ }, [stopMakerService, isLoading, isSending, isWaitingMakerStart, isWaitingMakerStop, t])
return (
@@ -494,166 +684,18 @@ export default function Earn({ wallet }: EarnProps) {
>
)}
- {!serviceInfo?.coinjoinInProgress && (
-
- {({ handleSubmit, setFieldValue, handleChange, handleBlur, values, touched, errors, isSubmitting }) => (
- <>
-
- {!serviceInfo?.makerRunning && !isWaitingMakerStart && !isWaitingMakerStop && (
-
- <>
-
- {
- checked && setFieldValue('offertype', tab.value, true)
- }}
- initialValue={values.offertype}
- disabled={isLoading || isSubmitting}
- />
-
- {values.offertype === OFFERTYPE_REL ? (
-
-
- {t('earn.label_rel_fee', {
- fee:
- typeof values.feeRel === 'number' ? `(${factorToPercentage(values.feeRel)}%)` : '',
- })}
-
-
- {t('earn.description_rel_fee')}
-
- {isLoading ? (
-
-
-
- ) : (
-
-
- %
-
- {
- const value = e.target.value || ''
- setFieldValue(
- 'feeRel',
- value !== '' ? percentageToFactor(parseInt(value, 10)) : '',
- true,
- )
- }}
- onBlur={handleBlur}
- value={typeof values.feeRel === 'number' ? factorToPercentage(values.feeRel) : ''}
- isValid={touched.feeRel && !errors.feeRel}
- isInvalid={touched.feeRel && !!errors.feeRel}
- min={0}
- step={feeRelPercentageStep}
- />
- {errors.feeRel}
-
- )}
-
- ) : (
-
-
- {t('earn.label_abs_fee', {
- fee:
- typeof values.feeAbs === 'number'
- ? `(${values.feeAbs} ${values.feeAbs === 1 ? 'sat' : 'sats'})`
- : '',
- })}
-
-
- {t('earn.description_abs_fee')}
-
- {isLoading ? (
-
-
-
- ) : (
-
-
-
-
-
- {errors.feeAbs}
-
- )}
-
- )}
-
- {t('earn.label_min_amount')}
- {isLoading ? (
-
-
-
- ) : (
-
-
-
-
-
- {errors.minsize}
-
- )}
-
- >
-
- )}
-
-
+ {!serviceInfo?.coinjoinInProgress && (
+ <>
+ {!serviceInfo?.makerRunning && !isWaitingMakerStart && !isWaitingMakerStop ? (
+ {
+ return (
+ <>
{isWaitingMakerStart || isWaitingMakerStop ? (
<>
{serviceInfo?.makerRunning === true ? t('earn.button_stop') : t('earn.button_start')}>
)}
-
-
-
- >
+ >
+ )
+ }}
+ />
+ ) : (
+
+ {({ handleSubmit, isSubmitting }) => (
+
+
+
+ {isWaitingMakerStart || isWaitingMakerStop ? (
+ <>
+
+ {isWaitingMakerStart && t('earn.text_starting')}
+ {isWaitingMakerStop && t('earn.text_stopping')}
+ >
+ ) : (
+ <>{t('earn.button_stop')}>
+ )}
+
+
+
+ )}
+
)}
-
+ >
)}