diff --git a/__mocks__/LocalizeMock.js b/__mocks__/LocalizeMock.js index 1debd1ec..f037d7ad 100644 --- a/__mocks__/LocalizeMock.js +++ b/__mocks__/LocalizeMock.js @@ -7,7 +7,9 @@ const Localize = ({ i18n_default_text, values }) => { // Mock for useTranslations hook const useTranslations = () => ({ - localize: jest.fn(text => text), + localize: jest.fn((text, args) => { + return text.replace(/{{(.*?)}}/g, (_, match) => args[match.trim()]); + }), }); const localize = jest.fn(text => text); diff --git a/package-lock.json b/package-lock.json index e8fd2255..ed3cdfef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@babel/preset-env": "^7.24.5", "@deriv-com/api-hooks": "^0.1.19", - "@deriv-com/translations": "^1.2.3", + "@deriv-com/translations": "^1.2.4", "@deriv-com/ui": "^1.26.0", "@deriv-com/utils": "latest", "@deriv/deriv-api": "^1.0.15", @@ -2654,9 +2654,9 @@ } }, "node_modules/@deriv-com/translations": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@deriv-com/translations/-/translations-1.2.3.tgz", - "integrity": "sha512-NVZ2Ut8e+rG2eYjQhzI0nESmENx5U6wkaVoTZhAfFwswKJfTwDcOJYtmLPpUgAPUcgUNrWRx1iH0o5zGhLTOxg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@deriv-com/translations/-/translations-1.2.4.tgz", + "integrity": "sha512-LA6ZVNRhigN+wFQsfNzCCWdF0u4saud/AtnDDDlUo1pioKNckXoMv8RxvodiiSbnU4BvChaar/wvPeR3c+O3oA==", "dependencies": { "@xmldom/xmldom": "^0.8.10", "commander": "^12.0.0", @@ -22657,9 +22657,9 @@ "requires": {} }, "@deriv-com/translations": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@deriv-com/translations/-/translations-1.2.3.tgz", - "integrity": "sha512-NVZ2Ut8e+rG2eYjQhzI0nESmENx5U6wkaVoTZhAfFwswKJfTwDcOJYtmLPpUgAPUcgUNrWRx1iH0o5zGhLTOxg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@deriv-com/translations/-/translations-1.2.4.tgz", + "integrity": "sha512-LA6ZVNRhigN+wFQsfNzCCWdF0u4saud/AtnDDDlUo1pioKNckXoMv8RxvodiiSbnU4BvChaar/wvPeR3c+O3oA==", "requires": { "@rollup/rollup-linux-x64-gnu": "^4.17.1", "@xmldom/xmldom": "^0.8.10", diff --git a/package.json b/package.json index 797d2c34..b49028a3 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "@babel/preset-env": "^7.24.5", "@deriv-com/api-hooks": "^0.1.19", - "@deriv-com/translations": "^1.2.3", + "@deriv-com/translations": "^1.2.4", "@deriv-com/ui": "^1.26.0", "@deriv-com/utils": "latest", "@deriv/deriv-api": "^1.0.15", diff --git a/src/components/AdvertiserName/AdvertiserNameStats.scss b/src/components/AdvertiserName/AdvertiserNameStats.scss index b0e916ff..e146ddad 100644 --- a/src/components/AdvertiserName/AdvertiserNameStats.scss +++ b/src/components/AdvertiserName/AdvertiserNameStats.scss @@ -9,7 +9,7 @@ gap: 2rem; } - & div { + & div:not(.deriv-tooltip-container):not(.deriv-tooltip-container *) { display: flex; align-items: center; gap: 0.8rem; diff --git a/src/components/AdvertiserName/AdvertiserNameStats.tsx b/src/components/AdvertiserName/AdvertiserNameStats.tsx index d7be63e6..36ea5fe7 100644 --- a/src/components/AdvertiserName/AdvertiserNameStats.tsx +++ b/src/components/AdvertiserName/AdvertiserNameStats.tsx @@ -2,9 +2,10 @@ import clsx from 'clsx'; import { DeepPartial, TAdvertiserStats } from 'types'; import { OnlineStatusIcon, OnlineStatusLabel, StarRating } from '@/components'; import { getCurrentRoute } from '@/utils'; -import { LabelPairedCircleUserSlashSmRegularIcon, LabelPairedThumbsUpSmRegularIcon } from '@deriv/quill-icons'; +import { LabelPairedThumbsUpSmRegularIcon } from '@deriv/quill-icons'; import { Localize } from '@deriv-com/translations'; import { Text, useDevice } from '@deriv-com/ui'; +import BlockUserCount from './BlockUserCount'; import './AdvertiserNameStats.scss'; /** @@ -64,11 +65,7 @@ const AdvertiserNameStats = ({ advertiserStats }: { advertiserStats: DeepPartial <>
- {isMobile && ( - - ({ratingAverage}) - - )} + ({ratingAverage}) () @@ -83,14 +80,7 @@ const AdvertiserNameStats = ({ advertiserStats }: { advertiserStats: DeepPartial
)} - {isMyProfile && ( -
- - - {blockedByCount || 0} - -
- )} + {isMyProfile && }
); }; diff --git a/src/components/AdvertiserName/BlockUserCount.scss b/src/components/AdvertiserName/BlockUserCount.scss new file mode 100644 index 00000000..a2bc46e2 --- /dev/null +++ b/src/components/AdvertiserName/BlockUserCount.scss @@ -0,0 +1,19 @@ +.block-user-count { + & .deriv-tooltip-container { + display: flex; + align-items: center; + gap: 0.1rem; + } + &__tooltip { + padding: 0.8rem; + gap: 0; + + @include mobile { + display: none; + } + } + + &__button { + padding: 0; + } +} diff --git a/src/components/AdvertiserName/BlockUserCount.tsx b/src/components/AdvertiserName/BlockUserCount.tsx new file mode 100644 index 00000000..c79a364d --- /dev/null +++ b/src/components/AdvertiserName/BlockUserCount.tsx @@ -0,0 +1,60 @@ +import { TLocalize } from 'types'; +import { useModalManager } from '@/hooks'; +import { LabelPairedCircleUserSlashSmRegularIcon } from '@deriv/quill-icons'; +import { useTranslations } from '@deriv-com/translations'; +import { Button, Text, Tooltip, useDevice } from '@deriv-com/ui'; +import { BlockUserCountModal } from '../Modals'; +import './BlockUserCount.scss'; + +type TBlockUserCount = { + count?: number; +}; + +const getMessage = (localize: TLocalize, count = 0) => { + switch (count) { + case 0: + return localize('Nobody has blocked you. Yay!'); + case 1: + return localize('{{count}} person has blocked you', { + count, + }); + default: + return localize('{{count}} people have blocked you', { + count, + }); + } +}; + +const BlockUserCount = ({ count }: TBlockUserCount) => { + const { localize } = useTranslations(); + const { hideModal, isModalOpenFor, showModal } = useModalManager(); + const { isMobile } = useDevice(); + return ( +
+ {getMessage(localize, count)}} + > + + + {count ?? 0} + + + {!!isModalOpenFor('BlockUserCountModal') && ( + + )} +
+ ); +}; + +export default BlockUserCount; diff --git a/src/components/AdvertiserName/__tests__/BlockUserCount.spec.tsx b/src/components/AdvertiserName/__tests__/BlockUserCount.spec.tsx new file mode 100644 index 00000000..bcead7b5 --- /dev/null +++ b/src/components/AdvertiserName/__tests__/BlockUserCount.spec.tsx @@ -0,0 +1,50 @@ +import { useDevice } from '@deriv-com/ui'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import BlockUserCount from '../BlockUserCount'; + +const modalManager = { + hideModal: jest.fn(), + isModalOpenFor: jest.fn(), + showModal: jest.fn(), +}; +modalManager.showModal.mockImplementation(() => { + modalManager.isModalOpenFor.mockReturnValue(true); +}); + +jest.mock('@/hooks', () => ({ + ...jest.requireActual('@/hooks'), + useModalManager: jest.fn(() => modalManager), +})); + +jest.mock('@deriv-com/ui', () => ({ + ...jest.requireActual('@deriv-com/ui'), + useDevice: jest.fn(() => ({ isMobile: false })), +})); + +describe('BlockUserCount', () => { + it('should render the component as expected', () => { + render(); + expect(screen.getByText('0')).toBeInTheDocument(); + expect(screen.getByTestId('dt_block_user_count_button')).toBeInTheDocument(); + }); + it('should display the tooltip message on hovering', async () => { + render(); + const button = screen.getByTestId('dt_block_user_count_button'); + await userEvent.hover(button); + expect(screen.getByText('Nobody has blocked you. Yay!')).toBeInTheDocument(); + }); + it('should display the count in the tooltip message', async () => { + render(); + const button = screen.getByTestId('dt_block_user_count_button'); + await userEvent.hover(button); + expect(screen.getByText('1 person has blocked you')).toBeInTheDocument(); + }); + it('should open the modal on clicking the button in mobile view', async () => { + (useDevice as jest.Mock).mockReturnValue({ isMobile: true }); + render(); + await userEvent.click(screen.getByTestId('dt_block_user_count_button')); + expect(modalManager.showModal).toHaveBeenCalledWith('BlockUserCountModal'); + expect(modalManager.isModalOpenFor()).toBe(true); + }); +}); diff --git a/src/components/AppFooter/AppFooter.tsx b/src/components/AppFooter/AppFooter.tsx index 147d7288..df92dfe2 100644 --- a/src/components/AppFooter/AppFooter.tsx +++ b/src/components/AppFooter/AppFooter.tsx @@ -17,7 +17,7 @@ import WhatsApp from './WhatsApp'; import './AppFooter.scss'; const AppFooter = () => { - const { currentLang, localize, switchLanguage } = useTranslations(); + const { currentLang = 'EN', localize, switchLanguage } = useTranslations(); const { hideModal, isModalOpenFor, showModal } = useModalManager(); const openLanguageSettingModal = () => showModal('DesktopLanguagesModal'); diff --git a/src/components/Modals/BlockUserCountModal/BlockUserCountModal.scss b/src/components/Modals/BlockUserCountModal/BlockUserCountModal.scss new file mode 100644 index 00000000..99a996fa --- /dev/null +++ b/src/components/Modals/BlockUserCountModal/BlockUserCountModal.scss @@ -0,0 +1,3 @@ +.block-user-count-modal { + @include default-modal; +} diff --git a/src/components/Modals/BlockUserCountModal/BlockUserCountModal.tsx b/src/components/Modals/BlockUserCountModal/BlockUserCountModal.tsx new file mode 100644 index 00000000..3056704b --- /dev/null +++ b/src/components/Modals/BlockUserCountModal/BlockUserCountModal.tsx @@ -0,0 +1,32 @@ +import { Localize } from '@deriv-com/translations'; +import { Button, Modal, Text } from '@deriv-com/ui'; +import './BlockUserCountModal.scss'; + +type TBlockUserCountModalProps = { + isModalOpen: boolean; + message: string; + onRequestClose: () => void; +}; + +const BlockUserCountModal = ({ isModalOpen, message, onRequestClose }: TBlockUserCountModalProps) => { + return ( + + + {message} + + + + + + ); +}; + +export default BlockUserCountModal; diff --git a/src/components/Modals/BlockUserCountModal/__tests__/BlockUserCountModal.spec.tsx b/src/components/Modals/BlockUserCountModal/__tests__/BlockUserCountModal.spec.tsx new file mode 100644 index 00000000..56100a4e --- /dev/null +++ b/src/components/Modals/BlockUserCountModal/__tests__/BlockUserCountModal.spec.tsx @@ -0,0 +1,21 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import BlockUserCountModal from '../BlockUserCountModal'; + +const mockProps = { + isModalOpen: true, + message: 'test', + onRequestClose: jest.fn(), +}; + +describe('BlockUserCountModal', () => { + it('should render the modal component as expected', () => { + render(); + expect(screen.getByText('test')).toBeInTheDocument(); + }); + it('should handle the onclick event for the ok button', async () => { + render(); + await userEvent.click(screen.getByText('Ok')); + expect(mockProps.onRequestClose).toHaveBeenCalled(); + }); +}); diff --git a/src/components/Modals/BlockUserCountModal/index.ts b/src/components/Modals/BlockUserCountModal/index.ts new file mode 100644 index 00000000..cd216f53 --- /dev/null +++ b/src/components/Modals/BlockUserCountModal/index.ts @@ -0,0 +1 @@ +export { default as BlockUserCountModal } from './BlockUserCountModal'; diff --git a/src/components/Modals/index.ts b/src/components/Modals/index.ts index 1d239b27..3fb2f423 100644 --- a/src/components/Modals/index.ts +++ b/src/components/Modals/index.ts @@ -7,6 +7,7 @@ export * from './AdRateSwitchModal'; export * from './AdVisibilityErrorModal'; export * from './AvailableP2PBalanceModal'; export * from './BlockUnblockUserModal'; +export * from './BlockUserCountModal'; export * from './DailyLimitModal'; export * from './EmailLinkBlockedModal'; export * from './EmailLinkExpiredModal'; diff --git a/src/components/PaymentMethodCard/PaymentMethodCardHeader/PaymentMethodCardHeader.scss b/src/components/PaymentMethodCard/PaymentMethodCardHeader/PaymentMethodCardHeader.scss index dc4ba77a..9f1c5b37 100644 --- a/src/components/PaymentMethodCard/PaymentMethodCardHeader/PaymentMethodCardHeader.scss +++ b/src/components/PaymentMethodCard/PaymentMethodCardHeader/PaymentMethodCardHeader.scss @@ -12,6 +12,11 @@ .deriv-dropdown { width: 3.2rem; + & .deriv-dropdown__button { + transform: none; + transition: none; + } + &__items { width: 12.8rem; border: 0; diff --git a/src/components/PaymentMethodForm/PaymentMethodForm.scss b/src/components/PaymentMethodForm/PaymentMethodForm.scss index b84819b2..305e2adf 100644 --- a/src/components/PaymentMethodForm/PaymentMethodForm.scss +++ b/src/components/PaymentMethodForm/PaymentMethodForm.scss @@ -78,7 +78,7 @@ @include mobile { overflow: auto; - height: calc(100vh - 14rem); + height: calc(100vh - 12rem); padding-bottom: 8rem; } } @@ -112,6 +112,9 @@ } & .deriv-dropdown { + & .deriv-dropdown__items { + box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.24); + } & .deriv-input { height: 4rem; align-items: center; diff --git a/src/components/PaymentMethodForm/PaymentMethodFormModalRenderer/PaymentMethodFormModalRenderer.tsx b/src/components/PaymentMethodForm/PaymentMethodFormModalRenderer/PaymentMethodFormModalRenderer.tsx index aa8491dd..45f840ce 100644 --- a/src/components/PaymentMethodForm/PaymentMethodFormModalRenderer/PaymentMethodFormModalRenderer.tsx +++ b/src/components/PaymentMethodForm/PaymentMethodFormModalRenderer/PaymentMethodFormModalRenderer.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react'; import { TFormState, TSocketError } from 'types'; import { PaymentMethodErrorModal, PaymentMethodModal } from '@/components/Modals'; import { useModalManager } from '@/hooks'; +import { useTranslations } from '@deriv-com/translations'; type TPaymentMethodFormModalRendererProps = { actionType: TFormState['actionType']; @@ -22,6 +23,7 @@ const PaymentMethodFormModalRenderer = ({ setIsError, updateError, }: TPaymentMethodFormModalRendererProps) => { + const { localize } = useTranslations(); const { hideModal, isModalOpenFor, showModal } = useModalManager(); useEffect(() => { @@ -32,10 +34,10 @@ const PaymentMethodFormModalRenderer = ({ showModal('PaymentMethodModal'); } - // TODO: Remember to translate these strings if (createError || updateError) { showModal('PaymentMethodErrorModal'); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionType, createError, isCreateSuccessful, isUpdateSuccessful, updateError]); return ( @@ -49,15 +51,15 @@ const PaymentMethodFormModalRenderer = ({ setIsError(false); hideModal({ shouldHideAllModals: true }); }} - title='Something’s not right' + title={localize('Something’s not right')} /> )} {!!isModalOpenFor('PaymentMethodModal') && ( { @@ -69,9 +71,13 @@ const PaymentMethodFormModalRenderer = ({ hideModal(); setIsError(false); }} - primaryButtonLabel={actionType === 'ADD' ? 'Go back' : "Don't cancel"} + primaryButtonLabel={actionType === 'ADD' ? localize('Go back') : localize("Don't cancel")} secondaryButtonLabel='Cancel' - title={actionType === 'ADD' ? 'Cancel adding this payment method?' : 'Cancel your edits?'} + title={ + actionType === 'ADD' + ? localize('Cancel adding this payment method?') + : localize('Cancel your edits?') + } /> )} diff --git a/src/components/Search/Search.scss b/src/components/Search/Search.scss index 2f140442..c8a4470b 100644 --- a/src/components/Search/Search.scss +++ b/src/components/Search/Search.scss @@ -9,10 +9,14 @@ &__container { width: 100%; } - &__field { margin-left: 0.8rem; + // placeholder is hidden when input is focused, visibility hidden doesn't work with firefox. + &::-moz-placeholder { + color: transparent; + } + &:not(:placeholder-shown) ~ label, &:focus ~ label { display: none; diff --git a/src/pages/my-ads/screens/MyAdsEmpty/MyAdsEmpty.tsx b/src/pages/my-ads/screens/MyAdsEmpty/MyAdsEmpty.tsx index e7e5bbd4..d0958989 100644 --- a/src/pages/my-ads/screens/MyAdsEmpty/MyAdsEmpty.tsx +++ b/src/pages/my-ads/screens/MyAdsEmpty/MyAdsEmpty.tsx @@ -3,6 +3,7 @@ import { NicknameModal } from '@/components/Modals'; import { MY_ADS_URL } from '@/constants'; import { useIsAdvertiser, useModalManager } from '@/hooks/custom-hooks'; import { DerivLightIcCashierNoAdsIcon } from '@deriv/quill-icons'; +import { Localize } from '@deriv-com/translations'; import { ActionScreen, Button, Text, useDevice } from '@deriv-com/ui'; const MyAdsEmpty = () => { @@ -23,18 +24,18 @@ const MyAdsEmpty = () => { size='lg' textSize={isMobile ? 'md' : 'sm'} > - Create new ad + } description={ - Looking to buy or sell USD? You can post your own ad for others to respond. + } icon={} title={ - You have no ads 😞 + } /> diff --git a/src/pages/my-profile/screens/MyProfile/MyProfile.scss b/src/pages/my-profile/screens/MyProfile/MyProfile.scss index 297600ee..ab7d7e70 100644 --- a/src/pages/my-profile/screens/MyProfile/MyProfile.scss +++ b/src/pages/my-profile/screens/MyProfile/MyProfile.scss @@ -3,7 +3,7 @@ flex-direction: column; padding-top: 2.4rem; overflow-y: auto; - height: calc(100vh - 12rem); + height: calc(100vh - 14rem); @include mobile { padding: 0; diff --git a/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.scss b/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.scss index 563c38b9..75bf0688 100644 --- a/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.scss +++ b/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.scss @@ -16,6 +16,14 @@ &__mobile-wrapper { position: absolute; top: 4rem; - height: calc(100vh - 8rem); + height: calc(100vh - 4rem); + } + + & .deriv-textarea { + &__footer { + & .deriv-text { + font-size: 1.2rem; + } + } } } diff --git a/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.tsx b/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.tsx index da8c1c0c..6a511ba4 100644 --- a/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.tsx +++ b/src/pages/my-profile/screens/MyProfileAdDetails/MyProfileAdDetails.tsx @@ -20,6 +20,8 @@ const MyProfileAdDetailsTextArea = ({ setContactInfo, }: TMYProfileAdDetailsTextAreaProps) => { const { localize } = useTranslations(); + const { isMobile } = useDevice(); + const textSize = isMobile ? 'md' : 'sm'; return ( <>