From 6cd7b9decdc49386950e608cbc5eae1ee3b28ea3 Mon Sep 17 00:00:00 2001 From: Nijil Nirmal <62882794+nijil-binary@users.noreply.github.com> Date: Thu, 29 Sep 2022 06:58:21 +0400 Subject: [PATCH] P2P / Feature - Blocked advertiser (#6006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * create feature branch * Ameerul /Task 65488 Reusable modal to block advertiser (#5589) * changed the margin-top for mobile view for buy-sell * merging upstream master * merge with local * added new component (modal) for blocking/unblocking users and added a new observable in advertiser-page-store to view modal * ammended observable bool value to false and made props required for the modal * changed the ternary for each localized text to accomadate translations * added minor changes * changed the localize tags in button * fixed isRequired * removed observable is_block_user_modal_open, pass as prop to BlockUserModal instead * Update branch (#5680) * fixed translation for amount field in account transfer form cashier (#5605) Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * farrah/updated deposit store test coverage (#5430) * updated deposit store test coverage * added a check for updateAccountStatus Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * likhith/Fix validation message for payment methods (#5187) * Added appropriate error message * Fixed error messages in Edit form * Added validation for SWIFT * Added a hook to validate methods * Removed commented code * Refactored the code by moving the custom hook to hooks folder * Minor bug fixes * refactor: Renamed a variable * fix: added regex to allow more characters as valid * fix: implemented button disabled * fix: changed the edit payment method * fix: added payment error message overlapping issue Co-authored-by: Likhith Kolayari Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * Amina/poi_limited_test_case (#5642) * poi_limited_test_case * poi_limited_test_case Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * yauheni / IdvRejected test coverage (#5637) Co-authored-by: “yauheni-kryzhyk-deriv” <“yauheni@deriv.me”> Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * yauheni / IdvNoSubmisssions test coverage (#5636) * yauheni / IdvNoSubmisssions test coverage * imports fix * naming typo fix * Delete poi-idv-rejected.spec.js Co-authored-by: “yauheni-kryzhyk-deriv” <“yauheni@deriv.me”> Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * feat: add check if has restricted parent (#5635) Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * Adrienne / P2P responsive add PM full screen modal button footers are now fixed by design (#5561) * Mobile add PM full screen modal button footers are now fixed by design specs * Incorporated code review changes Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * Bala/Add bootstrap dev script (#5659) * chore: add bootstrap dev script * Update package.json Co-authored-by: Yashim Wong <75345074+yashim-deriv@users.noreply.github.com> * chore: remove build from bootstrap Co-authored-by: Yashim Wong <75345074+yashim-deriv@users.noreply.github.com> Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> * translations: 📚 sync translations with crowdin (#5679) Co-authored-by: DerivFE <80095553+DerivFE@users.noreply.github.com> * update branch Co-authored-by: vinu-deriv <100689171+vinu-deriv@users.noreply.github.com> Co-authored-by: Farrah Mae Ochoa <82315152+farrah-deriv@users.noreply.github.com> Co-authored-by: Likhith Kolayari <98398322+likhith-deriv@users.noreply.github.com> Co-authored-by: Likhith Kolayari Co-authored-by: amina-deriv <84661147+amina-deriv@users.noreply.github.com> Co-authored-by: yauheni-kryzhyk-deriv <103182683+yauheni-kryzhyk-deriv@users.noreply.github.com> Co-authored-by: “yauheni-kryzhyk-deriv” <“yauheni@deriv.me”> Co-authored-by: maryiafrantsava-binary <103181650+maryiafrantsava-binary@users.noreply.github.com> Co-authored-by: adrienne-deriv <103016120+adrienne-deriv@users.noreply.github.com> Co-authored-by: balakrishna-binary <56330681+balakrishna-binary@users.noreply.github.com> Co-authored-by: Yashim Wong <75345074+yashim-deriv@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: DerivFE <80095553+DerivFE@users.noreply.github.com> * Adrienne / Create blocked advertisers tab (#5590) * Added new blocked advertisers tab for desktop and mobile * Refactored code * Added blocked advertisers svg icon * Resolved failed build, added blocked-advertisers.svg icon * Incorporated code review suggestions * Incorporated code review suggestions * Show loading screen when icon is still loading * Removed loading * Removed manual font size * fix: block user folder and stuff (#5848) * Block unblock function draft (#5857) * Added block/unblock function to general-store * Added new observable * Refactored * Adrienne / Blocked user count icon with tooltip (#5684) * Created component for blocked user count * Added blocked user count component * Added blocked user icon * Linked blocked_by_count returned by BE * Incorporated code reviews * Added block user count modal for mobile when user taps on the block user count * Ensure blocked user icon count has correct fill in dark mode * Adrienne / Blocked advertiser overlay (#5859) * Added block user overlay component for desktop/responsive * Refacted sass file to use BEM convention and removed default * Refactored code review changes * Refactored code review changes * Refactored overlay component as a wrapper * Show unblock user modal and added unblock user action * changed icon name * Added checks for response * Incorporated code review changes * Fixed typo * Change overlay height for mobile to ensure it fills remaining space * Removed tabs in advertiser's page as per design * Fixed test issues * Made advertiser page non-scrollable when overlay is shown * Removed close icon from block user modal as discussed * Removed block me button * Removed setIsBlockedUserModalOpen from advertiser page store * Renamed props in block user overlay to onClickUnblock * Renamed overlay props * Added TODO regarding design changes on tabs in advertiser page * updated observables * added boolean checks * Ameerul /Task 65494 list of blocked users feature (#5858) * added 2 new components for row and table, added new scss file, added new functions and observables in my_profile and advertiser-page store * fixed loading issue, mobile full page modal view and the css issue for viewing the table * refactored code in advertiser store and block user modal * removed promise for getBlockedAdvertisersList function * merged blocked advertiser tab with list of users * added removed code * empty commit * refactored code * refactored blockUnblockUser function * separated the block user table to my-profile and refactored code * added blockUnblockUser function to general store * added new functions to general store * merged with master, added new functions in general store to block user table and to my profile store * changed page header for blocked advertisers list * Adrienne / Added search functionality for list of blocked advertisers (#6005) * Added search functionality * Refactored code changes * Refactored code changes * Renamed BlockedAdvertisersList to BlockUserList * Fixed issues with block advertiser list table height * Fixed issue with search box not loading * Fixed an issue where the profile header is not fully width * Reduced margin bottom height for tabs and stats height due to flex * Ameerul /Task #65487 Dropdown Block User Feature (#5682) * added dropdown, menu dots icon, added function for users to block advertisers with block user modal * refactored code, and renamed dropdown component to advertiser-page-dropdown-menu * renamed variables and functions according to comments * refactored block user function in advertiser-page-store and block-user-modal * minor changes to the imports * merged with master and added changes * removed old blockUser and showModal functions from advertiser store * removed is_blocked from dropdown * refactored code with comments * moved showBlockUserModal function to the store * removed curly brackets in name * fixed gap in modal * fixed tooltip message and user cannot block themselves * fixed dropdown darkmode design and hid menu dots icon when user is blocked * changed dropdown hover colour * added useOnClickOutisde Hook to hide dropdown when clicked outside * Refactored blocked user overlay css and advertiser page css (#6097) * Blocked user feature sanity check (#6434) * added dropdown, menu dots icon, added function for users to block advertisers with block user modal * refactored code, and renamed dropdown component to advertiser-page-dropdown-menu * renamed variables and functions according to comments * refactored block user function in advertiser-page-store and block-user-modal * minor changes to the imports * merged with master and added changes * removed old blockUser and showModal functions from advertiser store * removed is_blocked from dropdown * refactored code with comments * moved showBlockUserModal function to the store * removed curly brackets in name * fixed gap in modal * fixed tooltip message and user cannot block themselves * fixed dropdown darkmode design and hid menu dots icon when user is blocked * changed dropdown hover colour * added useOnClickOutisde Hook to hide dropdown when clicked outside * fixed bug fixes for advertiser page and my profile name * Adrienne / Blocked user feature search box bug (Merge after sanity check PR is merged!) (#6436) * added dropdown, menu dots icon, added function for users to block advertisers with block user modal * refactored code, and renamed dropdown component to advertiser-page-dropdown-menu * renamed variables and functions according to comments * refactored block user function in advertiser-page-store and block-user-modal * minor changes to the imports * merged with master and added changes * removed old blockUser and showModal functions from advertiser store * removed is_blocked from dropdown * refactored code with comments * moved showBlockUserModal function to the store * removed curly brackets in name * fixed gap in modal * fixed tooltip message and user cannot block themselves * fixed dropdown darkmode design and hid menu dots icon when user is blocked * changed dropdown hover colour * added useOnClickOutisde Hook to hide dropdown when clicked outside * fixed bug fixes for advertiser page and my profile name * Fixed an issue where the block user table does not display No blocked advertisers after unblocking the last user during search Co-authored-by: ameerul * Blocked user count not appearing in my profile (#6446) * fixed bugs for blocked user count in my profile * reverted changes from my profile and change my profile name * Aligned avatar to flex start in advertiser page as per design requirements (#6553) * Adrienne / Hide block user dropdown when user views their own advertiser page (#6529) * Hide block user dropdown when user views their own advertiser page * Removed dropdown when user views their own ad and also removes disabled dropdown prop functionality * Adrienne / Fixed alignment issues with block user count in responsive and bug where Blocked Advertisers responsive tab is not fully visible (#6527) * Fixed alignment issues with block user count in responsive and bug where Blocked Advertisers responsive tab is not fully visible * Integrated code review * Adrienne / Made block user count to be subscribed to updates (#6561) * Moved blocked user count observable to general store's subscription to subscribe to block user count updates * Destructure response * Refactored code * Ameerul /Bug 76978 Blocked advertiser list is not loading after barred one user and showing console error (#6565) * added error handling if user is barred and wants to get blocked advertisers list * updated ui for error message and added new component to handle errors in table * changed mobile margin for error message * added new icon for blocked advertisers barred * removed commented code * Adrienne / Removed hover styling for responsive in blocked advertisers list (#6567) * test * test * test agane * Added styling for unfocusing * Added styling for unfocusing * Removed hover styling for responsive * Ameerul /Bug 77176 The Block modal keep on looping for Disable P2P user (#6580) * added error-modal in advertiser page to handle if advertiser is banned and user wants to block * updated error modal has_close_icon and implemented comments * added has_close_icon in props * Ameerul /Bug 77339 When userA trying to block an advertiser is barred, once userA is unbarred on trying to block advertiser temporary barred error model is displayed (#6591) * fixed issue when user is banneed and tries to block advertiser, and changed width for error modal * fixed checking if user is barred and changed error modal styling * Adrienne / Scrollable advertiser page when no ads in responsive (#6622) * draft pr * removed top margin in advertiser page ads table Co-authored-by: Carol Sachdeva Co-authored-by: ameerul-deriv <103412909+ameerul-deriv@users.noreply.github.com> Co-authored-by: Carol Sachdeva <58209918+carol-binary@users.noreply.github.com> Co-authored-by: vinu-deriv <100689171+vinu-deriv@users.noreply.github.com> Co-authored-by: Farrah Mae Ochoa <82315152+farrah-deriv@users.noreply.github.com> Co-authored-by: Likhith Kolayari <98398322+likhith-deriv@users.noreply.github.com> Co-authored-by: Likhith Kolayari Co-authored-by: amina-deriv <84661147+amina-deriv@users.noreply.github.com> Co-authored-by: yauheni-kryzhyk-deriv <103182683+yauheni-kryzhyk-deriv@users.noreply.github.com> Co-authored-by: “yauheni-kryzhyk-deriv” <“yauheni@deriv.me”> Co-authored-by: maryiafrantsava-binary <103181650+maryiafrantsava-binary@users.noreply.github.com> Co-authored-by: adrienne-deriv <103016120+adrienne-deriv@users.noreply.github.com> Co-authored-by: balakrishna-binary <56330681+balakrishna-binary@users.noreply.github.com> Co-authored-by: Yashim Wong <75345074+yashim-deriv@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: DerivFE <80095553+DerivFE@users.noreply.github.com> Co-authored-by: Adrienne Rio Co-authored-by: Ameerul Hady --- .../src/components/icon/common/ic-block.svg | 1 + .../common/ic-blocked-advertisers-barred.svg | 1 + .../common/ic-empty-blocked-advertisers.svg | 1 + .../components/icon/common/ic-menu-dots.svg | 1 + .../icon/common/ic-user-blocked-outline.svg | 1 + .../components/src/components/icon/icons.js | 4 + packages/components/stories/icon/icons.js | 5 + .../advertiser-page-dropdown-menu.jsx | 61 ++++ .../advertiser-page/advertiser-page-stats.jsx | 5 - .../advertiser-page/advertiser-page.jsx | 283 +++++++++++------- .../advertiser-page/advertiser-page.scss | 75 ++++- .../block-user/block-user-count.jsx | 69 +++++ .../block-user/block-user-count.scss | 13 + .../block-user-overlay/block-user-overlay.jsx | 39 +++ .../block-user-overlay.scss | 39 +++ .../block-user/block-user-overlay/index.js | 4 + .../block-user-empty/block-user-empty.jsx | 49 +++ .../block-user-empty/block-user-empty.scss | 13 + .../block-user/block-user-empty/index.js | 4 + .../block-user-modal/block-user-modal.jsx | 73 +++++ .../block-user-modal/block-user-modal.scss | 5 + .../block-user/block-user-modal/index.js | 4 + .../p2p/src/components/buy-sell/buy-sell.scss | 2 +- .../components/error-modal/error-modal.jsx | 25 +- .../components/error-modal/error-modal.scss | 5 + .../block-user-table/block-user-row.jsx | 37 +++ .../block-user-table-error.jsx | 66 ++++ .../block-user-table/block-user-table.jsx | 70 +++++ .../block-user/block-user-table/index.js | 4 + .../my-profile/block-user/block-user.jsx | 75 +++++ .../my-profile/block-user/block-user.scss | 120 ++++++++ .../components/my-profile/block-user/index.js | 3 + .../my-profile/my-profile-content.jsx | 14 +- .../my-profile-header/my-profile-header.jsx | 4 + .../my-profile-header/my-profile-header.scss | 2 +- .../my-profile-name/my-profile-name.jsx | 59 +++- .../my-profile-name/my-profile-name.scss | 20 +- .../my-profile-stats-table.scss | 6 +- .../my-profile-stats/my-profile-stats.jsx | 60 ++-- .../my-profile-stats/my-profile-stats.scss | 4 + .../src/components/my-profile/my-profile.jsx | 17 +- .../src/components/my-profile/my-profile.scss | 24 ++ packages/p2p/src/constants/my-profile-tabs.js | 1 + .../p2p/src/stores/advertiser-page-store.js | 42 ++- packages/p2p/src/stores/general-store.js | 71 ++++- packages/p2p/src/stores/my-profile-store.js | 82 +++++ 46 files changed, 1361 insertions(+), 202 deletions(-) create mode 100644 packages/components/src/components/icon/common/ic-block.svg create mode 100644 packages/components/src/components/icon/common/ic-blocked-advertisers-barred.svg create mode 100644 packages/components/src/components/icon/common/ic-empty-blocked-advertisers.svg create mode 100644 packages/components/src/components/icon/common/ic-menu-dots.svg create mode 100644 packages/components/src/components/icon/common/ic-user-blocked-outline.svg create mode 100644 packages/p2p/src/components/advertiser-page/advertiser-page-dropdown-menu.jsx create mode 100644 packages/p2p/src/components/advertiser-page/block-user/block-user-count.jsx create mode 100644 packages/p2p/src/components/advertiser-page/block-user/block-user-count.scss create mode 100644 packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/block-user-overlay.jsx create mode 100644 packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/block-user-overlay.scss create mode 100644 packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/index.js create mode 100644 packages/p2p/src/components/block-user/block-user-empty/block-user-empty.jsx create mode 100644 packages/p2p/src/components/block-user/block-user-empty/block-user-empty.scss create mode 100644 packages/p2p/src/components/block-user/block-user-empty/index.js create mode 100644 packages/p2p/src/components/block-user/block-user-modal/block-user-modal.jsx create mode 100644 packages/p2p/src/components/block-user/block-user-modal/block-user-modal.scss create mode 100644 packages/p2p/src/components/block-user/block-user-modal/index.js create mode 100644 packages/p2p/src/components/error-modal/error-modal.scss create mode 100644 packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-row.jsx create mode 100644 packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-table-error.jsx create mode 100644 packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-table.jsx create mode 100644 packages/p2p/src/components/my-profile/block-user/block-user-table/index.js create mode 100644 packages/p2p/src/components/my-profile/block-user/block-user.jsx create mode 100644 packages/p2p/src/components/my-profile/block-user/block-user.scss create mode 100644 packages/p2p/src/components/my-profile/block-user/index.js diff --git a/packages/components/src/components/icon/common/ic-block.svg b/packages/components/src/components/icon/common/ic-block.svg new file mode 100644 index 000000000000..444bb52a3bb3 --- /dev/null +++ b/packages/components/src/components/icon/common/ic-block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-blocked-advertisers-barred.svg b/packages/components/src/components/icon/common/ic-blocked-advertisers-barred.svg new file mode 100644 index 000000000000..7189095ac3ec --- /dev/null +++ b/packages/components/src/components/icon/common/ic-blocked-advertisers-barred.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-empty-blocked-advertisers.svg b/packages/components/src/components/icon/common/ic-empty-blocked-advertisers.svg new file mode 100644 index 000000000000..e8838156237c --- /dev/null +++ b/packages/components/src/components/icon/common/ic-empty-blocked-advertisers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-menu-dots.svg b/packages/components/src/components/icon/common/ic-menu-dots.svg new file mode 100644 index 000000000000..36f7407a70fc --- /dev/null +++ b/packages/components/src/components/icon/common/ic-menu-dots.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-user-blocked-outline.svg b/packages/components/src/components/icon/common/ic-user-blocked-outline.svg new file mode 100644 index 000000000000..c919a28a4e0e --- /dev/null +++ b/packages/components/src/components/icon/common/ic-user-blocked-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/icons.js b/packages/components/src/components/icon/icons.js index 848a836dfe0c..c6903f6cf267 100644 --- a/packages/components/src/components/icon/icons.js +++ b/packages/components/src/components/icon/icons.js @@ -180,6 +180,7 @@ import './common/ic-bank-dashboard.svg'; import './common/ic-bank.svg'; import './common/ic-bell.svg'; import './common/ic-black-warning.svg'; +import './common/ic-block.svg'; import './common/ic-box.svg'; import './common/ic-button-back.svg'; import './common/ic-calendar-datefrom.svg'; @@ -251,6 +252,7 @@ import './common/ic-email-verification-link-invalid.svg'; import './common/ic-email-verification-link-valid.svg'; import './common/ic-email-verified.svg'; import './common/ic-email.svg'; +import './common/ic-empty-blocked-advertisers.svg'; import './common/ic-empty-folder.svg'; import './common/ic-empty-star.svg'; import './common/ic-eye.svg'; @@ -296,6 +298,7 @@ import './common/ic-lookbacks.svg'; import './common/ic-loss.svg'; import './common/ic-macos-logo.svg'; import './common/ic-macos.svg'; +import './common/ic-menu-dots.svg'; import './common/ic-message-delivered.svg'; import './common/ic-message-errored.svg'; import './common/ic-message-pending.svg'; @@ -402,6 +405,7 @@ import './common/ic-unsaved-changes-dashboard.svg'; import './common/ic-unsaved-changes.svg'; import './common/ic-up-down-solid.svg'; import './common/ic-ups-downs.svg'; +import './common/ic-user-blocked-outline.svg'; import './common/ic-user-outline.svg'; import './common/ic-user.svg'; import './common/ic-utility.svg'; diff --git a/packages/components/stories/icon/icons.js b/packages/components/stories/icon/icons.js index 49cf65d39d25..69ad09e56ca8 100644 --- a/packages/components/stories/icon/icons.js +++ b/packages/components/stories/icon/icons.js @@ -187,6 +187,8 @@ export const icons = 'IcBank', 'IcBell', 'IcBlackWarning', + 'IcBlock', + 'IcBlockedAdvertisersBarred', 'IcBox', 'IcButtonBack', 'IcCalendarDatefrom', @@ -258,6 +260,7 @@ export const icons = 'IcEmailVerificationLinkValid', 'IcEmailVerified', 'IcEmail', + 'IcEmptyBlockedAdvertisers', 'IcEmptyFolder', 'IcEmptyStar', 'IcEye', @@ -303,6 +306,7 @@ export const icons = 'IcLoss', 'IcMacosLogo', 'IcMacos', + 'IcMenuDots', 'IcMessageDelivered', 'IcMessageErrored', 'IcMessagePending', @@ -409,6 +413,7 @@ export const icons = 'IcUnsavedChanges', 'IcUpDownSolid', 'IcUpsDowns', + 'IcUserBlockedOutline', 'IcUserOutline', 'IcUser', 'IcUtility', diff --git a/packages/p2p/src/components/advertiser-page/advertiser-page-dropdown-menu.jsx b/packages/p2p/src/components/advertiser-page/advertiser-page-dropdown-menu.jsx new file mode 100644 index 000000000000..2e2e7b23d5d6 --- /dev/null +++ b/packages/p2p/src/components/advertiser-page/advertiser-page-dropdown-menu.jsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { Dropdown, Icon, Text } from '@deriv/components'; +import { Localize } from 'Components/i18next'; +import { useStores } from 'Stores'; +import { useOnClickOutside } from '../../../../components/src/hooks'; +import './advertiser-page.scss'; + +const AdvertiserPageDropdownMenu = () => { + const dropdown_menu_ref = React.useRef(); + const { advertiser_page_store } = useStores(); + + const onClickOutside = () => { + advertiser_page_store.setIsDropdownMenuVisible(false); + }; + + useOnClickOutside(dropdown_menu_ref, onClickOutside, () => advertiser_page_store.is_dropdown_menu_visible); + + return ( + !advertiser_page_store.is_counterparty_advertiser_blocked && ( +
+ + advertiser_page_store.setIsDropdownMenuVisible(!advertiser_page_store.is_dropdown_menu_visible) + } + size={16} + /> + {advertiser_page_store.is_dropdown_menu_visible && ( +
+ + + + } + /> +
+ )} +
+ ) + ); +}; + +export default observer(AdvertiserPageDropdownMenu); diff --git a/packages/p2p/src/components/advertiser-page/advertiser-page-stats.jsx b/packages/p2p/src/components/advertiser-page/advertiser-page-stats.jsx index f1f17ed6626f..e9f76f2f745e 100644 --- a/packages/p2p/src/components/advertiser-page/advertiser-page-stats.jsx +++ b/packages/p2p/src/components/advertiser-page/advertiser-page-stats.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Money, Table, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { observer } from 'mobx-react-lite'; @@ -293,8 +292,4 @@ const AdvertiserPageStats = () => { ); }; -AdvertiserPageStats.propTypes = { - is_visible: PropTypes.bool, -}; - export default observer(AdvertiserPageStats); diff --git a/packages/p2p/src/components/advertiser-page/advertiser-page.jsx b/packages/p2p/src/components/advertiser-page/advertiser-page.jsx index f6ea481e1afb..ebda18c26d13 100644 --- a/packages/p2p/src/components/advertiser-page/advertiser-page.jsx +++ b/packages/p2p/src/components/advertiser-page/advertiser-page.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Loading, Text } from '@deriv/components'; +import { DesktopWrapper, Loading, MobileWrapper, Text } from '@deriv/components'; import { daysSince, isMobile } from '@deriv/shared'; import { reaction } from 'mobx'; import { observer } from 'mobx-react-lite'; @@ -14,11 +14,16 @@ import UserAvatar from 'Components/user/user-avatar/user-avatar.jsx'; import AdvertiserPageStats from './advertiser-page-stats.jsx'; import AdvertiserPageAdverts from './advertiser-page-adverts.jsx'; import StarRating from 'Components/star-rating'; +import AdvertiserPageDropdownMenu from './advertiser-page-dropdown-menu.jsx'; import TradeBadge from '../trade-badge/trade-badge.jsx'; +import BlockUserOverlay from './block-user/block-user-overlay'; +import BlockUserModal from 'Components/block-user/block-user-modal'; +import ErrorModal from 'Components/error-modal/error-modal'; +import classNames from 'classnames'; import './advertiser-page.scss'; const AdvertiserPage = () => { - const { advertiser_page_store, buy_sell_store } = useStores(); + const { general_store, advertiser_page_store, buy_sell_store } = useStores(); const { basic_verification, @@ -26,7 +31,9 @@ const AdvertiserPage = () => { created_time, first_name, full_verification, + id, last_name, + name, rating_average, rating_count, recommended_average, @@ -37,20 +44,26 @@ const AdvertiserPage = () => { // rating_average_decimal converts rating_average to 1 d.p number const rating_average_decimal = rating_average ? Number(rating_average).toFixed(1) : null; const joined_since = daysSince(created_time); + const is_my_advert = id === general_store.advertiser_id; + const [is_error_modal_open, setIsErrorModalOpen] = React.useState(false); React.useEffect(() => { advertiser_page_store.onMount(); + advertiser_page_store.setIsDropdownMenuVisible(false); return reaction( - () => advertiser_page_store.active_index, - () => advertiser_page_store.onTabChange(), + () => [advertiser_page_store.active_index, general_store.block_unblock_user_error], + () => { + advertiser_page_store.onTabChange(); + if (general_store.block_unblock_user_error) setIsErrorModalOpen(true); + }, { fireImmediately: true } ); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - if (advertiser_page_store.is_loading) { + if (advertiser_page_store.is_loading || general_store.is_block_unblock_user_loading) { return ; } @@ -59,131 +72,173 @@ const AdvertiserPage = () => { } return ( -
+
+ { + if (!is_open) buy_sell_store.hideAdvertiserPage(); + advertiser_page_store.onCancel(); + general_store.setBlockUnblockUserError(''); + }} + small + /> + - -
-
- -
-
- - {advertiser_page_store.advertiser_details_name} - - {first_name && last_name && ( -
- - {`(${first_name} ${last_name})`} - -
- )} -
-
- {rating_average ? ( - -
- -
- - {rating_average_decimal} - - - {rating_count === 1 ? ( - - ) : ( - - )} - +
+ + {!is_my_advert && ( + + + + )} +
+ general_store.setIsBlockUserModalOpen(true)} + > +
+
+ +
+
+ + {advertiser_page_store.advertiser_details_name} + + {first_name && last_name && ( +
+ + {`(${first_name} ${last_name})`} + +
+ )} +
+
+ {rating_average ? ( + +
+ +
+ + {rating_average_decimal} + + + {rating_count === 1 ? ( + + ) : ( + + )} + +
+
+
+
+
+ ) : ( +
+ + +
+ )} + {!isMobile() && (
- + + {joined_since > 0 ? ( + + ) : ( + + )} +
- - ) : ( -
- - - -
- )} - {!isMobile() && ( -
- - {joined_since > 0 ? ( - - ) : ( - - )} - -
+ )} +
+ {isMobile() && ( + + {joined_since > 0 ? ( + + ) : ( + + )} + )} +
+ +
- {isMobile() && ( - - {joined_since > 0 ? ( - - ) : ( - - )} - + {!is_my_advert && ( + + + )} -
- -
+
- -
- + +
); }; diff --git a/packages/p2p/src/components/advertiser-page/advertiser-page.scss b/packages/p2p/src/components/advertiser-page/advertiser-page.scss index 74f9459a2db2..4e33161eb441 100644 --- a/packages/p2p/src/components/advertiser-page/advertiser-page.scss +++ b/packages/p2p/src/components/advertiser-page/advertiser-page.scss @@ -3,6 +3,70 @@ display: flex; flex-direction: column; + @include mobile { + overflow-y: auto; + } + + &__dropdown { + box-shadow: 0 1rem 2rem var(--shadow-menu); + border-radius: $BORDER_RADIUS; + cursor: pointer; + position: absolute; + right: 0; + z-index: 2; + + .dc-dropdown__display { + background-color: var(--general-main-2); + border: 1px solid var(--general-active); + + span { + background-color: var(--general-main-2); + } + + &-placeholder { + top: auto; + } + } + + &-container { + margin-top: 0; + } + + &:hover { + .dc-dropdown__display { + background-color: var(--border-normal); + border: 1px solid var(--border-normal); + + span { + background-color: var(--border-normal); + } + } + } + } + + &__menu-dots-icon { + cursor: pointer; + margin-bottom: 0.8rem; + } + + &__menu-dots-toggle { + position: absolute; + right: 2.4rem; + + @include mobile { + bottom: 0.8rem; + right: 1.2rem; + } + } + + &__page-return-header { + position: relative; + } + + &--no-scroll { + overflow: hidden; + } + &__header { display: flex; flex-direction: column; @@ -21,7 +85,9 @@ margin: 0 0 1rem; .dp2p-avatar { - align-self: center; + align-self: flex-start; + margin-top: 0.6rem; + margin-right: 0.7rem; } } } @@ -191,7 +257,7 @@ display: flex; @media (max-height: 580px) { - height: 10rem; + height: 5rem; } @include mobile { display: flex; @@ -269,7 +335,6 @@ @include mobile { display: flex; flex-flow: column; - margin-top: 1rem; width: 100vw; } @@ -352,6 +417,10 @@ padding: 2.4rem; border: 1px solid var(--general-hover); border-radius: 0.8rem; + + @include mobile { + border-radius: unset; + } } } diff --git a/packages/p2p/src/components/advertiser-page/block-user/block-user-count.jsx b/packages/p2p/src/components/advertiser-page/block-user/block-user-count.jsx new file mode 100644 index 000000000000..8a86361852ae --- /dev/null +++ b/packages/p2p/src/components/advertiser-page/block-user/block-user-count.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { Button, Icon, MobileWrapper, Modal, Popover, Text } from '@deriv/components'; +import { localize } from 'Components/i18next'; +import { useStores } from 'Stores'; +import { isDesktop } from '@deriv/shared'; +import './block-user-count.scss'; + +const BlockUserCount = () => { + const { general_store } = useStores(); + const { user_blocked_count } = general_store; + + const [is_block_user_count_modal_open, setIsBlockUserCountModalOpen] = React.useState(false); + + const getMessage = () => { + switch (user_blocked_count) { + case 0: + return localize('Nobody has blocked you. Yay!'); + case 1: + return localize('{{user_blocked_count}} person has blocked you', { + user_blocked_count, + }); + default: + return localize('{{user_blocked_count}} people have blocked you', { + user_blocked_count, + }); + } + }; + + return ( + + + + + + {getMessage()} + + + + + + + +
setIsBlockUserCountModalOpen(true) : null}> + + + + {user_blocked_count} + + +
+
+ ); +}; + +export default observer(BlockUserCount); diff --git a/packages/p2p/src/components/advertiser-page/block-user/block-user-count.scss b/packages/p2p/src/components/advertiser-page/block-user/block-user-count.scss new file mode 100644 index 000000000000..e55ec7ecea6a --- /dev/null +++ b/packages/p2p/src/components/advertiser-page/block-user/block-user-count.scss @@ -0,0 +1,13 @@ +.block-user-count { + width: fit-content; + margin: auto 0.5rem; + + &__container { + display: flex; + align-items: center; + + &--icon { + margin-right: 0.4rem; + } + } +} diff --git a/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/block-user-overlay.jsx b/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/block-user-overlay.jsx new file mode 100644 index 000000000000..ccdbd03eada3 --- /dev/null +++ b/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/block-user-overlay.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Button, Icon, Text } from '@deriv/components'; +import { Localize } from 'Components/i18next'; +import { useStores } from 'Stores'; +import { observer } from 'mobx-react-lite'; +import PropTypes from 'prop-types'; + +const BlockUserOverlay = ({ children, is_visible, onClickUnblock }) => { + const { advertiser_page_store } = useStores(); + + if (is_visible) { + return ( +
+
+ + + + + +
+ {children} +
+ ); + } + return children; +}; + +BlockUserOverlay.propTypes = { + children: PropTypes.any, + is_visible: PropTypes.bool.isRequired, + onClickUnblock: PropTypes.func, +}; + +export default observer(BlockUserOverlay); diff --git a/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/block-user-overlay.scss b/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/block-user-overlay.scss new file mode 100644 index 000000000000..61b95397d9fc --- /dev/null +++ b/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/block-user-overlay.scss @@ -0,0 +1,39 @@ +.block-user-overlay { + position: relative; + margin-bottom: 3rem; + + &__wrapper { + align-items: center; + background: var(--general-main-1); + border: 2px solid var(--general-section-1); + border-radius: 6px; + display: flex; + flex-direction: column; + height: 100%; + justify-content: center; + position: absolute; + opacity: 0.9; + top: 0; + left: 0; + width: 100%; + z-index: 9999; + + @include mobile { + border: none; + border-radius: unset; + flex-flow: column; + } + + &-text { + margin: 2.5rem 0 0.8rem; + } + + &-button { + margin-top: 2rem; + } + } + + .advertiser-page__adverts-table-body { + min-height: 20vh; + } +} diff --git a/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/index.js b/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/index.js new file mode 100644 index 000000000000..015d6d9b4f3f --- /dev/null +++ b/packages/p2p/src/components/advertiser-page/block-user/block-user-overlay/index.js @@ -0,0 +1,4 @@ +import BlockUserOverlay from './block-user-overlay'; +import './block-user-overlay.scss'; + +export default BlockUserOverlay; diff --git a/packages/p2p/src/components/block-user/block-user-empty/block-user-empty.jsx b/packages/p2p/src/components/block-user/block-user-empty/block-user-empty.jsx new file mode 100644 index 000000000000..78fa00b8b210 --- /dev/null +++ b/packages/p2p/src/components/block-user/block-user-empty/block-user-empty.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { DesktopWrapper, Icon, MobileFullPageModal, MobileWrapper, Text } from '@deriv/components'; +import { my_profile_tabs } from 'Constants/my-profile-tabs'; +import { Localize, localize } from 'Components/i18next'; +import { useStores } from 'Stores'; + +const BlockUserEmpty = () => { + const { my_profile_store } = useStores(); + + return ( + + +
+ + + + +
+
+ + my_profile_store.setActiveTab(my_profile_tabs.MY_STATS)} + > + + + + + + +
+ ); +}; + +export default observer(BlockUserEmpty); diff --git a/packages/p2p/src/components/block-user/block-user-empty/block-user-empty.scss b/packages/p2p/src/components/block-user/block-user-empty/block-user-empty.scss new file mode 100644 index 000000000000..dd67de69a806 --- /dev/null +++ b/packages/p2p/src/components/block-user/block-user-empty/block-user-empty.scss @@ -0,0 +1,13 @@ +.block-user-empty { + align-items: center; + display: flex; + flex-direction: column; + + &__icon { + margin-top: 2.2rem; + } + + &__text { + margin: 3rem 0 0.8rem; + } +} diff --git a/packages/p2p/src/components/block-user/block-user-empty/index.js b/packages/p2p/src/components/block-user/block-user-empty/index.js new file mode 100644 index 000000000000..12ae08ba102a --- /dev/null +++ b/packages/p2p/src/components/block-user/block-user-empty/index.js @@ -0,0 +1,4 @@ +import BlockUserEmpty from './block-user-empty.jsx'; +import './block-user-empty.scss'; + +export default BlockUserEmpty; diff --git a/packages/p2p/src/components/block-user/block-user-modal/block-user-modal.jsx b/packages/p2p/src/components/block-user/block-user-modal/block-user-modal.jsx new file mode 100644 index 000000000000..bb6949e6def7 --- /dev/null +++ b/packages/p2p/src/components/block-user/block-user-modal/block-user-modal.jsx @@ -0,0 +1,73 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { Button, Modal, Text } from '@deriv/components'; +import { observer } from 'mobx-react-lite'; +import { Localize } from 'Components/i18next'; + +const BlockUserModal = ({ advertiser_name, is_advertiser_blocked, is_block_user_modal_open, onCancel, onSubmit }) => { + return ( + + {is_advertiser_blocked ? ( + + ) : ( + + )} + + } + > + + + {is_advertiser_blocked ? ( + + ) : ( + + )} + + + + + + + + ); +}; + +BlockUserModal.propTypes = { + advertiser_name: PropTypes.string.isRequired, + is_advertiser_blocked: PropTypes.bool.isRequired, + is_block_user_modal_open: PropTypes.bool.isRequired, + onCancel: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, +}; + +export default observer(BlockUserModal); diff --git a/packages/p2p/src/components/block-user/block-user-modal/block-user-modal.scss b/packages/p2p/src/components/block-user/block-user-modal/block-user-modal.scss new file mode 100644 index 000000000000..84d470158208 --- /dev/null +++ b/packages/p2p/src/components/block-user/block-user-modal/block-user-modal.scss @@ -0,0 +1,5 @@ +.block-user-modal { + &__body { + padding: 0 2.4rem; + } +} diff --git a/packages/p2p/src/components/block-user/block-user-modal/index.js b/packages/p2p/src/components/block-user/block-user-modal/index.js new file mode 100644 index 000000000000..385476da1fa2 --- /dev/null +++ b/packages/p2p/src/components/block-user/block-user-modal/index.js @@ -0,0 +1,4 @@ +import BlockUserModal from './block-user-modal.jsx'; +import './block-user-modal.scss'; + +export default BlockUserModal; diff --git a/packages/p2p/src/components/buy-sell/buy-sell.scss b/packages/p2p/src/components/buy-sell/buy-sell.scss index bb9026399279..099e99d950d6 100644 --- a/packages/p2p/src/components/buy-sell/buy-sell.scss +++ b/packages/p2p/src/components/buy-sell/buy-sell.scss @@ -5,7 +5,7 @@ flex-direction: column; &__advertiser-page-return { - margin: 0 0 2.4rem; + margin-top: unset; .dc-text { align-self: flex-start; diff --git a/packages/p2p/src/components/error-modal/error-modal.jsx b/packages/p2p/src/components/error-modal/error-modal.jsx index 05a356c5d7cc..dac8d9ebf406 100644 --- a/packages/p2p/src/components/error-modal/error-modal.jsx +++ b/packages/p2p/src/components/error-modal/error-modal.jsx @@ -1,12 +1,27 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { observer } from 'mobx-react-lite'; import { Button, Modal } from '@deriv/components'; import { Localize } from 'Components/i18next'; +import './error-modal.scss'; -const ErrorModal = ({ error_message, error_modal_title, is_error_modal_open, setIsErrorModalOpen }) => { +const ErrorModal = ({ + error_message, + error_modal_title, + has_close_icon, + is_error_modal_open, + setIsErrorModalOpen, + small, +}) => { return ( - - {error_message} + + {error_message} + + + ); +}; + +BlockUserRow.propTypes = { + advertiser: PropTypes.object, +}; + +export default observer(BlockUserRow); diff --git a/packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-table-error.jsx b/packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-table-error.jsx new file mode 100644 index 000000000000..d76941b3e5d1 --- /dev/null +++ b/packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-table-error.jsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import PropTypes from 'prop-types'; +import { DesktopWrapper, Icon, MobileFullPageModal, MobileWrapper, Text } from '@deriv/components'; +import { my_profile_tabs } from 'Constants/my-profile-tabs'; +import { Localize, localize } from 'Components/i18next'; +import { useStores } from 'Stores'; + +const BlockUserTableError = ({ error_message }) => { + const { my_profile_store } = useStores(); + + return ( + + +
+ + + + +
+
+ + my_profile_store.setActiveTab(my_profile_tabs.MY_STATS)} + > + + + + + + +
+ ); +}; + +BlockUserTableError.propTypes = { + error_message: PropTypes.string, +}; + +export default observer(BlockUserTableError); diff --git a/packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-table.jsx b/packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-table.jsx new file mode 100644 index 000000000000..fb2498c104e5 --- /dev/null +++ b/packages/p2p/src/components/my-profile/block-user/block-user-table/block-user-table.jsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { reaction } from 'mobx'; +import { observer } from 'mobx-react-lite'; +import { InfiniteDataList, Loading, Table, Text } from '@deriv/components'; +import { localize } from 'Components/i18next'; +import { isMobile } from '@deriv/shared'; +import { useStores } from 'Stores'; +import BlockUserRow from './block-user-row.jsx'; +import BlockUserEmpty from 'Components/block-user/block-user-empty'; +import BlockUserTableError from './block-user-table-error.jsx'; + +const BlockUserTable = () => { + const { general_store, my_profile_store } = useStores(); + + React.useEffect(() => { + my_profile_store.setBlockedAdvertisersList([]); + my_profile_store.getBlockedAdvertisersList(); + my_profile_store.setSearchTerm(''); + + reaction( + () => general_store.is_barred, + () => { + if (!general_store.is_barred) general_store.setBlockUnblockUserError(''); + my_profile_store.getBlockedAdvertisersList(); + my_profile_store.setSearchTerm(''); + } + ); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (my_profile_store.is_loading) { + return ; + } + + if (general_store.block_unblock_user_error && general_store.is_barred) { + return ; + } + + if (my_profile_store.search_term && my_profile_store.search_results.length === 0) { + return ( + + {localize('There are no matching name.')} + + ); + } + + if (my_profile_store.blocked_advertisers_list.length) { + return ( + + + + item.id} + loadMoreRowsFn={() => {}} + rowRenderer={props => } + /> + +
+
+ ); + } + + return ; +}; + +export default observer(BlockUserTable); diff --git a/packages/p2p/src/components/my-profile/block-user/block-user-table/index.js b/packages/p2p/src/components/my-profile/block-user/block-user-table/index.js new file mode 100644 index 000000000000..65c74e1007ef --- /dev/null +++ b/packages/p2p/src/components/my-profile/block-user/block-user-table/index.js @@ -0,0 +1,4 @@ +import BlockUserTable from './block-user-table.jsx'; +import '../block-user.scss'; + +export default BlockUserTable; diff --git a/packages/p2p/src/components/my-profile/block-user/block-user.jsx b/packages/p2p/src/components/my-profile/block-user/block-user.jsx new file mode 100644 index 000000000000..b904aefddc5b --- /dev/null +++ b/packages/p2p/src/components/my-profile/block-user/block-user.jsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { useStores } from 'Stores'; +import { DesktopWrapper, MobileFullPageModal, MobileWrapper } from '@deriv/components'; +import BlockUserModal from 'Components/block-user/block-user-modal'; +import BlockUserTable from 'Components/my-profile/block-user/block-user-table/block-user-table'; +import SearchBox from 'Components/search-box'; +import { my_profile_tabs } from 'Constants/my-profile-tabs'; +import debounce from 'lodash.debounce'; +import { localize } from 'Components/i18next'; + +const BlockUserList = observer(() => { + const { general_store, my_profile_store } = useStores(); + + const loadBlockedAdvertisers = debounce(search => { + my_profile_store.setSearchTerm(search.trim()); + my_profile_store.loadMoreBlockedAdvertisers(); + }, 200); + + const onSearch = search => { + // Ensures that blocked advertisers list is not reloaded if search term entered is the same + if (my_profile_store.search_term !== search.trim()) { + my_profile_store.setIsLoading(true); + loadBlockedAdvertisers(search); + } + }; + + const onClear = () => { + my_profile_store.setSearchTerm(''); + my_profile_store.setSearchResults([]); + }; + + return ( +
+ {my_profile_store.blocked_advertisers_list.length > 0 && !general_store.is_barred && ( + + )} + +
+ ); +}); + +const BlockUser = () => { + const { general_store, my_profile_store } = useStores(); + + return ( + + general_store.setIsBlockUserModalOpen(false)} + onSubmit={my_profile_store.onSubmit} + /> + + + + + my_profile_store.setActiveTab(my_profile_tabs.MY_STATS)} + > + + + + + ); +}; + +export default observer(BlockUser); diff --git a/packages/p2p/src/components/my-profile/block-user/block-user.scss b/packages/p2p/src/components/my-profile/block-user/block-user.scss new file mode 100644 index 000000000000..73e701d803a2 --- /dev/null +++ b/packages/p2p/src/components/my-profile/block-user/block-user.scss @@ -0,0 +1,120 @@ +.block-user { + &__data-list { + flex: 1; + + &__data-list-body { + flex: 1; + } + + .data-list__body-wrapper { + flex: 1; + } + } + + &__list { + display: flex; + flex: 1; + flex-direction: column; + height: 100%; + + & .search-box { + margin: 0rem 0.8rem 2.4rem; + } + } + + &__modal { + @include mobile { + display: flex; + flex-direction: column; + grid-template-rows: 1fr 8rem; + height: 100%; + overflow-x: hidden; + width: 100vw; + + .search-box { + margin: 1rem auto; + width: 91%; + } + } + } + + &__table { + display: flex; + flex: 1; + flex-direction: column; + + @include mobile { + height: 100%; + } + + &-body { + flex: 1; + display: flex; + flex-direction: column; + } + + &--error { + align-items: center; + display: flex; + flex-direction: column; + + &-icon { + margin-top: 2.2rem; + } + + &-text { + margin: 3rem 11rem; + + @include mobile { + margin: 3rem 2rem; + } + } + } + + &-header { + display: grid; + grid-template-columns: 1fr; + + > .dc-table__head:first-child { + margin-left: 1.6rem; + } + } + } + + &__text { + display: inline-block; + padding-top: 3rem; + width: 100%; + } + + &__row { + display: grid; + padding: 1.6rem; + grid-template-columns: 1fr 1fr; + + &:hover { + background-color: var(--general-hover); + + @include mobile { + background-color: inherit; + } + } + + &-button { + display: flex; + justify-content: flex-end; + align-items: center; + } + + &-cell { + align-items: center; + display: flex; + + &--container { + display: flex; + flex-direction: column; + margin-left: 0.8rem; + } + } + } +} diff --git a/packages/p2p/src/components/my-profile/block-user/index.js b/packages/p2p/src/components/my-profile/block-user/index.js new file mode 100644 index 000000000000..87c786d0ba17 --- /dev/null +++ b/packages/p2p/src/components/my-profile/block-user/index.js @@ -0,0 +1,3 @@ +import BlockUser from './block-user'; + +export default BlockUser; diff --git a/packages/p2p/src/components/my-profile/my-profile-content.jsx b/packages/p2p/src/components/my-profile/my-profile-content.jsx index 6c959bce95e4..5576826fa4d4 100644 --- a/packages/p2p/src/components/my-profile/my-profile-content.jsx +++ b/packages/p2p/src/components/my-profile/my-profile-content.jsx @@ -7,20 +7,12 @@ import { useStores } from 'Stores'; import MyProfileForm from './my-profile-form'; import MyProfileStats from './my-profile-stats'; import PaymentMethods from './payment-methods'; +import BlockUser from './block-user'; const MyProfileContent = () => { const { my_profile_store } = useStores(); const formik_ref = React.useRef(); - const generatePageHeaderText = () => { - if (my_profile_store.should_show_add_payment_method_form) { - return localize('Add payment method'); - } else if (my_profile_store.should_show_edit_payment_method_form) { - return localize('Edit payment method'); - } - return localize('Payment methods'); - }; - if (my_profile_store.active_tab === my_profile_tabs.AD_TEMPLATE) { return ; } else if (my_profile_store.active_tab === my_profile_tabs.PAYMENT_METHODS) { @@ -36,7 +28,7 @@ const MyProfileContent = () => { is_modal_open is_flex page_header_className='buy-sell__modal-header' - page_header_text={generatePageHeaderText()} + page_header_text={localize('Payment methods')} pageHeaderReturnFn={() => { if ( (formik_ref.current && formik_ref.current.dirty) || @@ -55,6 +47,8 @@ const MyProfileContent = () => {
); + } else if (my_profile_store.active_tab === my_profile_tabs.BLOCKED_ADVERTISERS) { + return ; } return ; }; diff --git a/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.jsx b/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.jsx index 071636eb2b08..0c43797046eb 100644 --- a/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.jsx +++ b/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.jsx @@ -22,6 +22,10 @@ const MyProfileHeader = () => { text: localize('Ad details'), value: my_profile_tabs.AD_TEMPLATE, }, + { + text: localize('Blocked advertisers'), + value: my_profile_tabs.BLOCKED_ADVERTISERS, + }, ]; const onChangeTab = event => my_profile_store.setActiveTab(event.target.value); diff --git a/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.scss b/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.scss index 774541808d82..fe0b6c3846d4 100644 --- a/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.scss +++ b/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.scss @@ -2,5 +2,5 @@ align-items: center; display: flex; height: 3.5rem; - width: 45rem; + width: 65rem; } diff --git a/packages/p2p/src/components/my-profile/my-profile-stats/my-profile-name/my-profile-name.jsx b/packages/p2p/src/components/my-profile/my-profile-stats/my-profile-name/my-profile-name.jsx index fb2070f5eab1..dbc05232f0cb 100644 --- a/packages/p2p/src/components/my-profile/my-profile-stats/my-profile-name/my-profile-name.jsx +++ b/packages/p2p/src/components/my-profile/my-profile-stats/my-profile-name/my-profile-name.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { DesktopWrapper, Text } from '@deriv/components'; +import { DesktopWrapper, MobileWrapper, Text } from '@deriv/components'; import { observer } from 'mobx-react-lite'; import UserAvatar from 'Components/user/user-avatar/user-avatar.jsx'; import { useStores } from 'Stores'; @@ -9,6 +9,7 @@ import TradeBadge from '../../../trade-badge'; import MyProfilePrivacy from '../my-profile-privacy'; import StarRating from 'Components/star-rating'; import RecommendedBy from 'Components/recommended-by'; +import BlockUserCount from 'Components/advertiser-page/block-user/block-user-count'; const MyProfileName = () => { const { general_store, my_profile_store } = useStores(); @@ -89,22 +90,48 @@ const MyProfileName = () => {
)} - - {joined_since > 0 ? ( - - ) : ( - - )} - + +
+ +
+ + {joined_since > 0 ? ( + + ) : ( + + )} + +
-
+ +
+
+ +
+ + {joined_since > 0 ? ( + + ) : ( + + )} + +
+
+
{ const { my_profile_store } = useStores(); const [should_show_stats_and_ratings, setShouldShowStatsAndRatings] = React.useState(false); + const tabs = [ + { + default_text: 'Stats', + onClick: () => setShouldShowStatsAndRatings(true), + }, + { + default_text: 'Payment methods', + onClick: () => my_profile_store.setActiveTab(my_profile_tabs.PAYMENT_METHODS), + }, + { + default_text: 'Ad details', + onClick: () => my_profile_store.setActiveTab(my_profile_tabs.AD_TEMPLATE), + }, + { + default_text: 'Blocked advertisers', + onClick: () => my_profile_store.setActiveTab(my_profile_tabs.BLOCKED_ADVERTISERS), + }, + ]; if (my_profile_store.is_loading) { return ; @@ -33,33 +51,21 @@ const MyStats = () => { -
setShouldShowStatsAndRatings(true)}> - - - - -
- -
my_profile_store.setActiveTab(my_profile_tabs.PAYMENT_METHODS)} - > - - - - -
- -
my_profile_store.setActiveTab(my_profile_tabs.AD_TEMPLATE)} - > - - - - -
- + {tabs.map((tab, key) => { + return ( + +
+ + + + +
+ {key !== tabs.length - 1 && ( + + )} +
+ ); + })}
); diff --git a/packages/p2p/src/components/my-profile/my-profile-stats/my-profile-stats.scss b/packages/p2p/src/components/my-profile/my-profile-stats/my-profile-stats.scss index 5d1cbb0f923a..5423a122f106 100644 --- a/packages/p2p/src/components/my-profile/my-profile-stats/my-profile-stats.scss +++ b/packages/p2p/src/components/my-profile/my-profile-stats/my-profile-stats.scss @@ -1,5 +1,9 @@ .my-profile-stats { &-separator { margin: 1.6rem 0; + + @include mobile { + margin: 1.6rem -1.6rem; + } } } diff --git a/packages/p2p/src/components/my-profile/my-profile.jsx b/packages/p2p/src/components/my-profile/my-profile.jsx index 938ba0c4e0ce..ba8eada4f7e9 100644 --- a/packages/p2p/src/components/my-profile/my-profile.jsx +++ b/packages/p2p/src/components/my-profile/my-profile.jsx @@ -1,6 +1,5 @@ import React from 'react'; -import { AutoSizer, DesktopWrapper, Text, ThemedScrollbars } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; +import { AutoSizer, DesktopWrapper, Text } from '@deriv/components'; import { observer } from 'mobx-react-lite'; import { useStores } from 'Stores'; import { my_profile_tabs } from 'Constants/my-profile-tabs'; @@ -31,15 +30,13 @@ const MyProfile = () => { return ( {({ height, width }) => ( -
+
- - - - - - - + + + + +
)} diff --git a/packages/p2p/src/components/my-profile/my-profile.scss b/packages/p2p/src/components/my-profile/my-profile.scss index b61821bf5eb7..21d0fe1a0112 100644 --- a/packages/p2p/src/components/my-profile/my-profile.scss +++ b/packages/p2p/src/components/my-profile/my-profile.scss @@ -1,10 +1,30 @@ .my-profile { + display: flex; + + @include mobile { + display: block; + } + &__content { + display: flex; + flex-direction: column; + flex: 1; + overflow-x: hidden; + + &::-webkit-scrollbar { + display: none; + } + + & .p2p-toggle-container { + margin-bottom: 3.4rem; + } + @include tablet-up { max-width: 100%; min-width: 672px; } @include mobile { + display: block; padding: 0 1.6rem; width: 100vw; @@ -17,5 +37,9 @@ &__navigation { display: flex; justify-content: space-between; + + &:last-child { + margin-bottom: 1.6rem; + } } } diff --git a/packages/p2p/src/constants/my-profile-tabs.js b/packages/p2p/src/constants/my-profile-tabs.js index e2a130efd5fc..d36db86c19b9 100644 --- a/packages/p2p/src/constants/my-profile-tabs.js +++ b/packages/p2p/src/constants/my-profile-tabs.js @@ -2,4 +2,5 @@ export const my_profile_tabs = Object.freeze({ MY_STATS: 'my_stats', PAYMENT_METHODS: 'payment_methods', AD_TEMPLATE: 'ad_template', + BLOCKED_ADVERTISERS: 'blocked_advertisers', }); diff --git a/packages/p2p/src/stores/advertiser-page-store.js b/packages/p2p/src/stores/advertiser-page-store.js index f44693826db0..0f0f2d81e222 100644 --- a/packages/p2p/src/stores/advertiser-page-store.js +++ b/packages/p2p/src/stores/advertiser-page-store.js @@ -15,6 +15,8 @@ export default class AdvertiserPageStore extends BaseStore { @observable api_error_message = ''; @observable form_error_message = ''; @observable has_more_adverts_to_load = false; + @observable is_counterparty_advertiser_blocked = false; + @observable is_dropdown_menu_visible = false; @observable is_loading = true; @observable is_loading_adverts = true; @observable is_submit_disabled = true; @@ -59,7 +61,6 @@ export default class AdvertiserPageStore extends BaseStore { loadMoreAdvertiserAdverts({ startIndex }) { const { general_store } = this.root_store; this.setIsLoadingAdverts(true); - return new Promise(resolve => { requestWS({ p2p_advert_list: 1, @@ -75,7 +76,6 @@ export default class AdvertiserPageStore extends BaseStore { this.setAdverts(list); this.setHasMoreAdvertsToLoad(list.length >= general_store.list_item_limit); } - this.setIsLoadingAdverts(false); resolve(); }); @@ -85,15 +85,14 @@ export default class AdvertiserPageStore extends BaseStore { @action.bound getAdvertiserInfo() { this.setIsLoading(true); - requestWS({ p2p_advertiser_info: 1, id: this.advertiser_details_id, }).then(response => { if (!response.error) { const { p2p_advertiser_info } = response; - this.setAdvertiserInfo(p2p_advertiser_info); + this.setIsCounterpartyAdvertiserBlocked(!!p2p_advertiser_info.is_blocked); this.setAdvertiserFirstName(p2p_advertiser_info.first_name); this.setAdvertiserLastName(p2p_advertiser_info.last_name); } else { @@ -113,6 +112,12 @@ export default class AdvertiserPageStore extends BaseStore { } } + @action.bound + onCancel() { + this.root_store.general_store.setIsBlockUserModalOpen(false); + this.setIsDropdownMenuVisible(false); + } + @action.bound onCancelClick() { this.setShowAdPopup(false); @@ -129,6 +134,15 @@ export default class AdvertiserPageStore extends BaseStore { this.getAdvertiserInfo(); } + @action.bound + onSubmit() { + this.root_store.general_store.blockUnblockUser( + !this.is_counterparty_advertiser_blocked, + this.advertiser_details_id + ); + this.setIsDropdownMenuVisible(false); + } + onTabChange() { this.setAdverts([]); this.loadMoreAdvertiserAdverts({ startIndex: 0 }); @@ -164,6 +178,11 @@ export default class AdvertiserPageStore extends BaseStore { this.adverts = adverts; } + @action.bound + setIsCounterpartyAdvertiserBlocked(is_counterparty_advertiser_blocked) { + this.is_counterparty_advertiser_blocked = is_counterparty_advertiser_blocked; + } + @action.bound setCounterpartyType(counterparty_type) { this.counterparty_type = counterparty_type; @@ -184,6 +203,11 @@ export default class AdvertiserPageStore extends BaseStore { this.has_more_adverts_to_load = has_more_adverts_to_load; } + @action.bound + setIsDropdownMenuVisible(is_dropdown_menu_visible) { + this.is_dropdown_menu_visible = is_dropdown_menu_visible; + } + @action.bound setIsLoading(is_loading) { this.is_loading = is_loading; @@ -217,4 +241,14 @@ export default class AdvertiserPageStore extends BaseStore { this.setShowAdPopup(true); } } + + @action.bound + showBlockUserModal() { + if ( + !this.is_counterparty_advertiser_blocked && + this.advertiser_info.id !== this.root_store.general_store.advertiser_id + ) { + this.root_store.general_store.setIsBlockUserModalOpen(true); + } + } } diff --git a/packages/p2p/src/stores/general-store.js b/packages/p2p/src/stores/general-store.js index a55ed77bc1e0..1efda092c9de 100644 --- a/packages/p2p/src/stores/general-store.js +++ b/packages/p2p/src/stores/general-store.js @@ -13,10 +13,14 @@ export default class GeneralStore extends BaseStore { @observable active_index = 0; @observable active_notification_count = 0; @observable advertiser_id = null; + @observable block_unblock_user_error = ''; @observable balance; @observable inactive_notification_count = 0; @observable is_advertiser = false; + @observable is_advertiser_blocked = null; @observable is_blocked = false; + @observable is_block_unblock_user_loading = false; + @observable is_block_user_modal_open = false; @observable is_listed = false; @observable is_loading = false; @observable is_p2p_blocked_for_pa = false; @@ -32,6 +36,7 @@ export default class GeneralStore extends BaseStore { @observable review_period; @observable should_show_real_name = false; @observable should_show_popup = false; + @observable user_blocked_count = 0; @observable user_blocked_until = null; @observable is_high_risk_fully_authed_without_fa = false; @observable is_modal_open = false; @@ -86,6 +91,31 @@ export default class GeneralStore extends BaseStore { return this.is_blocked || this.is_high_risk_fully_authed_without_fa; } + @action.bound + blockUnblockUser(should_block, advertiser_id, should_set_is_counterparty_blocked = true) { + const { advertiser_page_store } = this.root_store; + this.setIsBlockUnblockUserLoading(true); + requestWS({ + p2p_advertiser_relations: 1, + [should_block ? 'add_blocked' : 'remove_blocked']: [advertiser_id], + }).then(response => { + if (response) { + if (!response.error) { + this.setIsBlockUserModalOpen(false); + if (should_set_is_counterparty_blocked) { + const { p2p_advertiser_relations } = response; + advertiser_page_store.setIsCounterpartyAdvertiserBlocked( + p2p_advertiser_relations.blocked_advertisers.some(ad => ad.id === advertiser_id) + ); + } + } else { + this.setBlockUnblockUserError(response.error.message); + } + } + this.setIsBlockUnblockUserLoading(false); + }); + } + @action.bound createAdvertiser(name) { requestWS({ @@ -400,6 +430,11 @@ export default class GeneralStore extends BaseStore { this.props = props; } + @action.bound + setBlockUnblockUserError(block_unblock_user_error) { + this.block_unblock_user_error = block_unblock_user_error; + } + @action.bound setInactiveNotificationCount(inactive_notification_count) { this.inactive_notification_count = inactive_notification_count; @@ -410,11 +445,26 @@ export default class GeneralStore extends BaseStore { this.is_advertiser = is_advertiser; } + @action.bound + setIsAdvertiserBlocked(is_advertiser_blocked) { + this.is_advertiser_blocked = is_advertiser_blocked; + } + @action.bound setIsBlocked(is_blocked) { this.is_blocked = is_blocked; } + @action.bound + setIsBlockUserModalOpen(is_block_user_modal_open) { + this.is_block_user_modal_open = is_block_user_modal_open; + } + + @action.bound + setIsBlockUnblockUserLoading(is_block_unblock_user_loading) { + this.is_block_unblock_user_loading = is_block_unblock_user_loading; + } + @action.bound setIsHighRiskFullyAuthedWithoutFa(is_high_risk_fully_authed_without_fa) { this.is_high_risk_fully_authed_without_fa = is_high_risk_fully_authed_without_fa; @@ -543,6 +593,11 @@ export default class GeneralStore extends BaseStore { this.should_show_popup = should_show_popup; } + @action.bound + setUserBlockedCount(user_blocked_count) { + this.user_blocked_count = user_blocked_count; + } + @action.bound setUserBlockedUntil(user_blocked_until) { this.user_blocked_until = user_blocked_until; @@ -561,13 +616,17 @@ export default class GeneralStore extends BaseStore { @action.bound updateAdvertiserInfo(response) { - const { p2p_advertiser_info } = response; + const { blocked_until, blocked_by_count, id, is_approved, is_blocked, is_listed, name } = + response?.p2p_advertiser_info || {}; + if (!response.error) { - this.setAdvertiserId(p2p_advertiser_info.id); - this.setIsAdvertiser(!!p2p_advertiser_info.is_approved); - this.setIsListed(!!p2p_advertiser_info.is_listed); - this.setNickname(p2p_advertiser_info.name); - this.setUserBlockedUntil(p2p_advertiser_info.blocked_until); + this.setAdvertiserId(id); + this.setIsAdvertiser(!!is_approved); + this.setIsAdvertiserBlocked(!!is_blocked); + this.setIsListed(!!is_listed); + this.setNickname(name); + this.setUserBlockedUntil(blocked_until); + this.setUserBlockedCount(blocked_by_count); } else { this.ws_subscriptions.advertiser_subscription.unsubscribe(); if (response.error.code === 'RestrictedCountry') { diff --git a/packages/p2p/src/stores/my-profile-store.js b/packages/p2p/src/stores/my-profile-store.js index f22f81e1ad2d..ca3f4e640eea 100644 --- a/packages/p2p/src/stores/my-profile-store.js +++ b/packages/p2p/src/stores/my-profile-store.js @@ -13,6 +13,7 @@ export default class MyProfileStore extends BaseStore { @observable advertiser_payment_methods_error = ''; @observable available_payment_methods = {}; @observable balance_available = null; + @observable blocked_advertisers_list = []; @observable contact_info = ''; @observable default_advert_description = ''; @observable delete_error_message = ''; @@ -33,6 +34,7 @@ export default class MyProfileStore extends BaseStore { @observable payment_method_to_edit = {}; @observable search_results = []; @observable search_term = ''; + @observable selected_blocked_user = {}; @observable selected_payment_method = ''; @observable selected_payment_method_display_name = ''; @observable selected_payment_method_fields = []; @@ -138,6 +140,20 @@ export default class MyProfileStore extends BaseStore { return list; } + /** + * Evaluates a new blocked_advertiser_list based on if the user has searched a blocked advertiser + * By default it returns the blocked_advertisers_list when there are no searches + * + * @returns {Array} Either the entire blocked advertisers list or filtered advertisers list by search term + */ + @computed + get rendered_blocked_advertisers_list() { + if (this.search_term) { + return this.search_results; + } + return this.blocked_advertisers_list; + } + @action.bound createPaymentMethod(values, { setSubmitting }) { setSubmitting(true); @@ -202,6 +218,24 @@ export default class MyProfileStore extends BaseStore { }); } + @action.bound + getBlockedAdvertisersList() { + this.setIsLoading(true); + requestWS({ + p2p_advertiser_relations: 1, + }).then(response => { + if (response) { + if (!response.error) { + this.setBlockedAdvertisersList(response.p2p_advertiser_relations?.blocked_advertisers); + this.loadMoreBlockedAdvertisers(); + } else { + this.root_store.general_store.setBlockUnblockUserError(response.error.message); + } + } + this.setIsLoading(false); + }); + } + @action.bound getAdvertiserPaymentMethods() { this.setIsLoading(true); @@ -345,6 +379,27 @@ export default class MyProfileStore extends BaseStore { this.setShouldShowAddPaymentMethodForm(false); } + /** + * This function loads more blocked advertisers as necessary if the user is searching for a blocked advertiser + * It updates the search_results based on the searched advertiser + */ + @action.bound + loadMoreBlockedAdvertisers() { + if (this.search_term) { + const search_results = this.blocked_advertisers_list.filter(blocked_advertiser => + blocked_advertiser.name.toLowerCase().includes(this.search_term.toLowerCase().trim()) + ); + + // if user deletes the last blocked advertiser while searching, display 'You have no blocked advertisers' message condition + if (this.search_term && search_results.length === 0 && this.blocked_advertisers_list.length === 0) { + this.setSearchTerm(''); + } + + this.setSearchResults(search_results); + } + this.setIsLoading(false); + } + @action.bound onClickDelete() { requestWS({ @@ -364,6 +419,23 @@ export default class MyProfileStore extends BaseStore { }); } + @action.bound + onClickUnblock(advertiser) { + const { general_store } = this.root_store; + + general_store.setIsBlockUserModalOpen(true); + this.setSelectedBlockedUser(advertiser); + } + + @action.bound + onSubmit() { + const { general_store } = this.root_store; + + general_store.setIsBlockUserModalOpen(false); + general_store.blockUnblockUser(false, this.selected_blocked_user.id); + this.getBlockedAdvertisersList(); + } + @action.bound showAddPaymentMethodForm() { this.setShouldShowAddPaymentMethodForm(true); @@ -501,6 +573,11 @@ export default class MyProfileStore extends BaseStore { this.balance_available = balance_available; } + @action.bound + setBlockedAdvertisersList(blocked_advertisers_list) { + this.blocked_advertisers_list = blocked_advertisers_list; + } + @action.bound setContactInfo(contact_info) { this.contact_info = contact_info; @@ -591,6 +668,11 @@ export default class MyProfileStore extends BaseStore { this.search_term = search_term; } + @action.bound + setSelectedBlockedUser(selected_blocked_user) { + this.selected_blocked_user = selected_blocked_user; + } + @action.bound setSelectedPaymentMethod(selected_payment_method) { this.selected_payment_method = selected_payment_method;