diff --git a/packages/appstore/src/components/banners/business-closure-banner/__tests__/business-closure-banner.spec.tsx b/packages/appstore/src/components/banners/business-closure-banner/__tests__/business-closure-banner.spec.tsx new file mode 100644 index 000000000000..cc025a2fd2b4 --- /dev/null +++ b/packages/appstore/src/components/banners/business-closure-banner/__tests__/business-closure-banner.spec.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import BusinessClosureBanner from '../business-closure-banner'; + +describe('', () => { + const mockDefault = mockStore({ client: { is_account_to_be_closed_by_residence: false } }); + + const wrapper = (mock: ReturnType = mockDefault) => { + const Component = ({ children }: { children: JSX.Element }) => ( + {children} + ); + return Component; + }; + + it('should render nothing by default', () => { + render(, { + wrapper: wrapper(), + }); + + expect(screen.queryByText(/Due to business changes/)).not.toBeInTheDocument(); + }); + + it('should render banner content if is_account_to_be_closed_by_residence === true ', () => { + const mock = mockStore({ client: { is_account_to_be_closed_by_residence: true } }); + + render(, { + wrapper: wrapper(mock), + }); + + expect(screen.queryByText(/Due to business changes/)).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.scss b/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.scss new file mode 100644 index 000000000000..5775fc55e842 --- /dev/null +++ b/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.scss @@ -0,0 +1,14 @@ +.business-closure-banner { + padding-inline: 1.6rem; + // One more solution just change the color and make it without opacity instead of height calc (background-color: #fff4e4;) + + @include desktop-screen { + position: sticky; + top: 0; + z-index: 2; + + & + div { + height: calc(100vh - $HEADER_HEIGHT - $FOOTER_HEIGHT - 3.4rem); + } + } +} diff --git a/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.tsx b/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.tsx new file mode 100644 index 000000000000..b404c3ddb221 --- /dev/null +++ b/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { InlineMessage, Text } from '@deriv-com/ui'; +import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import { formatDate } from '@deriv/shared'; +import './business-closure-banner.scss'; + +const BusinessClosureBanner = observer(() => { + const { client } = useStore(); + const { is_account_to_be_closed_by_residence, account_time_of_closure } = client; + + if (!is_account_to_be_closed_by_residence) return null; + + return ( + + + + + + ); +}); + +export default BusinessClosureBanner; diff --git a/packages/appstore/src/components/banners/business-closure-banner/index.ts b/packages/appstore/src/components/banners/business-closure-banner/index.ts new file mode 100644 index 000000000000..aec0d25a54cd --- /dev/null +++ b/packages/appstore/src/components/banners/business-closure-banner/index.ts @@ -0,0 +1,3 @@ +import BusinessClosureBanner from './business-closure-banner'; + +export default BusinessClosureBanner; diff --git a/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx b/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx index db139d8034c6..29086eaa7237 100644 --- a/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx +++ b/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx @@ -9,6 +9,7 @@ jest.mock('Components/cfds-listing', () => jest.fn(() => 'mockedCFDsListing')); jest.mock('Components/options-multipliers-listing', () => jest.fn(() => 'mocked')); jest.mock('@deriv-com/ui', () => ({ + ...jest.requireActual('@deriv-com/ui'), useDevice: jest.fn(() => ({ isDesktop: true, isMobile: false, diff --git a/packages/appstore/src/modules/traders-hub/index.tsx b/packages/appstore/src/modules/traders-hub/index.tsx index 87c531348e83..ef622c8518e5 100644 --- a/packages/appstore/src/modules/traders-hub/index.tsx +++ b/packages/appstore/src/modules/traders-hub/index.tsx @@ -11,6 +11,7 @@ import OptionsAndMultipliersListing from 'Components/options-multipliers-listing import ButtonToggleLoader from 'Components/pre-loader/button-toggle-loader'; import AfterSignupFlow from 'Components/after-signup-flow'; import Disclaimer from 'Components/disclaimer'; +import BusinessClosureBanner from 'Components/banners/business-closure-banner'; import { useContentFlag, useGrowthbookGetFeatureValue } from '@deriv/hooks'; import classNames from 'classnames'; import './traders-hub.scss'; @@ -152,6 +153,7 @@ const TradersHub = observer(() => { return ( + {can_show_notify && }
{ const no_residence = cashier_validation?.includes('no_residence'); const unwelcome_status = cashier_validation?.includes('unwelcome_status'); @@ -52,6 +56,21 @@ const getMessage = ({ const pa_commision_withdrawal_limit = cashier_validation?.includes('PACommisionWithdrawalLimit'); const pathname = history.location.pathname; + if (is_account_to_be_closed_by_residence && pathname === routes.cashier_deposit) { + return { + icon: 'IcCashierNoBalance', + title: localize('Deposits disabled'), + description: ( + + ), + }; + } + if (is_system_maintenance) { if (is_crypto && is_withdrawal_locked) return { diff --git a/packages/cashier/src/components/cashier-locked/cashier-locked.tsx b/packages/cashier/src/components/cashier-locked/cashier-locked.tsx index 9de83a7115d0..40cfc8e49d31 100644 --- a/packages/cashier/src/components/cashier-locked/cashier-locked.tsx +++ b/packages/cashier/src/components/cashier-locked/cashier-locked.tsx @@ -20,6 +20,8 @@ const CashierLocked = observer(() => { is_withdrawal_lock: is_withdrawal_locked, loginid, is_identity_verification_needed, + is_account_to_be_closed_by_residence, + account_time_of_closure, } = client; const mf_account_status = useMFAccountStatus(); const is_cashier_locked = useCashierLocked(); @@ -40,6 +42,8 @@ const CashierLocked = observer(() => { is_withdrawal_locked, is_identity_verification_needed, is_pending_verification: mf_account_status === MT5_ACCOUNT_STATUS.PENDING, + is_account_to_be_closed_by_residence, + account_time_of_closure, }); return ( diff --git a/packages/core/src/App/Containers/app-notification-messages.jsx b/packages/core/src/App/Containers/app-notification-messages.jsx index 4f9603da1693..ee8862778273 100644 --- a/packages/core/src/App/Containers/app-notification-messages.jsx +++ b/packages/core/src/App/Containers/app-notification-messages.jsx @@ -129,6 +129,7 @@ const AppNotificationMessages = observer( 'trustpilot', 'unwelcome', 'additional_kyc_info', + 'notify_account_is_to_be_closed_by_residence', ].includes(message.key) || message.type === 'p2p_completed_order' : true; diff --git a/packages/core/src/Stores/client-store.js b/packages/core/src/Stores/client-store.js index 8385afcfc677..cedde01b6053 100644 --- a/packages/core/src/Stores/client-store.js +++ b/packages/core/src/Stores/client-store.js @@ -421,6 +421,8 @@ export default class ClientStore extends BaseStore { is_mf_account: computed, is_tradershub_tracking: observable, setTradersHubTracking: action.bound, + account_time_of_closure: computed, + is_account_to_be_closed_by_residence: computed, }); reaction( @@ -2881,4 +2883,14 @@ export default class ClientStore extends BaseStore { get is_mf_account() { return this.loginid?.startsWith('MF'); } + + get account_time_of_closure() { + return this.account_status?.account_closure?.find( + item => item?.status_codes?.includes('residence_closure') && item?.type === 'residence' + )?.time_of_closure; + } + + get is_account_to_be_closed_by_residence() { + return this.account_time_of_closure && this.residence && this.residence === 'sn'; + } } diff --git a/packages/core/src/Stores/notification-store.js b/packages/core/src/Stores/notification-store.js index 07840441f8eb..7652dd65d7a9 100644 --- a/packages/core/src/Stores/notification-store.js +++ b/packages/core/src/Stores/notification-store.js @@ -416,6 +416,14 @@ export default class NotificationStore extends BaseStore { this.removeNotificationByKey({ key: this.client_notifications.enable_passkey }); } + if (this.root_store.client.is_account_to_be_closed_by_residence) { + this.addNotificationMessage(this.client_notifications.notify_account_is_to_be_closed_by_residence); + } else { + this.removeNotificationByKey({ + key: this.client_notifications.notify_account_is_to_be_closed_by_residence, + }); + } + const client = accounts[loginid]; if (client && !client.is_virtual) { if (isEmptyObject(account_status)) return; @@ -1541,6 +1549,24 @@ export default class NotificationStore extends BaseStore { }, type: 'warning', }, + notify_account_is_to_be_closed_by_residence: { + action: { + route: routes.cashier_withdrawal, + text: localize('Withdraw funds'), + }, + header: localize('Deposits and trading disabled'), + key: 'notify_account_is_to_be_closed_by_residence', + message: ( + + ), + should_show_again: true, + type: 'warning', + }, }; this.client_notifications = notifications; diff --git a/packages/core/src/Stores/ui-store.js b/packages/core/src/Stores/ui-store.js index 2dd38a2a5441..122f41d2223f 100644 --- a/packages/core/src/Stores/ui-store.js +++ b/packages/core/src/Stores/ui-store.js @@ -46,6 +46,7 @@ export default class UIStore extends BaseStore { is_update_email_modal_visible = false; is_reset_trading_password_modal_visible = false; is_mf_verification_pending_modal_visible = false; + is_trading_disabled_by_residence_modal_visible = false; // @observable is_purchase_lock_on = false; // SmartCharts Controls // TODO: enable asset information @@ -267,6 +268,7 @@ export default class UIStore extends BaseStore { is_landscape: observable, is_language_settings_modal_on: observable, is_mf_verification_pending_modal_visible: observable, + is_trading_disabled_by_residence_modal_visible: observable, is_mobile_language_menu_open: observable, is_nativepicker_visible: observable, @@ -397,6 +399,7 @@ export default class UIStore extends BaseStore { setSubSectionIndex: action.bound, setTopUpInProgress: action.bound, setIsMFVericationPendingModal: action.bound, + setIsTradingDisabledByResidenceModal: action.bound, setMT5MigrationModalEnabled: action.bound, setMobileLanguageMenuOpen: action.bound, toggleAccountsDialog: action.bound, @@ -956,6 +959,10 @@ export default class UIStore extends BaseStore { this.is_mf_verification_pending_modal_visible = value; } + setIsTradingDisabledByResidenceModal(value) { + this.is_trading_disabled_by_residence_modal_visible = value; + } + toggleAdditionalKycInfoModal() { this.is_additional_kyc_info_modal_open = !this.is_additional_kyc_info_modal_open; } diff --git a/packages/hooks/src/useDepositLocked.ts b/packages/hooks/src/useDepositLocked.ts index 3e6c39ae9610..1cd1148edd9b 100644 --- a/packages/hooks/src/useDepositLocked.ts +++ b/packages/hooks/src/useDepositLocked.ts @@ -5,7 +5,12 @@ import useNeedTNC from './useNeedTNC'; const useDepositLocked = () => { const { client } = useStore(); - const { is_deposit_lock, is_trading_experience_incomplete, landing_company_shortcode } = client; + const { + is_deposit_lock, + is_trading_experience_incomplete, + landing_company_shortcode, + is_account_to_be_closed_by_residence, + } = client; const is_need_authentication = useNeedAuthentication(); const is_need_tnc = useNeedTNC(); const is_need_financial_assessment = useNeedFinancialAssessment(); @@ -18,7 +23,8 @@ const useDepositLocked = () => { is_deposit_lock || is_need_authentication || is_need_tnc || - is_trading_experience_incomplete_or_need_financial_assessment; + is_trading_experience_incomplete_or_need_financial_assessment || + is_account_to_be_closed_by_residence; return is_deposit_locked; }; diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index e309fe27c29e..fdc66ab43bdf 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -308,6 +308,8 @@ const mock = (): TStores & { is_mock: boolean } => { is_mf_account: false, is_tradershub_tracking: false, setTradersHubTracking: jest.fn(), + account_time_of_closure: undefined, + is_account_to_be_closed_by_residence: false, }, common: { error: common_store_error, @@ -481,6 +483,8 @@ const mock = (): TStores & { is_mock: boolean } => { setShouldShowDepositNowOrLaterModal: jest.fn(), should_show_crypto_transaction_processing_modal: false, setShouldShowCryptoTransactionProcessingModal: jest.fn(), + is_trading_disabled_by_residence_modal_visible: false, + setIsTradingDisabledByResidenceModal: jest.fn(), }, traders_hub: { getAccount: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index b6d2239f58f5..631c2a1dcd1c 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -618,6 +618,8 @@ type TClientStore = { is_cr_account: boolean; is_mf_account: boolean; setTradersHubTracking: (value: boolean) => void; + account_time_of_closure?: number; + is_account_to_be_closed_by_residence: boolean; }; type TCommonStoreError = { @@ -826,6 +828,8 @@ type TUiStore = { setShouldShowDepositNowOrLaterModal: (value: boolean) => void; should_show_crypto_transaction_processing_modal: boolean; setShouldShowCryptoTransactionProcessingModal: (value: boolean) => void; + is_trading_disabled_by_residence_modal_visible: boolean; + setIsTradingDisabledByResidenceModal: (value: boolean) => void; }; type TPortfolioStore = { diff --git a/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/__tests__/trading-disabled-by-residence-modal-content.spec.tsx b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/__tests__/trading-disabled-by-residence-modal-content.spec.tsx new file mode 100644 index 000000000000..a0203d76f097 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/__tests__/trading-disabled-by-residence-modal-content.spec.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { TradingDisabledByResidenceModalContent } from '../trading-disabled-by-residence-modal-content'; + +describe('', () => { + const mockDefault = mockStore({}); + + const wrapper = (mock: ReturnType = mockDefault) => { + const Component = ({ children }: { children: JSX.Element }) => ( + {children} + ); + return Component; + }; + + it('should render modal content with correct title', () => { + render(, { + wrapper: wrapper(), + }); + + expect(screen.getByText(/Trading disabled/)).toBeInTheDocument(); + }); +}); diff --git a/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/__tests__/trading-disabled-by-residence-modal.spec.tsx b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/__tests__/trading-disabled-by-residence-modal.spec.tsx new file mode 100644 index 000000000000..bf1cfd96200c --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/__tests__/trading-disabled-by-residence-modal.spec.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { useDevice } from '@deriv-com/ui'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import TradingDisabledByResidenceModal from '../trading-disabled-by-residence-modal'; + +jest.mock('@deriv-com/ui', () => ({ + ...jest.requireActual('@deriv-com/ui'), + useDevice: jest.fn(() => ({ isMobile: false })), +})); + +jest.mock('../trading-disabled-by-residence-modal-content', () => ({ + __esModule: true, + default: () => undefined, + TradingDisabledByResidenceModalContent: () =>
Content
, +})); + +describe('', () => { + let modal_root_el: HTMLDivElement; + + const setIsTradingDisabledByResidenceModal = jest.fn(); + + const mockDefault = mockStore({ + ui: { + is_trading_disabled_by_residence_modal_visible: true, + setIsTradingDisabledByResidenceModal, + }, + }); + + const wrapper = (mock: ReturnType = mockDefault) => { + const Component = ({ children }: { children: JSX.Element }) => ( + {children} + ); + return Component; + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + beforeAll(() => { + modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root'); + modal_root_el.setAttribute('data-testid', 'dt_test_modal'); + document.body.appendChild(modal_root_el); + }); + + afterAll(() => { + document.body.removeChild(modal_root_el); + }); + + it('should render modal for desktop', () => { + render(, { + wrapper: wrapper(), + }); + + expect(screen.getByTestId('dt_test_modal')).toBeInTheDocument(); + expect(screen.getByText('Content')).toBeInTheDocument(); + }); + + it('should render modal for responsive', () => { + (useDevice as jest.Mock).mockReturnValueOnce({ isMobile: true }); + + render(, { + wrapper: wrapper(), + }); + + expect(screen.getByTestId('dt_test_modal')).toBeInTheDocument(); + expect(screen.getByText('Content')).toBeInTheDocument(); + }); + + it('should call setIsTradingDisabledByResidenceModal with false when try to close modal', () => { + render(, { + wrapper: wrapper(), + }); + + const close_button = screen.getByRole('button', { + name: '', + }); + userEvent.click(close_button); + + expect(setIsTradingDisabledByResidenceModal).toHaveBeenCalledWith(false); + }); +}); diff --git a/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/index.ts b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/index.ts new file mode 100644 index 000000000000..eedacba07c7a --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/index.ts @@ -0,0 +1,3 @@ +import TradingDisabledByResidenceModal from './trading-disabled-by-residence-modal'; + +export default TradingDisabledByResidenceModal; diff --git a/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal-content.tsx b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal-content.tsx new file mode 100644 index 000000000000..aab565006eac --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal-content.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Icon, Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { formatDate } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; + +export const TradingDisabledByResidenceModalContent = observer(() => { + const { client } = useStore(); + const { account_time_of_closure } = client; + + return ( +
+ + + + + + + +
+ ); +}); diff --git a/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal.scss b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal.scss new file mode 100644 index 000000000000..14d99da3ee42 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal.scss @@ -0,0 +1,14 @@ +.trading-disabled-by-residence-modal { + &__content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 2.4rem; + padding: 3.2rem; + + @include mobile-screen { + padding: 1.6rem; + } + } +} diff --git a/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal.tsx b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal.tsx new file mode 100644 index 000000000000..ba0ce0440bb3 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Modals/TradingDisabledByResidenceModal/trading-disabled-by-residence-modal.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { useDevice } from '@deriv-com/ui'; +import { MobileDialog, Modal } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import { TradingDisabledByResidenceModalContent } from './trading-disabled-by-residence-modal-content'; +import './trading-disabled-by-residence-modal.scss'; + +const TradingDisabledByResidenceModal = observer(() => { + const { isMobile } = useDevice(); + const { ui } = useStore(); + const { is_trading_disabled_by_residence_modal_visible, setIsTradingDisabledByResidenceModal } = ui; + + const onCloseModal = () => { + setIsTradingDisabledByResidenceModal(false); + }; + + return ( + + {isMobile ? ( + + + + ) : ( + + + + )} + + ); +}); + +export default TradingDisabledByResidenceModal; diff --git a/packages/trader/src/App/Containers/Modals/trade-modals.tsx b/packages/trader/src/App/Containers/Modals/trade-modals.tsx index 19739aec97d9..e7cc66cba220 100644 --- a/packages/trader/src/App/Containers/Modals/trade-modals.tsx +++ b/packages/trader/src/App/Containers/Modals/trade-modals.tsx @@ -3,6 +3,7 @@ import { getUrlSmartTrader } from '@deriv/shared'; import MarketUnavailableModal from 'App/Components/Elements/Modals/MarketUnavailableModal'; import ServicesErrorModal from 'App/Components/Elements/Modals/ServicesErrorModal'; import AccountVerificationPendingModal from 'App/Components/Elements/Modals/AccountVerificationPendingModal'; +import TradingDisabledByResidenceModal from 'App/Components/Elements/Modals/TradingDisabledByResidenceModal'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; @@ -57,6 +58,8 @@ const TradeModals = observer(() => { is_visible={is_mf_verification_pending_modal_visible} onConfirm={() => setIsMFVericationPendingModal(false)} /> + + ); }); diff --git a/packages/trader/src/Modules/Trading/Containers/purchase.tsx b/packages/trader/src/Modules/Trading/Containers/purchase.tsx index c14fb41b4ca3..6e4d6696ecb8 100644 --- a/packages/trader/src/Modules/Trading/Containers/purchase.tsx +++ b/packages/trader/src/Modules/Trading/Containers/purchase.tsx @@ -30,7 +30,13 @@ const getSortedIndex = (type: string, index: number) => { const Purchase = observer(({ is_market_closed }: { is_market_closed?: boolean }) => { const { portfolio: { all_positions, onClickSell }, - ui: { purchase_states: purchased_states_arr, is_mobile, setPurchaseState }, + ui: { + purchase_states: purchased_states_arr, + is_mobile, + setPurchaseState, + setIsTradingDisabledByResidenceModal, + }, + client: { is_account_to_be_closed_by_residence }, } = useStore(); const { basis, @@ -47,7 +53,7 @@ const Purchase = observer(({ is_market_closed }: { is_market_closed?: boolean }) is_vanilla_fx, is_vanilla, onHoverPurchase, - onPurchase: onClickPurchase, + onPurchase, proposal_info, purchase_info, symbol, @@ -82,6 +88,10 @@ const Purchase = observer(({ is_market_closed }: { is_market_closed?: boolean }) const components: JSX.Element[] = []; + const onClickPurchase = is_account_to_be_closed_by_residence + ? () => setIsTradingDisabledByResidenceModal(true) + : onPurchase; + Object.keys(trade_types).forEach((type, index) => { const info = proposal_info?.[type] || {}; const is_disabled = !is_trade_enabled || !info.id || !is_purchase_enabled;