Skip to content

Commit

Permalink
OCT-1369: Closing Calculate Reward modal should cancel the API call t…
Browse files Browse the repository at this point in the history
…o /rewards/estimated_budget if response is not recieved (#47)

Co-authored-by: Andrzej Ziółek <[email protected]>
  • Loading branch information
jmikolajczyk and aziolek authored Mar 6, 2024
1 parent 6b2f705 commit 499195b
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 58 deletions.
36 changes: 26 additions & 10 deletions client/cypress/e2e/rewardsCalculator.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes';

Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => {
describe(`rewards calculator: ${device}`, { viewportHeight, viewportWidth }, () => {
before(() => {
/**
* Global Metamask setup done by Synpress is not always done.
* Since Synpress needs to have valid provider to fetch the data from contracts,
* setupMetamask is required in each test suite.
*/
cy.setupMetamask();
});

beforeEach(() => {
mockCoinPricesServer();
localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false');
Expand All @@ -35,7 +26,7 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes
});
}

it('clicking on rewards calculator icon opens rewards calcultor modal', () => {
it('clicking on rewards calculator icon opens rewards calculator modal', () => {
cy.get('[data-test=Tooltip__rewardsCalculator__body]').click();
cy.get('[data-test=ModalRewardsCalculator]').should('be.visible');
});
Expand Down Expand Up @@ -201,5 +192,30 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDes
.invoke('val')
.should('eq', '');
});

it('Closing the modal successfully cancels the request /estimated_budget', () => {
cy.intercept('POST', '/rewards/estimated_budget', {
body: { budget: '850684931506849269541' },
// Long enough to never complete.
delay: 5000000,
}).as('postEstimatedRewards');

cy.window().then(win => {
cy.spy(win.console, 'error').as('consoleErrSpy');
});

cy.get('[data-test=Tooltip__rewardsCalculator__body]').click();

cy.get('[data-test=RewardsCalculator__InputText--estimatedRewards--crypto__Loader]').should(
'be.visible',
);

cy.get('[data-test=ModalRewardsCalculator__Button]').click();
cy.get('[data-test=ModalRewardsCalculator').should('not.be.visible');

cy.on('uncaught:exception', error => {
expect(error.code).to.equal('ERR_CANCELED');
});
});
});
});
4 changes: 3 additions & 1 deletion client/src/api/calls/calculateRewards.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { GenericAbortSignal } from 'axios';

import env from 'env';
import apiService from 'services/apiService';

Expand All @@ -9,7 +11,7 @@ export function apiPostCalculateRewards(
// WEI
amount: string,
days: number,
signal?: AbortSignal,
signal: GenericAbortSignal,
): Promise<PostCalculateRewardsResponse> {
return apiService
.post(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import cx from 'classnames';
import { useFormik } from 'formik';
import debounce from 'lodash/debounce';
import React, { FC, useCallback, useEffect, useState } from 'react';
import React, { FC, useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';

import { apiPostCalculateRewards } from 'api/calls/calculateRewards';
import clientReactQuery from 'api/clients/client-react-query';
import { QUERY_KEYS, ROOTS } from 'api/queryKeys';
import BoxRounded from 'components/ui/BoxRounded';
import InputText from 'components/ui/InputText';
import { GLM_TOTAL_SUPPLY } from 'constants/currencies';
import useCalculateRewards from 'hooks/mutations/useCalculateRewards';
import useCryptoValues from 'hooks/queries/useCryptoValues';
import i18n from 'i18n';
import useSettingsStore from 'store/settings/store';
Expand All @@ -27,8 +24,6 @@ const EarnRewardsCalculator: FC = () => {
const { t } = useTranslation('translation', {
keyPrefix: 'components.dedicated.rewardsCalculator',
});
const [estimatedRewards, setEstimatedRewards] = useState<bigint | undefined>();
const [isFetching, setIsFetching] = useState(false);
const {
data: { displayCurrency, isCryptoMainValueDisplay },
} = useSettingsStore(({ data }) => ({
Expand All @@ -38,44 +33,29 @@ const EarnRewardsCalculator: FC = () => {
},
}));
const { data: cryptoValues } = useCryptoValues(displayCurrency);
const {
data: calculateRewards,
mutateAsync: mutateAsyncRewardsCalculator,
reset: resetCalculateRewards,
isPending: isPendingCalculateRewards,
} = useCalculateRewards();

// eslint-disable-next-line react-hooks/exhaustive-deps
const fetchEstimatedRewardsDebounced = useCallback(
debounce((amountGlm, d) => {
const isFetchingRewards = clientReactQuery.isFetching({ queryKey: [ROOTS.calculateRewards] });

if (!amountGlm || !d || parseUnitsBigInt(amountGlm) > GLM_TOTAL_SUPPLY) {
if (isFetchingRewards) {
clientReactQuery.cancelQueries({ queryKey: [ROOTS.calculateRewards] });
}
setEstimatedRewards(undefined);
setIsFetching(false);
return;
}

if (isFetchingRewards) {
clientReactQuery.cancelQueries({ queryKey: [ROOTS.calculateRewards] });
}

debounce(({ amountGlm, numberOfDays }) => {
const amountGlmWEI = formatUnitsBigInt(parseUnitsBigInt(amountGlm, 'ether'), 'wei');
setIsFetching(true);

clientReactQuery
.fetchQuery({
queryFn: ({ signal }) => apiPostCalculateRewards(amountGlmWEI, parseInt(d, 10), signal),
queryKey: QUERY_KEYS.calculateRewards(amountGlmWEI, parseInt(d, 10)),
})
.then(res => {
setIsFetching(false);
setEstimatedRewards(parseUnitsBigInt(res.budget, 'wei'));
});
const numberOfDaysNumber = parseInt(numberOfDays, 10);

resetCalculateRewards();
mutateAsyncRewardsCalculator({ amountGlm: amountGlmWEI, numberOfDays: numberOfDaysNumber });
}, 300),
[],
);

const formik = useFormik<FormFields>({
initialValues: formInitialValues,
onSubmit: values => fetchEstimatedRewardsDebounced(values.valueCrypto, values.days),
onSubmit: values =>
fetchEstimatedRewardsDebounced({ amountGlm: values.valueCrypto, numberOfDays: values.days }),
validateOnChange: true,
validationSchema: validationSchema(t),
});
Expand All @@ -99,19 +79,29 @@ const EarnRewardsCalculator: FC = () => {

useEffect(() => {
formik.validateForm().then(() => {
fetchEstimatedRewardsDebounced(formik.values.valueCrypto, formik.values.days);
fetchEstimatedRewardsDebounced({
amountGlm: formik.values.valueCrypto,
numberOfDays: formik.values.days,
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formik.values.valueCrypto, formik.values.days]);

useEffect(() => {
return () => {
resetCalculateRewards();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const estimatedFormattedRewardsValue: FormattedCryptoValue =
formik.values.valueCrypto && formik.values.days && estimatedRewards
? getFormattedEthValue(estimatedRewards)
formik.values.valueCrypto && formik.values.days && calculateRewards
? getFormattedEthValue(parseUnitsBigInt(calculateRewards.budget, 'wei'))
: {
fullString: '',
suffix: 'ETH',
value: '',
};
fullString: '',
suffix: 'ETH',
value: '',
};

const cryptoFiatRatio = cryptoValues?.ethereum[displayCurrency || 'usd'] || 1;
const fiat = estimatedFormattedRewardsValue.value
Expand Down Expand Up @@ -155,7 +145,7 @@ const EarnRewardsCalculator: FC = () => {
isButtonClearVisible={false}
isDisabled
shouldAutoFocusAndSelect={false}
showLoader={isFetching}
showLoader={isPendingCalculateRewards}
suffix={estimatedFormattedRewardsValue.suffix}
suffixClassName={styles.estimatedRewardsSuffix}
value={estimatedFormattedRewardsValue.value}
Expand All @@ -169,7 +159,7 @@ const EarnRewardsCalculator: FC = () => {
isButtonClearVisible={false}
isDisabled
shouldAutoFocusAndSelect={false}
showLoader={isFetching}
showLoader={isPendingCalculateRewards}
suffix={displayCurrency.toUpperCase()}
suffixClassName={styles.estimatedRewardsSuffix}
value={fiat}
Expand Down
6 changes: 3 additions & 3 deletions client/src/hooks/mutations/useAllocateSimulate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMutation, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
import { useCallback, useRef } from 'react';
import { useRef } from 'react';
import { useAccount } from 'wagmi';

import { apiPostAllocateLeverage, ApiPostAllocateLeverageResponse } from 'api/calls/allocate';
Expand All @@ -26,10 +26,10 @@ export default function useAllocateSimulate(
...options,
});

const reset = useCallback(() => {
const reset = () => {
abortControllerRef.current?.abort();
mutation.reset();
}, [abortControllerRef, mutation]);
};

return { ...mutation, reset };
}
30 changes: 30 additions & 0 deletions client/src/hooks/mutations/useCalculateRewards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useMutation, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
import { useRef } from 'react';

import { PostCalculateRewardsResponse, apiPostCalculateRewards } from 'api/calls/calculateRewards';

type CalculateRewardsArguments = {
amountGlm: string;
numberOfDays: number;
};

export default function useCalculateRewards(
options?: UseMutationOptions<any, unknown, CalculateRewardsArguments>,
): UseMutationResult<PostCalculateRewardsResponse, unknown, CalculateRewardsArguments> {
const abortControllerRef = useRef<AbortController | null>(null);

const mutation = useMutation({
mutationFn: async ({ amountGlm, numberOfDays }) => {
abortControllerRef.current = new AbortController();
return apiPostCalculateRewards(amountGlm, numberOfDays, abortControllerRef.current.signal);
},
...options,
});

const reset = () => {
abortControllerRef.current?.abort();
mutation.reset();
};

return { ...mutation, reset };
}

0 comments on commit 499195b

Please sign in to comment.