diff --git a/package-lock.json b/package-lock.json index bec152ba..aef249aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "p2p", "version": "0.0.0", "dependencies": { - "@deriv-com/api-hooks": "^0.1.8", + "@deriv-com/api-hooks": "^0.1.13", "@deriv-com/ui": "latest", "@deriv-com/utils": "latest", "@deriv/deriv-api": "^1.0.15", @@ -1623,9 +1623,9 @@ } }, "node_modules/@deriv-com/api-hooks": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@deriv-com/api-hooks/-/api-hooks-0.1.8.tgz", - "integrity": "sha512-84jqyJ3yRuLmBBqXhnshKUW9Tcv+kl0pbUCOwOfC4l5H6AeMxsy529Gcu7UxjjZTv0IT+gh0TMOADW9QC3MO7w==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@deriv-com/api-hooks/-/api-hooks-0.1.13.tgz", + "integrity": "sha512-GtXI+xTL3c62kUtpbt3uaQ3WS0yUGpXStohcc/Yyn28oZGScDYPY7b9gCBN2+shY9HE8ReqR9AxccV4t3YfFwA==", "dependencies": { "@deriv-com/utils": "^0.0.11", "@deriv/api-types": "^1.0.177", @@ -19948,9 +19948,9 @@ } }, "@deriv-com/api-hooks": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@deriv-com/api-hooks/-/api-hooks-0.1.8.tgz", - "integrity": "sha512-84jqyJ3yRuLmBBqXhnshKUW9Tcv+kl0pbUCOwOfC4l5H6AeMxsy529Gcu7UxjjZTv0IT+gh0TMOADW9QC3MO7w==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@deriv-com/api-hooks/-/api-hooks-0.1.13.tgz", + "integrity": "sha512-GtXI+xTL3c62kUtpbt3uaQ3WS0yUGpXStohcc/Yyn28oZGScDYPY7b9gCBN2+shY9HE8ReqR9AxccV4t3YfFwA==", "requires": { "@deriv-com/utils": "^0.0.11", "@deriv/api-types": "^1.0.177", diff --git a/package.json b/package.json index 137431a2..ac5fce95 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "prepare": "husky install" }, "dependencies": { - "@deriv-com/api-hooks": "^0.1.8", + "@deriv-com/api-hooks": "^0.1.13", "@deriv-com/ui": "latest", "@deriv-com/utils": "latest", "@deriv/deriv-api": "^1.0.15", diff --git a/src/components/AdvertiserName/AdvertiserNameStats.tsx b/src/components/AdvertiserName/AdvertiserNameStats.tsx index 6ddf06c9..fb8813dd 100644 --- a/src/components/AdvertiserName/AdvertiserNameStats.tsx +++ b/src/components/AdvertiserName/AdvertiserNameStats.tsx @@ -4,7 +4,7 @@ import { OnlineStatusIcon, OnlineStatusLabel, StarRating } from '@/components'; import { getCurrentRoute } from '@/utils'; import { Text, useDevice } from '@deriv-com/ui'; import ThumbUpIcon from '../../public/ic-thumb-up.svg'; -import BlockedUserOutlineIcon from '../../public/ic-user-blocked-outline.svg'; +import BlockedUserOutlineIcon from '../../public/ic-user-blocked-outline.svg?react'; import './AdvertiserNameStats.scss'; /** diff --git a/src/components/AdvertsTableRow/AdvertsTableRow.tsx b/src/components/AdvertsTableRow/AdvertsTableRow.tsx index 17678737..a06d2842 100644 --- a/src/components/AdvertsTableRow/AdvertsTableRow.tsx +++ b/src/components/AdvertsTableRow/AdvertsTableRow.tsx @@ -1,13 +1,14 @@ -import { Fragment, memo, useState } from 'react'; +import { Fragment, memo, useEffect, useRef, useState } from 'react'; import clsx from 'clsx'; import { useHistory } from 'react-router-dom'; -import { TAdvertsTableRowRenderer } from 'types'; +import { TAdvertsTableRowRenderer, TCurrency, TExchangeRate } from 'types'; import { Badge, BuySellForm, PaymentMethodLabel, StarRating, UserAvatar } from '@/components'; import { ADVERTISER_URL, BUY_SELL } from '@/constants'; import { api } from '@/hooks'; import { useIsAdvertiser } from '@/hooks/custom-hooks'; import { generateEffectiveRate, getCurrentRoute } from '@/utils'; import { LabelPairedChevronRightMdRegularIcon } from '@deriv/quill-icons'; +import { useExchangeRates } from '@deriv-com/api-hooks'; import { Button, Text, useDevice } from '@deriv-com/ui'; import './AdvertsTableRow.scss'; @@ -15,7 +16,7 @@ const BASE_CURRENCY = 'USD'; const AdvertsTableRow = memo((props: TAdvertsTableRowRenderer) => { const [isModalOpen, setIsModalOpen] = useState(false); - const { subscribeRates } = api.account.useExchangeRateSubscription(); + const { subscribeRates } = useExchangeRates(); const { isDesktop, isMobile } = useDevice(); const history = useHistory(); const isBuySellPage = getCurrentRoute() === 'buy-sell'; @@ -26,12 +27,14 @@ const AdvertsTableRow = memo((props: TAdvertsTableRowRenderer) => { const { data } = api.advertiser.useGetInfo() || {}; const { daily_buy = 0, daily_buy_limit = 0, daily_sell = 0, daily_sell_limit = 0 } = data || {}; + const exchangeRateRef = useRef(null); + const { account_currency, advertiser_details, counterparty_type, effective_rate, - local_currency, + local_currency = '', max_order_amount_limit_display, min_order_amount_limit_display, payment_method_names, @@ -40,15 +43,22 @@ const AdvertsTableRow = memo((props: TAdvertsTableRowRenderer) => { rate_type, } = props; - const exchangeRate = subscribeRates({ base_currency: BASE_CURRENCY, target_currencies: [local_currency] }); + useEffect(() => { + if (local_currency) { + exchangeRateRef.current = subscribeRates({ + base_currency: BASE_CURRENCY, + target_currencies: [local_currency], + }); + } + }, [local_currency]); const Container = isMobile ? 'div' : Fragment; const { completed_orders_count, id, is_online, name, rating_average, rating_count } = advertiser_details || {}; const { displayEffectiveRate, effectiveRate } = generateEffectiveRate({ - exchangeRate: exchangeRate?.rates?.[local_currency], - localCurrency: local_currency, + exchangeRate: exchangeRateRef.current?.rates?.[local_currency], + localCurrency: local_currency as TCurrency, marketRate: Number(effective_rate), price: Number(price_display), rate, diff --git a/src/components/BuySellForm/__tests__/BuySellForm.spec.tsx b/src/components/BuySellForm/__tests__/BuySellForm.spec.tsx index e89718dd..95bebafe 100644 --- a/src/components/BuySellForm/__tests__/BuySellForm.spec.tsx +++ b/src/components/BuySellForm/__tests__/BuySellForm.spec.tsx @@ -26,7 +26,17 @@ const mockOnChange = jest.fn(); const mockHandleSubmit = jest.fn(); jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), - Controller: ({ control, defaultValue, name, render }) => + Controller: ({ + control, + defaultValue, + name, + render, + }: { + control: string; + defaultValue: object; + name: string; + render: (param: object) => void; + }) => render({ field: { control, name, onBlur: jest.fn(), onChange: mockOnChange, value: defaultValue }, fieldState: { error: null }, diff --git a/src/components/FloatingRate/FloatingRate.tsx b/src/components/FloatingRate/FloatingRate.tsx index d8a6d756..d996425d 100644 --- a/src/components/FloatingRate/FloatingRate.tsx +++ b/src/components/FloatingRate/FloatingRate.tsx @@ -1,6 +1,8 @@ -import { ChangeEvent, FocusEvent } from 'react'; +import { ChangeEvent, FocusEvent, useEffect, useRef } from 'react'; +import { TCurrency, TExchangeRate } from 'types'; import { api } from '@/hooks'; import { mobileOSDetect, percentOf, removeTrailingZeros, roundOffDecimal, setDecimalPlaces } from '@/utils'; +import { useExchangeRates } from '@deriv-com/api-hooks'; import { Text, useDevice } from '@deriv-com/ui'; import { FormatUtils } from '@deriv-com/utils'; import InputField from '../InputField'; @@ -10,7 +12,7 @@ type TFloatingRate = { changeHandler?: (event: ChangeEvent) => void; errorMessages: string; fiatCurrency: string; - localCurrency: string; + localCurrency: TCurrency; name?: string; onChange: (event: ChangeEvent) => void; value?: string; @@ -25,16 +27,25 @@ const FloatingRate = ({ onChange, value, }: TFloatingRate) => { - const { subscribeRates } = api.account.useExchangeRateSubscription(); + const { subscribeRates } = useExchangeRates(); const { isMobile } = useDevice(); - const exchangeRateValue = subscribeRates({ base_currency: 'USD', target_currencies: [localCurrency] }); - const { data: p2pSettings } = api.settings.useSettings(); const overrideExchangeRate = p2pSettings?.override_exchange_rate; + const exchangeRateRef = useRef(null); + + useEffect(() => { + if (localCurrency) { + exchangeRateRef.current = subscribeRates({ + base_currency: 'USD', + target_currencies: [localCurrency], + }); + } + }, [localCurrency]); + const marketRate = overrideExchangeRate ? Number(overrideExchangeRate) - : exchangeRateValue?.rates?.[localCurrency] ?? 1; + : exchangeRateRef.current?.rates?.[localCurrency] ?? 1; const os = mobileOSDetect(); const marketFeed = value ? percentOf(marketRate, Number(value)) : marketRate; const decimalPlace = setDecimalPlaces(marketFeed, 6); diff --git a/src/components/Modals/AvailableP2PBalanceModal/AvailableP2PBalanceModal.tsx b/src/components/Modals/AvailableP2PBalanceModal/AvailableP2PBalanceModal.tsx index c38c1f99..8d8687a9 100644 --- a/src/components/Modals/AvailableP2PBalanceModal/AvailableP2PBalanceModal.tsx +++ b/src/components/Modals/AvailableP2PBalanceModal/AvailableP2PBalanceModal.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import Modal from 'react-modal'; import { Button, Text } from '@deriv-com/ui'; import { customStyles } from '../helpers'; @@ -10,10 +9,6 @@ type TAvailableP2PBalanceModalProps = { }; const AvailableP2PBalanceModal = ({ isModalOpen, onRequestClose }: TAvailableP2PBalanceModalProps) => { - useEffect(() => { - Modal.setAppElement('#v2_modal_root'); - }, []); - return ( { - Modal.setAppElement('#v2_modal_root'); - }, []); - const getModalTitle = () => (isBlocked ? `Unblock ${advertiserName}?` : `Block ${advertiserName}?`); const getModalContent = () => diff --git a/src/components/Modals/DailyLimitModal/DailyLimitModal.tsx b/src/components/Modals/DailyLimitModal/DailyLimitModal.tsx index 1abc8821..6990060c 100644 --- a/src/components/Modals/DailyLimitModal/DailyLimitModal.tsx +++ b/src/components/Modals/DailyLimitModal/DailyLimitModal.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import Modal from 'react-modal'; import { api } from '@/hooks'; import { useDevice } from '@/hooks/custom-hooks'; @@ -16,9 +15,6 @@ const DailyLimitModal = ({ currency, isModalOpen, onRequestClose }: TDailyLimitM const { data, error, isLoading, isSuccess, mutate } = api.advertiser.useUpdate(); const { daily_buy_limit, daily_sell_limit } = data ?? {}; const { isMobile } = useDevice(); - useEffect(() => { - Modal.setAppElement('#v2_modal_root'); - }, []); const getModalContent = () => { //TODO: modal header title to be moved out if needed according to implementation, can be moved to a separate getheader, getcontent, getfooter functions diff --git a/src/components/Modals/NicknameModal/NicknameModal.tsx b/src/components/Modals/NicknameModal/NicknameModal.tsx index 15700868..97e1cd56 100644 --- a/src/components/Modals/NicknameModal/NicknameModal.tsx +++ b/src/components/Modals/NicknameModal/NicknameModal.tsx @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction, useEffect } from 'react'; +import { useEffect } from 'react'; import { debounce } from 'lodash'; import { Controller, useForm } from 'react-hook-form'; import { useHistory } from 'react-router-dom'; @@ -11,10 +11,10 @@ import './NicknameModal.scss'; type TNicknameModalProps = { isModalOpen: boolean | undefined; - setIsModalOpen: Dispatch>; + onRequestClose: () => void; }; -const NicknameModal = ({ isModalOpen, setIsModalOpen }: TNicknameModalProps) => { +const NicknameModal = ({ isModalOpen, onRequestClose }: TNicknameModalProps) => { const { control, formState: { isDirty, isValid }, @@ -40,7 +40,7 @@ const NicknameModal = ({ isModalOpen, setIsModalOpen }: TNicknameModalProps) => useEffect(() => { if (isSuccess) { - setIsModalOpen(false); + onRequestClose; setHasCreatedAdvertiser(true); } else if (isError) { debouncedReset(); @@ -90,7 +90,7 @@ const NicknameModal = ({ isModalOpen, setIsModalOpen }: TNicknameModalProps) => color='black' onClick={() => { history.push(BUY_SELL_URL); - setIsModalOpen(false); + onRequestClose; }} size='lg' textSize={textSize} diff --git a/src/components/PaymentMethodField/__tests__/PaymentMethodField.spec.tsx b/src/components/PaymentMethodField/__tests__/PaymentMethodField.spec.tsx index abbb7302..0f257290 100644 --- a/src/components/PaymentMethodField/__tests__/PaymentMethodField.spec.tsx +++ b/src/components/PaymentMethodField/__tests__/PaymentMethodField.spec.tsx @@ -4,7 +4,17 @@ import PaymentMethodField from '../PaymentMethodField'; jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), - Controller: ({ control, defaultValue, name, render }) => + Controller: ({ + control, + defaultValue, + name, + render, + }: { + control: string; + defaultValue: object; + name: string; + render: (param: object) => void; + }) => render({ field: { control, name, onBlur: jest.fn(), onChange: jest.fn(), value: defaultValue }, fieldState: { error: null }, diff --git a/src/constants/ad-constants.ts b/src/constants/ad-constants.ts index 3038dd7e..870e9227 100644 --- a/src/constants/ad-constants.ts +++ b/src/constants/ad-constants.ts @@ -1,12 +1,12 @@ -export const COUNTERPARTIES_DROPDOWN_LIST = Object.freeze([ +export const COUNTERPARTIES_DROPDOWN_LIST = [ { text: 'All', value: 'all' }, { text: 'Blocked', value: 'blocked' }, -]); +] as const; -export const RATE_TYPE = Object.freeze({ +export const RATE_TYPE = { FIXED: 'fixed', FLOAT: 'float', -}); +} as const; export const AD_ACTION = { ACTIVATE: 'activate', diff --git a/src/hooks/api/account/index.ts b/src/hooks/api/account/index.ts index df73e5f8..9b5bc72d 100644 --- a/src/hooks/api/account/index.ts +++ b/src/hooks/api/account/index.ts @@ -1,5 +1,4 @@ export { default as useActiveAccount } from './useActiveAccount'; export { default as useAuthentication } from './useAuthentication'; export { default as useServerTime } from './useServerTime'; -export { default as useExchangeRateSubscription } from './useExchangeRateSubscription'; export { default as useSendbirdServiceToken } from './useSendbirdServiceToken'; diff --git a/src/hooks/api/account/useExchangeRateSubscription.ts b/src/hooks/api/account/useExchangeRateSubscription.ts deleted file mode 100644 index 3717b592..00000000 --- a/src/hooks/api/account/useExchangeRateSubscription.ts +++ /dev/null @@ -1,94 +0,0 @@ -//TODO: remove once implemented in api-hooks -import { useCallback, useRef, useState } from 'react'; -import { useSubscribe } from '@deriv-com/api-hooks'; - -type TCurrencyExchangeSubscribeFunction = { base_currency: T; target_currencies: T[] }; - -/** - * `useExchangeRateSubscription` is a custom hook that provides functionalities for subscribing to exchange rates, - * unsubscribing from them, and getting the exchange rate between two currencies. - * - * @returns {Object} An object containing the following properties: - * - `data`: The data received from the subscription. - * - `exchangeRates`: The current exchange rates. - * - `getExchangeRate`: A function to get the exchange rate between two currencies. - * - `subscribeRates`: A function to subscribe to exchange rates for a base currency and a list of target currencies. - * - `unsubscribeRates`: A function to unsubscribe from exchange rates for a base currency and a list of target currencies. - */ -const useExchangeRateSubscription = () => { - const { data, subscribe, unsubscribe } = useSubscribe('exchange_rates'); - const [exchangeRates, setExchangeRates] = useState>>({}); - const exchangeRatesSubscriptions = useRef<{ base_currency: string; target_currency: string }[]>([]); - - /** - * `subscribeRates` is a function that subscribes to exchange rates for a base currency and a list of target currencies. - * It updates the `exchangeRates` state with the initial exchange rates for the new subscriptions. - * - * @param {Object} params - The parameters for the subscription. - * @param {string} params.base_currency - The base currency. - * @param {string[]} params.target_currencies - The target currencies. - * @returns {Promise} A promise that resolves to the new exchange rates. - */ - const subscribeRates = useCallback( - async ({ base_currency, target_currencies }: TCurrencyExchangeSubscribeFunction) => { - await Promise.all( - target_currencies.map(async target_currency => { - exchangeRatesSubscriptions.current.push({ base_currency, target_currency }); - await subscribe({ base_currency, target_currency }); - }) - ); - - const newExchangeRates = { ...exchangeRates }; - - if (!newExchangeRates[base_currency]) newExchangeRates[base_currency] = {}; - - target_currencies.forEach(c => { - newExchangeRates[base_currency][c] = 1; - }); - - setExchangeRates(newExchangeRates); - - return newExchangeRates; - }, - [exchangeRates, subscribe] - ); - - /** - * `unsubscribeRates` is a function that unsubscribes from exchange rates for a base currency and a list of target currencies. - * It updates the `exchangeRates` state to remove the exchange rates for the unsubscribed currencies. - * - * @param {Object} params - The parameters for the unsubscription. - * @param {string} params.base_currency - The base currency. - * @param {string[]} params.target_currencies - The target currencies. - */ - const unsubscribeRates = useCallback( - async ({ base_currency, target_currencies }: TCurrencyExchangeSubscribeFunction) => { - exchangeRatesSubscriptions.current = exchangeRatesSubscriptions.current.filter(s => { - if (s.base_currency === base_currency && target_currencies.includes(s.target_currency)) { - unsubscribe(); - return false; - } - return true; - }); - - setExchangeRates(prev => { - const currentData = { ...(prev ?? {}) }; - if (currentData[base_currency]) { - target_currencies.forEach(c => { - delete currentData[base_currency][c]; - }); - } - return currentData; - }); - }, - [unsubscribe] - ); - - const getExchangeRate = (base: string, target: string) => { - return exchangeRates?.[base]?.[target] ?? 1; - }; - - return { data, exchangeRates, getExchangeRate, subscribeRates, unsubscribeRates }; -}; - -export default useExchangeRateSubscription; diff --git a/src/hooks/api/advertiser/p2p-advertiser/useAdvertiserUpdate.ts b/src/hooks/api/advertiser/p2p-advertiser/useAdvertiserUpdate.ts index 9dd67b7f..4db4d8c5 100644 --- a/src/hooks/api/advertiser/p2p-advertiser/useAdvertiserUpdate.ts +++ b/src/hooks/api/advertiser/p2p-advertiser/useAdvertiserUpdate.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo } from 'react'; -import { useP2pAdvertCreate } from '@deriv-com/api-hooks'; +import { useP2pAdvertCreate, useP2pAdvertiserUpdate } from '@deriv-com/api-hooks'; import useInvalidateQuery from '../../useInvalidateQuery'; type TPayload = NonNullable['mutate']>[0]>['payload']; @@ -20,7 +20,7 @@ const useAdvertiserUpdate = () => { data, mutate: _mutate, ...rest - } = useP2pAdvertCreate({ + } = useP2pAdvertiserUpdate({ onSuccess: () => { invalidate('p2p_advertiser_info'); }, @@ -29,7 +29,7 @@ const useAdvertiserUpdate = () => { const mutate = useCallback((payload: TPayload) => _mutate({ payload }), [_mutate]); const modified_data = useMemo(() => { - const p2p_advertiser_update = data?.p2p_advertiser_update; + const p2p_advertiser_update = data; if (!p2p_advertiser_update) return undefined; const { basic_verification, full_verification, is_approved, is_listed, is_online, show_name } = @@ -50,7 +50,7 @@ const useAdvertiserUpdate = () => { /** When true, the advertiser's real name will be displayed on to other users on adverts and orders. */ should_show_name: Boolean(show_name), }; - }, [data?.p2p_advertiser_update]); + }, [data]); return { /** Returns latest information of the advertiser from p2p_advertiser endpoint */ diff --git a/src/hooks/custom-hooks/useAdvertiserStats.ts b/src/hooks/custom-hooks/useAdvertiserStats.ts index d682ef09..abb0ef76 100644 --- a/src/hooks/custom-hooks/useAdvertiserStats.ts +++ b/src/hooks/custom-hooks/useAdvertiserStats.ts @@ -30,7 +30,7 @@ const useAdvertiserStats = (advertiserId?: string) => { useEffect(() => { if (isSuccess && advertiserId) { - subscribe({ id: advertiserId }); + subscribe(); } return () => { diff --git a/src/pages/my-ads/components/AdFormInput/__tests__/AdFormInput.spec.tsx b/src/pages/my-ads/components/AdFormInput/__tests__/AdFormInput.spec.tsx index c2e8ae1d..63b0721b 100644 --- a/src/pages/my-ads/components/AdFormInput/__tests__/AdFormInput.spec.tsx +++ b/src/pages/my-ads/components/AdFormInput/__tests__/AdFormInput.spec.tsx @@ -4,7 +4,17 @@ import AdFormInput from '../AdFormInput'; jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), - Controller: ({ control, defaultValue, name, render }) => + Controller: ({ + control, + defaultValue, + name, + render, + }: { + control: string; + defaultValue: object; + name: string; + render: (param: object) => void; + }) => render({ field: { control, name, onBlur: jest.fn(), onChange: jest.fn(), value: defaultValue }, fieldState: { error: null }, diff --git a/src/pages/my-ads/components/AdFormTextArea/__tests__/AdFormTextArea.spec.tsx b/src/pages/my-ads/components/AdFormTextArea/__tests__/AdFormTextArea.spec.tsx index 7932a289..e34e1c87 100644 --- a/src/pages/my-ads/components/AdFormTextArea/__tests__/AdFormTextArea.spec.tsx +++ b/src/pages/my-ads/components/AdFormTextArea/__tests__/AdFormTextArea.spec.tsx @@ -3,7 +3,17 @@ import AdFormTextArea from '../AdFormTextArea'; jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), - Controller: ({ control, defaultValue, name, render }) => + Controller: ({ + control, + defaultValue, + name, + render, + }: { + control: string; + defaultValue: object; + name: string; + render: (param: object) => void; + }) => render({ field: { control, name, onBlur: jest.fn(), onChange: jest.fn(), value: defaultValue }, fieldState: { error: null }, diff --git a/src/pages/my-ads/components/AdSummary/AdSummary.tsx b/src/pages/my-ads/components/AdSummary/AdSummary.tsx index cf686296..1578b93b 100644 --- a/src/pages/my-ads/components/AdSummary/AdSummary.tsx +++ b/src/pages/my-ads/components/AdSummary/AdSummary.tsx @@ -1,8 +1,10 @@ +import { useEffect, useRef } from 'react'; +import { TExchangeRate } from 'types'; import { AD_ACTION, RATE_TYPE } from '@/constants'; import { api } from '@/hooks'; -import { useExchangeRateSubscription } from '@/hooks/api/account'; import { useQueryString } from '@/hooks/custom-hooks'; import { percentOf, roundOffDecimal, setDecimalPlaces } from '@/utils'; +import { useExchangeRates } from '@deriv-com/api-hooks'; import { Text, useDevice } from '@deriv-com/ui'; import { FormatUtils } from '@deriv-com/utils'; @@ -29,7 +31,7 @@ const AdSummary = ({ const { queryString } = useQueryString(); const adOption = queryString.formAction; const { data: p2pSettings } = api.settings.useSettings(); - const { subscribeRates } = useExchangeRateSubscription(); + const { subscribeRates } = useExchangeRates(); const overrideExchangeRate = p2pSettings?.override_exchange_rate; const marketRateType = adOption === AD_ACTION.CREATE ? rateType : adRateType; @@ -39,9 +41,18 @@ const AdSummary = ({ let displayPriceRate: number | string = ''; let displayTotal = ''; - const exchangeRateValue = subscribeRates({ base_currency: 'USD', target_currencies: [localCurrency] }); + const exchangeRateRef = useRef(null); - const exchangeRate = exchangeRateValue?.rates?.[localCurrency]; + useEffect(() => { + if (localCurrency) { + exchangeRateRef.current = subscribeRates({ + base_currency: currency, + target_currencies: [localCurrency], + }); + } + }, [localCurrency]); + + const exchangeRate = exchangeRateRef?.current?.rates?.[localCurrency]; const marketRate = overrideExchangeRate ? Number(overrideExchangeRate) : exchangeRate; const marketFeed = marketRateType === RATE_TYPE.FLOAT ? marketRate : null; const summaryTextSize = isMobile ? 'md' : 'sm'; diff --git a/src/pages/my-ads/components/AdTypeSection/__tests__/AdTypeSection.spec.tsx b/src/pages/my-ads/components/AdTypeSection/__tests__/AdTypeSection.spec.tsx index cbacdd46..4082f728 100644 --- a/src/pages/my-ads/components/AdTypeSection/__tests__/AdTypeSection.spec.tsx +++ b/src/pages/my-ads/components/AdTypeSection/__tests__/AdTypeSection.spec.tsx @@ -10,7 +10,17 @@ const mockSetFieldValue = jest.fn(); const mockTriggerFunction = jest.fn(); jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), - Controller: ({ control, defaultValue, name, render }) => + Controller: ({ + control, + defaultValue, + name, + render, + }: { + control: string; + defaultValue: object; + name: string; + render: (param: object) => void; + }) => render({ field: { control, name, onBlur: jest.fn(), onChange: jest.fn(), value: defaultValue }, fieldState: { error: null }, diff --git a/src/pages/my-ads/components/OrderTimeSelection/OrderTimeSelection.tsx b/src/pages/my-ads/components/OrderTimeSelection/OrderTimeSelection.tsx index 3313e062..2b79fb2d 100644 --- a/src/pages/my-ads/components/OrderTimeSelection/OrderTimeSelection.tsx +++ b/src/pages/my-ads/components/OrderTimeSelection/OrderTimeSelection.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; +import { MutableOption } from 'types'; import { OrderTimeTooltipModal } from '@/components/Modals'; import { ORDER_COMPLETION_TIME_LIST, ORDER_TIME_INFO_MESSAGE } from '@/constants'; import { LabelPairedChevronDownMdRegularIcon, LabelPairedCircleInfoCaptionRegularIcon } from '@deriv/quill-icons'; @@ -40,7 +41,7 @@ const OrderTimeSelection = () => { } - list={ORDER_COMPLETION_TIME_LIST} + list={ORDER_COMPLETION_TIME_LIST as unknown as MutableOption[]} name='order-completion-time' onSelect={onChange} value={value} diff --git a/src/pages/my-ads/components/OrderTimeSelection/__tests__/OrderTimeSelection.spec.tsx b/src/pages/my-ads/components/OrderTimeSelection/__tests__/OrderTimeSelection.spec.tsx index 8fe9ecc9..2c654726 100644 --- a/src/pages/my-ads/components/OrderTimeSelection/__tests__/OrderTimeSelection.spec.tsx +++ b/src/pages/my-ads/components/OrderTimeSelection/__tests__/OrderTimeSelection.spec.tsx @@ -12,7 +12,7 @@ const mockUseDevice = useDevice as jest.Mock; jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), - Controller: ({ defaultValue, render }) => + Controller: ({ defaultValue, render }: { defaultValue: object; render: (param: object) => void }) => render({ field: { onChange: jest.fn(), value: defaultValue }, fieldState: { error: null }, diff --git a/src/pages/my-ads/screens/CreateEditAd/__tests__/CreateEditAd.spec.tsx b/src/pages/my-ads/screens/CreateEditAd/__tests__/CreateEditAd.spec.tsx index 1a3e5fb1..d3746a79 100644 --- a/src/pages/my-ads/screens/CreateEditAd/__tests__/CreateEditAd.spec.tsx +++ b/src/pages/my-ads/screens/CreateEditAd/__tests__/CreateEditAd.spec.tsx @@ -1,3 +1,4 @@ +import { ReactNode } from 'react'; import { MY_ADS_URL } from '@/constants'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -7,8 +8,18 @@ import '../../../components/AdFormInput'; const mockOnChange = jest.fn(); jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), - FormProvider: ({ children }) =>
{children}
, - Controller: ({ control, defaultValue, name, render }) => + FormProvider: ({ children }: { children: ReactNode }) =>
{children}
, + Controller: ({ + control, + defaultValue, + name, + render, + }: { + control: string; + defaultValue: object; + name: string; + render: (param: object) => void; + }) => render({ field: { control, name, onBlur: jest.fn(), onChange: mockOnChange, value: defaultValue }, fieldState: { error: null }, diff --git a/src/pages/my-ads/screens/MyAds/MyAdsTableRow/MyAdsTableRow.tsx b/src/pages/my-ads/screens/MyAds/MyAdsTableRow/MyAdsTableRow.tsx index 4efe5f65..2bfcc4e9 100644 --- a/src/pages/my-ads/screens/MyAds/MyAdsTableRow/MyAdsTableRow.tsx +++ b/src/pages/my-ads/screens/MyAds/MyAdsTableRow/MyAdsTableRow.tsx @@ -1,10 +1,11 @@ -import { memo, useEffect, useState } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import clsx from 'clsx'; +import { TCurrency, TExchangeRate } from 'types'; import { PaymentMethodLabel, PopoverDropdown } from '@/components'; import { AD_ACTION, ADVERT_TYPE, RATE_TYPE } from '@/constants'; -import { useExchangeRateSubscription } from '@/hooks/api/account'; import { useFloatingRate } from '@/hooks/custom-hooks'; import { generateEffectiveRate, shouldShowTooltipIcon } from '@/utils'; +import { useExchangeRates } from '@deriv-com/api-hooks'; import { Text, useDevice } from '@deriv-com/ui'; import { FormatUtils } from '@deriv-com/utils'; import { AdStatus, AdType, AlertComponent, ProgressIndicator } from '../../../components'; @@ -32,7 +33,7 @@ type TMyAdsTableProps = Omit { const { isMobile } = useDevice(); - const { subscribeRates } = useExchangeRateSubscription(); + const { subscribeRates } = useExchangeRates(); const { account_currency: accountCurrency, @@ -59,7 +60,16 @@ const MyAdsTableRow = ({ currentRateType, showModal, ...rest }: TMyAdsTableProps const isFloatingRate = rateType === RATE_TYPE.FLOAT; - const exchangeRateValue = subscribeRates({ base_currency: BASE_CURRENCY, target_currencies: [localCurrency] }); + const exchangeRateRef = useRef(null); + + useEffect(() => { + if (localCurrency) { + exchangeRateRef.current = subscribeRates({ + base_currency: BASE_CURRENCY, + target_currencies: [localCurrency], + }); + } + }, [localCurrency]); const [showAlertIcon, setShowAlertIcon] = useState(false); const isAdvertListed = isListed && !isBarred; @@ -69,7 +79,7 @@ const MyAdsTableRow = ({ currentRateType, showModal, ...rest }: TMyAdsTableProps const isRowDisabled = !isActive || isBarred || !isListed; const isAdActive = !!isActive && !isBarred; - const exchangeRate = exchangeRateValue?.rates?.[localCurrency ?? '']; + const exchangeRate = exchangeRateRef.current?.rates?.[localCurrency ?? '']; const enableActionPoint = currentRateType !== rateType; useEffect(() => { @@ -78,7 +88,7 @@ const MyAdsTableRow = ({ currentRateType, showModal, ...rest }: TMyAdsTableProps const { displayEffectiveRate } = generateEffectiveRate({ exchangeRate, - localCurrency, + localCurrency: localCurrency as TCurrency, marketRate: Number(effectiveRate), price: Number(priceDisplay), rate: Number(rateDisplay), diff --git a/src/pages/my-ads/screens/MyAds/MyAdsTableRow/__tests__/MyAdsTableRow.spec.tsx b/src/pages/my-ads/screens/MyAds/MyAdsTableRow/__tests__/MyAdsTableRow.spec.tsx index 8a08ab1e..5c8a5b82 100644 --- a/src/pages/my-ads/screens/MyAds/MyAdsTableRow/__tests__/MyAdsTableRow.spec.tsx +++ b/src/pages/my-ads/screens/MyAds/MyAdsTableRow/__tests__/MyAdsTableRow.spec.tsx @@ -1,4 +1,4 @@ -import { useExchangeRateSubscription } from '@/hooks/api/account'; +import { useExchangeRates } from '@deriv-com/api-hooks'; import { useDevice } from '@deriv-com/ui'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -70,8 +70,8 @@ const mockProps = { visibility_status: [], }; -jest.mock('@deriv/api-v2', () => ({ - useExchangeRateSubscription: jest.fn(), +jest.mock('@deriv-com/api-hooks', () => ({ + useExchangeRates: jest.fn(), })); jest.mock('@deriv-com/ui', () => ({ @@ -79,7 +79,7 @@ jest.mock('@deriv-com/ui', () => ({ useDevice: jest.fn().mockReturnValue({ isMobile: false }), })); -const mockUseExchangeRate = useExchangeRateSubscription as jest.Mock; +const mockUseExchangeRate = useExchangeRates as jest.Mock; describe('MyAdsTableRow', () => { beforeEach(() => { diff --git a/src/pages/my-profile/screens/MyProfile/MyProfile.tsx b/src/pages/my-profile/screens/MyProfile/MyProfile.tsx index 2a3209c9..b3033aaf 100644 --- a/src/pages/my-profile/screens/MyProfile/MyProfile.tsx +++ b/src/pages/my-profile/screens/MyProfile/MyProfile.tsx @@ -1,7 +1,13 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { ProfileContent, Verification } from '@/components'; import { NicknameModal } from '@/components/Modals'; -import { useAdvertiserStats, useIsAdvertiser, usePoiPoaStatus, useQueryString } from '@/hooks/custom-hooks'; +import { + useAdvertiserStats, + useIsAdvertiser, + useModalManager, + usePoiPoaStatus, + useQueryString, +} from '@/hooks/custom-hooks'; import { Loader, Tab, Tabs, useDevice } from '@deriv-com/ui'; import { MyProfileAdDetails } from '../MyProfileAdDetails'; import { MyProfileCounterparties } from '../MyProfileCounterparties'; @@ -19,7 +25,7 @@ const MyProfile = () => { const { data: advertiserStats, isLoading } = useAdvertiserStats(); const { isP2PPoaRequired, isPoaVerified, isPoiVerified } = data || {}; const isAdvertiser = useIsAdvertiser(); - const [isNicknameModalOpen, setIsNicknameModalOpen] = useState(false); + const { hideModal, isModalOpenFor, showModal } = useModalManager({ shouldReinitializeModals: false }); const currentTab = queryString.tab; @@ -32,7 +38,7 @@ const MyProfile = () => { useEffect(() => { const isPoaPoiVerified = (!isP2PPoaRequired || isPoaVerified) && isPoiVerified; - if (isPoaPoiVerified && !isAdvertiser) setIsNicknameModalOpen(true); + if (isPoaPoiVerified && !isAdvertiser) showModal('NicknameModal'); }, [isAdvertiser, isP2PPoaRequired, isPoaVerified, isPoiVerified]); if (isLoading && !advertiserStats) { @@ -47,7 +53,7 @@ const MyProfile = () => { return (
- +
); } @@ -71,7 +77,7 @@ const MyProfile = () => { ))} - + ); }; diff --git a/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.tsx b/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.tsx index b8ccd5d1..fe364d7a 100644 --- a/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.tsx +++ b/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.tsx @@ -20,14 +20,14 @@ const MyProfileAdDetailsTextArea = ({ return ( <>